136 lines
5.1 KiB
Ruby
136 lines
5.1 KiB
Ruby
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
|