class GekanatorGamesController < ApplicationController def create return head :unauthorized unless current_user guessed_post_id = params.require(:guessed_post_id) correct_post_id = params[:correct_post_id].presence answers = params.require(:answers).as_json game = GekanatorGame.new( user: current_user, guessed_post_id:, correct_post_id:, won: correct_post_id.present? && guessed_post_id.to_i == correct_post_id.to_i, question_count: answers.length, answers:) if game.save render json: { id: game.id }, status: :created else render json: { errors: game.errors.full_messages }, status: :unprocessable_entity end end def extra_questions game = find_owned_game return if performed? questions = GekanatorQuestion .accepted .includes(:gekanator_question_examples) .where(kind: 'post_similarity', source: 'user_suggested') .to_a selected = weighted_sample_questions( questions, post_id: game.correct_post_id, limit: 2) render json: { questions: selected.map { |question| extra_question_json(question) } } end def extra_question_answers game = find_owned_game return if performed? answer_params = params.require(:answers) if !answer_params.is_a?(Array) return render_validation_error fields: { answers: ['配列で指定してください.'] } end answers = answer_params.map { |answer| { question_id: answer.require(:question_id).to_i, answer: answer.require(:answer) } } questions = GekanatorQuestion.where(id: answers.map { _1[:question_id] }) question_by_id = questions.index_by(&:id) if questions.length != answers.length return render_validation_error fields: { answers: ['質問が見つかりません.'] } end if questions.any? { |question| question.status != 'accepted' || question.kind != 'post_similarity' } return render_validation_error fields: { answers: ['質問が不正です.'] } end ActiveRecord::Base.transaction do answers.each do |item| question = question_by_id[item[:question_id]] example = GekanatorQuestionExample.find_or_initialize_by( gekanator_question: question, post: game.correct_post, user: current_user) example.record_answer!( answer: item[:answer], source: 'post_game_extra', gekanator_game: game) example.save! end end render json: { count: answers.length }, status: :created end private def extra_question_json question { id: question.id, text: question.text, source: question.source, priority_weight: question.priority_weight } end def weighted_sample_questions questions, post_id:, limit: remaining = questions.uniq(&:id) selected = [] while selected.length < limit && remaining.any? weighted = remaining.map { |question| [question, selection_weight_for(question, post_id: post_id)] } total_weight = weighted.sum { |_question, weight| weight } break if total_weight <= 0 target = rand * total_weight cumulative = 0.0 chosen = weighted.find do |_question, weight| cumulative += weight cumulative >= target end&.first || weighted.first.first selected << chosen remaining.reject! { |question| question.id == chosen.id } end selected end def selection_weight_for question, post_id: sample_count = question.gekanator_question_examples.sum { |example| next 0 unless example.post_id == post_id example.sample_count.presence || 1 } question.priority_weight.to_f / (1.0 + sample_count * 0.15) end def find_owned_game return head :unauthorized unless current_user game = GekanatorGame.find_by(id: params[:id]) return head :not_found unless game if !current_user.admin? && game.user_id != current_user.id return head :not_found end game end end