class Game < ApplicationRecord has_many :turns, dependent: :destroy has_many :participants, dependent: :destroy has_many :users, through: :participants # パスワード保護 has_secure_password :password, validations: false # バリデーション validates :status, inclusion: { in: %w[recruiting power_selection in_progress finished cancelled] } validates :auto_order_mode, inclusion: { in: %w[hold random] } validates :participants_count, numericality: { greater_than_or_equal_to: 2, less_than_or_equal_to: 7 }, unless: :is_solo_mode? # ハウスルールバリデーション validates :year_limit, numericality: { only_integer: true, greater_than_or_equal_to: 1901, less_than_or_equal_to: 1999 }, allow_nil: true validates :victory_sc_count, numericality: { only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 34 } validates :scoring_system, inclusion: { in: %w[none sc_count sc_ratio dss sos] } # ターンスケジュールバリデーション validate :validate_turn_schedule # ヘルパーメソッド def password_protected? password_digest.present? end def solo_mode? is_solo_mode end def administrator participants.find_by(is_administrator: true)&.user end def available_powers assigned_powers = participants.where.not(power: nil).pluck(:power) %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY] - assigned_powers end def all_powers_assigned? participants.where(power: nil).empty? && participants.count == participants_count end def all_orders_submitted? participants.where(power: nil).empty? && participants.all?(&:orders_submitted) end def unassigned_powers all_powers = %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY] assigned_powers = participants.where.not(power: nil).pluck(:power) all_powers - assigned_powers end # ターンスケジュール関連メソッド def auto_turn? turn_schedule.present? end # 次のデッドラインを計算(日本時間基準) def calculate_next_deadline return nil unless turn_schedule.present? hours = turn_schedule.split(",").map(&:strip).map(&:to_i).sort now = Time.current.in_time_zone("Asia/Tokyo") # 今日の残りの時間枠を探す(JST基準) next_time = hours.map { |h| now.beginning_of_day + h.hours } .find { |t| t > now } # 今日の枠がなければ翌日の最初の枠 next_time || (now.beginning_of_day + 1.day + hours.first.hours) end # スケジュール表示用 def schedule_display return "手動" unless turn_schedule.present? hours = turn_schedule.split(",").map(&:strip) "毎日 " + hours.map { |h| "#{h}時" }.join("・") end # ハウスルール関連メソッド # 年数制限チェック: フェーズ名から年を抽出し、year_limitを超えているか判定 def year_limit_reached?(phase_name) return false unless year_limit.present? && phase_name.present? # フェーズ名の例: "S1901M", "F1910R", "W1901A" year_match = phase_name.match(/[SFW](\d{4})[MRA]/) return false unless year_match year_match[1].to_i > year_limit end # ソロ勝利判定: いずれかの国のSC数がvictory_sc_count以上か def solo_victory?(game_state) centers = game_state&.dig("centers") || {} centers.any? { |_power, scs| scs.size >= victory_sc_count } end # ソロ勝利した国を返す def solo_victory_power(game_state) centers = game_state&.dig("centers") || {} centers.find { |_power, scs| scs.size >= victory_sc_count }&.first end # スコア計算 def calculate_scores(game_state) return {} if scoring_system == "none" centers = game_state&.dig("centers") || {} total_scs = centers.values.flatten.size alive_count = centers.count { |_power, scs| scs.any? } case scoring_system when "sc_count" # 単純SC数 centers.transform_values { |scs| scs.size } when "sc_ratio" # SC比率(%) centers.transform_values { |scs| total_scs > 0 ? (scs.size.to_f / total_scs * 100).round(1) : 0 } when "dss" # Draw Size Scoring: 生存国で均等分割 centers.transform_values { |scs| scs.any? ? (100.0 / alive_count).round(1) : 0 } when "sos" # Sum of Squares sum_of_squares = centers.values.sum { |scs| scs.size ** 2 }.to_f centers.transform_values { |scs| sum_of_squares > 0 ? ((scs.size ** 2) / sum_of_squares * 100).round(1) : 0 } else {} end end # スコアリング方式の日本語名 def scoring_system_name case scoring_system when "none" then "なし" when "sc_count" then "SC数" when "sc_ratio" then "SC比率" when "dss" then "DSS(均等分割)" when "sos" then "SoS(二乗和)" else scoring_system end end private def validate_turn_schedule return if turn_schedule.blank? hours = turn_schedule.split(",").map(&:strip) unless hours.all? { |h| h.match?(/\A\d{1,2}\z/) && h.to_i.between?(0, 23) } errors.add(:turn_schedule, "は0〜23の数値をカンマ区切りで入力してください(例: 0,18)") end end end