Files
kondiplo_front/app/controllers/games_controller.rb

425 lines
14 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class GamesController < ApplicationController
include GamesHelper
before_action :set_game, only: %i[ show edit update destroy join_game start_power_selection start_order_input turn_data vote_draw force_draw ]
before_action :require_login, only: %i[ new create join_game ]
before_action :require_game_admin, only: %i[ edit update destroy start_power_selection start_order_input ]
helper_method :get_available_powers_for_select
# GET /games or /games.json
def index
@recruiting_games = Game.where(status: "recruiting").includes(:participants)
@my_games = current_user ? Game.joins(:participants).where(participants: { user_id: current_user.id }).includes(:participants, :turns) : []
@games = Game.all.includes(:participants, :turns)
end
# GET /games/1 or /games/1.json
def show
@latest_turn = @game.turns.last
# ゲーム終了判定
@game_finished = @game.status == "finished"
# 表示するターンの決定
if @game_finished
# 終了済みの場合:
# params[:turn_number] があればそのターン
# なければ 最初のターン (Turn 1) を表示
if params[:turn_number].present?
@display_turn = @game.turns.find_by(number: params[:turn_number].to_i)
end
# 指定がない、または見つからない場合は初期ターン(number=1)を表示
# もし存在しなければ最新(というかあるやつ)
@display_turn ||= @game.turns.find_by(number: 1) || @latest_turn
# 最終結果の取得 (最後のターン情報から)
if @latest_turn.game_state
centers = @latest_turn.game_state["centers"] || {}
alive_powers = centers.keys
# ソロ勝利判定
solo_winner = @game.solo_victory_power(@latest_turn.game_state)
if solo_winner
result_type = "Solo Victory"
winners = [ solo_winner ]
else
result_type = "Draw"
winners = alive_powers
end
@winner_info = {
type: result_type,
winners: winners,
scores: @game.calculate_scores(@latest_turn.game_state)
}
end
else
# 進行中の場合: params[:turn_number] があればそのターン、なければ最新ターンを表示
if params[:turn_number].present?
@display_turn = @game.turns.find_by(number: params[:turn_number].to_i)
end
@display_turn ||= @latest_turn
end
if @display_turn
@game_state = @display_turn.game_state
# フェーズ名のパース
@current_season_year = parse_phase(@display_turn.phase)
# 国別情報の集計 (表示対象ターンのデータ)
centers = @game_state["centers"] || {}
units = @game_state["units"] || {}
# 全7カ国固定
powers = %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY]
# N+1 防止のためparticipantsをキャッシュ
participants_cache = @game.participants.includes(:user).index_by(&:power)
@country_statuses = powers.map do |power|
participant = participants_cache[power]
# 終了済みなら全員完了扱い、そうでなければターンごとの提出状況
submitted = @game_finished ? true : @display_turn.orders_submitted_for?(power)
{
power: power,
sc_count: centers[power]&.size || 0,
unit_count: units[power]&.size || 0,
submitted: submitted,
participant: participant,
is_user: current_user && participant&.user_id == current_user.id
}
end
# 自国を先頭に移動
if current_user
user_power_index = @country_statuses.find_index { |s| s[:is_user] }
if user_power_index
user_status = @country_statuses.delete_at(user_power_index)
@country_statuses.unshift(user_status)
end
end
end
end
# GET /games/new
def new
@game = Game.new
@game.is_solo_mode = current_user&.admin? ? false : false
end
# GET /games/1/edit
def edit
end
# POST /games or /games.json
def create
@game = Game.new(game_params)
# ソロモードかどうかを判定(管理者のみ選択可能)
if current_user&.admin? && params.dig(:game, :game_mode) == "admin_mode"
@game.is_solo_mode = true
else
@game.is_solo_mode = false
end
respond_to do |format|
if @game.save
# ゲーム作成者は自動的に参加者として登録(管理者として)
Participant.create!(
game: @game,
user: current_user,
is_administrator: true
)
# ソロモードの場合、即座に最初のターンを作成
if @game.solo_mode?
service = GameSetupService.new(@game)
result = service.setup_initial_turn
if result[:success]
@game.update!(status: "in_progress")
else
# 失敗したらロールバックしたいところだが・・・
# 現状はsave後なので、エラー表示だけにするかdestroyするか。
# 今回は簡易的にログに残す
logger.error result[:message]
end
end
format.html { redirect_to @game, notice: "ゲームが正常に作成されました。" }
format.json { render :show, status: :created, location: @game }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @game.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /games/1 or /games/1.json
def update
respond_to do |format|
if @game.update(game_params)
# スケジュール変更時にデッドラインを再計算
if @game.status == "in_progress"
@game.update_column(:next_deadline_at, @game.auto_turn? ? @game.calculate_next_deadline : nil)
end
format.html { redirect_to @game, notice: "ゲームが正常に更新されました。", status: :see_other }
format.json { render :show, status: :ok, location: @game }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @game.errors, status: :unprocessable_entity }
end
end
end
# DELETE /games/1 or /games/1.json
def destroy
@game.destroy!
respond_to do |format|
format.html { redirect_to games_path, notice: "ゲームが正常に削除されました。", status: :see_other }
format.json { head :no_content }
end
end
# POST /games/1/join
def join_game
unless current_user
redirect_to login_path, alert: "ゲームに参加するにはログインしてください"
return
end
# パスワードチェック
if @game.password_protected?
password = params.dig(:participant, :password)
unless @game.authenticate_password(password)
redirect_to @game, alert: "パスワードが正しくありません"
return
end
end
# 既に参加しているかチェック
if @game.participants.exists?(user: current_user)
redirect_to @game, alert: "既にこのゲームに参加しています"
return
end
# 満員チェック
if @game.participants.count >= @game.participants_count
redirect_to @game, alert: "このゲームは満員です"
return
end
# 参加者を作成
Participant.create!(
game: @game,
user: current_user
)
# 定員到達チェック
if @game.participants.count == @game.participants_count
@game.update(status: "power_selection")
end
redirect_to @game, notice: "ゲームに正常に参加しました!"
end
# POST /games/1/start_power_selection
def start_power_selection
unless @game.participants.count >= 2
redirect_to @game, alert: "最低2人の参加者が必要です"
return
end
@game.update!(status: "power_selection")
redirect_to @game, notice: "国選択フェーズを開始しました!"
end
# POST /games/1/start_order_input
def start_order_input
unless @game.all_powers_assigned?
redirect_to @game, alert: "全員が国を選択する必要があります"
return
end
# 最初のターンを作成
service = GameSetupService.new(@game)
result = service.setup_initial_turn
if result[:success]
update_attrs = { status: "in_progress" }
update_attrs[:next_deadline_at] = @game.calculate_next_deadline if @game.auto_turn?
@game.update!(update_attrs)
redirect_to @game, notice: "命令入力フェーズを開始しました!"
else
redirect_to @game, alert: result[:message]
end
end
# GET /games/1/turn_data.json
def turn_data
if params[:turn_number].present?
target_turn = @game.turns.find_by(number: params[:turn_number])
end
target_turn ||= @game.turns.last
return head :not_found unless target_turn
# 国別ステータス情報の再計算JSON用
# showアクションと同様のロジック
game_state = target_turn.game_state
centers = game_state["centers"] || {}
units = game_state["units"] || {}
powers = %w[AUSTRIA ENGLAND FRANCE GERMANY ITALY RUSSIA TURKEY]
country_statuses = powers.map do |power|
# 終了済みなら全員完了扱い
submitted = @game.status == "finished" ? true : target_turn.orders_submitted_for?(power)
{
power: power,
sc_count: centers[power]&.size || 0,
unit_count: units[power]&.size || 0,
submitted: submitted
}
end
render json: {
turn_number: target_turn.number,
phase: target_turn.phase,
possible_orders: target_turn.possible_orders,
decided_orders: target_turn.orders || {},
svg_orders: target_turn.svg_orders || {},
# svg_date カラムにデフォルトSVGが入っていると仮定または svg_orders["NONE"]
# ここでは svg_date が単一の画像パスかSVG文字列かによるが、
# 既存の show.html.erb 実装を見ると svg_orders["NONE"] を使っている可能性が高い
# 既存実装: default_svg: last_turn.svg_date となっていたのでそのまま変更なしでいくが
# target_turn.svg_date を返すようにする
default_svg: target_turn.svg_date,
# 完了状況
all_orders_submitted: @game.status == "finished" || @game.all_orders_submitted?,
missing_orders_powers: @game.status == "finished" ? [] : @game.participants.where(orders_submitted: false).pluck(:power).compact,
country_statuses: country_statuses
}
end
def vote_draw
return unless @game.status == "in_progress"
turn = @game.turns.last
participant = @game.participants.find_by(user: current_user)
unless participant && participant.power
redirect_to @game, alert: "権限がありません。"
return
end
power = participant.power
if turn.draw_voted?(power)
turn.revoke_draw_vote(power)
flash[:notice] = "引き分け投票を取り消しました。"
else
turn.vote_draw(power)
flash[:notice] = "引き分けに投票しました。"
end
if turn.unanimous_draw?
if execute_draw(turn)
flash[:notice] = "全会一致により、ゲームは引き分けとなりました。"
else
# エラーメッセージは execute_draw 内で flash[:alert] に設定される
end
end
redirect_to @game
end
def force_draw
return unless current_user.admin?
turn = @game.turns.last
if execute_draw(turn)
redirect_to @game, notice: "ゲームを強制的に引き分けにしました。"
else
redirect_to @game
end
end
private
def execute_draw(turn)
client = GameApiClient.new
# 生存国を勝者として扱う(引き分け)
winners = turn.powers
begin
response = client.api_game_draw(turn.game_state, winners: winners)
if response
# 新しいターン(完了状態)を作成
Turn.create!(
game: @game,
number: turn.number + 1,
# year, season カラムは存在しないため削除
# phase を COMPLETED に設定
game_state: response,
orders: {},
possible_orders: {},
phase: "COMPLETED",
svg_date: turn.svg_date,
svg_orders: turn.svg_orders
)
@game.update(status: "finished")
true
else
flash[:alert] = "ゲームサーバーからの応答が不正です。"
false
end
rescue Faraday::ConnectionFailed => e
flash[:alert] = "ゲームサーバーへの接続に失敗しました。管理者へ連絡してください。"
Rails.logger.error "API Connection Failed: #{e.message}"
false
rescue Faraday::TimeoutError => e
flash[:alert] = "ゲームサーバーとの通信がタイムアウトしました。"
Rails.logger.error "API Timeout: #{e.message}"
false
rescue StandardError => e
flash[:alert] = "予期せぬエラーが発生しました: #{e.message}"
Rails.logger.error "Execute Draw Error: #{e.message}"
false
end
end
def set_game
@game = Game.find(params.expect(:id))
end
def require_game_admin
unless current_user&.admin? || @game.administrator == current_user
redirect_to @game, alert: "ゲーム管理者のみこの機能にアクセスできます"
end
end
def game_params
house_rules = [ :year_limit, :victory_sc_count, :scoring_system, :turn_schedule ]
if action_name == "update"
params.expect(game: [ :title, :memo, :auto_order_mode ] + house_rules)
else
permitted = [ :title, :memo ] + house_rules
if current_user&.admin?
permitted += [ :participants_count, :password, :auto_order_mode ]
else
# 一般ユーザーはマルチプレイヤーモードのみ
permitted += [ :participants_count, :password, :auto_order_mode ]
end
params.expect(game: permitted)
end
end
end