class TurnProcessingService def initialize(turn, client: GameApiClient.new) @turn = turn @game = turn.game @client = client end def process(force: false) # Check for unsubmitted orders in multiplayer if !@game.solo_mode? && !@game.all_orders_submitted? unless force == "true" return { success: false, message: "全プレイヤーの命令入力が完了していません。強制ターン終了ボタンを使用してください。" } end end current_orders = @turn.orders || {} # 非人間プレイヤーのランダム命令を事前に生成(ターン処理の前に) if @game.auto_order_mode == "random" && !@game.solo_mode? # ゲーム状態から全国のリストを取得(参加者だけでなく全国にランダム命令を生成) all_powers = @turn.game_state&.dig("units")&.keys || [] submitted_powers = current_orders.keys.map(&:upcase) all_powers.each do |power| # 既に命令が提出済みならスキップ next if submitted_powers.include?(power.upcase) auto_orders_response = @client.api_calculate_auto_orders(@turn.game_state, power) if auto_orders_response && auto_orders_response["orders"] current_orders[power] = auto_orders_response["orders"] end end # 自動生成した命令を現在のターンに保存 @turn.update_columns(orders: current_orders) if current_orders.present? end # 現在のターンにALL SVG(全プレイヤーの命令を含む画像)を保存 # 履歴モードで「このターンでどんな命令が出されたか」を表示するため if current_orders.present? current_svg_orders = @turn.svg_orders || {} all_svg_current = @client.api_render(@turn.game_state, orders: current_orders) if all_svg_current current_svg_orders["ALL"] = all_svg_current @turn.update_columns(svg_orders: current_svg_orders) end end # Calculate next state process_response = @client.api_calculate_process(@turn.game_state, current_orders) unless process_response return { success: false, message: "ターン処理に失敗しました。" } end new_game_state = process_response["game_state"] # Transaction to ensure data consistency Game.transaction do # Create next turn foundation possible_orders = @client.api_calculate_possible_orders(new_game_state, by_power: true) svg = @client.api_render(new_game_state, orders: nil) new_turn = @game.turns.build( number: @turn.number + 1, game_state: new_game_state, orders: {}, phase: new_game_state&.dig("name"), possible_orders: possible_orders, svg_date: svg, svg_orders: { "NONE" => svg } ) # Save new turn if new_turn.save # Reset orders_submitted flag @game.participants.update_all(orders_submitted: false) unless @game.solo_mode? # ハウスルール: 勝利条件判定 result = check_victory_conditions(new_turn, new_game_state) return result if result { success: true, message: "ターンを終了し、次のフェーズへ進みました。" } else { success: false, message: "次のターンの作成に失敗しました: #{new_turn.errors.full_messages.join(', ')}" } end end rescue StandardError => e Rails.logger.error("Turn processing failed: #{e.message}") Rails.logger.error(e.backtrace.join("\n")) { success: false, message: "予期せぬエラーが発生しました。" } end private def check_victory_conditions(new_turn, new_game_state) centers = new_game_state&.dig("centers") || {} # 1. ソロ勝利判定 if @game.solo_victory?(new_game_state) winner = @game.solo_victory_power(new_game_state) finish_game(new_turn, "solo", [ winner ]) return { success: true, message: "#{winner} が #{@game.victory_sc_count} SC を獲得し、ソロ勝利しました!" } end # 2. 年数制限判定 if @game.year_limit_reached?(new_turn.phase) # SC数最多の国が勝者、同数なら引き分け max_sc = centers.values.map(&:size).max winners = centers.select { |_power, scs| scs.size == max_sc }.keys finish_game(new_turn, winners.size == 1 ? "year_limit_solo" : "year_limit_draw", winners) result_type = winners.size == 1 ? "#{winners.first} の勝利" : "#{winners.join(', ')} の引き分け" return { success: true, message: "年数制限(#{@game.year_limit}年)に達しました。#{result_type}です。" } end nil end def finish_game(last_turn, result_type, winners) # 完了ターンを作成 Turn.create!( game: @game, number: last_turn.number + 1, game_state: last_turn.game_state, orders: {}, possible_orders: {}, phase: "COMPLETED", svg_date: last_turn.svg_date, svg_orders: last_turn.svg_orders ) @game.update!(status: "finished") Rails.logger.info("Game #{@game.id} finished: #{result_type}, winners: #{winners.join(', ')}") end end