フロントエンドプレイアブル
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
2026-02-15 14:57:17 +09:00
commit f25fd6f802
198 changed files with 10342 additions and 0 deletions

165
app/models/game.rb Normal file
View File

@@ -0,0 +1,165 @@
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