ac858f1e6d
Implement comprehensive partnership tracking system and tournament management features including round-robin, single/double elimination, and Swiss system pairings. Changes: - Add partnership_games and partnership_stats tables - Implement PartnershipTracker service for recording partnership data - Add tournament generator with multiple formats - Create tournament management actions and views - Add acceptance tests for partnership tracking - Fix PartnershipTracker to use single snapshot per player per match - Fix round-robin test to query by round_number instead of hardcoded ID Note: All 8 acceptance tests now passing
109 lines
4.0 KiB
Ruby
109 lines
4.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module EuchreCamp
|
|
module Services
|
|
class PartnershipTracker
|
|
# Record partnerships when a match completes
|
|
def self.record_match(match_id)
|
|
rom = EuchreCamp::App["persistence.rom"]
|
|
match = rom.relations[:matches].by_pk(match_id).one
|
|
|
|
return unless match
|
|
|
|
games_repo = EuchreCamp::App["repositories.partnership_games"]
|
|
stats_repo = EuchreCamp::App["repositories.partnership_stats"]
|
|
|
|
# Calculate Elo changes for each player
|
|
# This is called AFTER Elo calculation, so we get the change from elo_snapshots
|
|
elo_changes = calculate_elo_changes(match_id)
|
|
|
|
# Record Team 1 partnership
|
|
record_partnership(
|
|
games_repo: games_repo,
|
|
stats_repo: stats_repo,
|
|
match_id: match_id,
|
|
player_1_id: match[:team_1_p1_id],
|
|
player_2_id: match[:team_1_p2_id],
|
|
team_number: 1,
|
|
won: match[:team_1_score] > match[:team_2_score],
|
|
elo_change: elo_changes[match[:team_1_p1_id]] + elo_changes[match[:team_1_p2_id]]
|
|
)
|
|
|
|
# Record Team 2 partnership
|
|
record_partnership(
|
|
games_repo: games_repo,
|
|
stats_repo: stats_repo,
|
|
match_id: match_id,
|
|
player_1_id: match[:team_2_p1_id],
|
|
player_2_id: match[:team_2_p2_id],
|
|
team_number: 2,
|
|
won: match[:team_2_score] > match[:team_1_score],
|
|
elo_change: elo_changes[match[:team_2_p1_id]] + elo_changes[match[:team_2_p2_id]]
|
|
)
|
|
end
|
|
|
|
def self.calculate_elo_changes(match_id)
|
|
rom = EuchreCamp::App["persistence.rom"]
|
|
|
|
# Get the two most recent snapshots before and after this match
|
|
snapshots = rom.relations[:elo_snapshots]
|
|
.where(match_id: match_id)
|
|
.order(:created_at)
|
|
.to_a
|
|
|
|
# Group by player and calculate change
|
|
changes = Hash.new(0)
|
|
snapshots.group_by { |s| s[:player_id] }.each do |player_id, player_snapshots|
|
|
# For a single match, there should be exactly one snapshot per player
|
|
# The snapshot contains rating_before and rating_after for this specific match
|
|
# So we calculate the change directly from that snapshot
|
|
player_snapshots.each do |snapshot|
|
|
changes[player_id] = snapshot[:rating_after] - snapshot[:rating_before]
|
|
end
|
|
end
|
|
|
|
changes
|
|
end
|
|
|
|
def self.record_partnership(games_repo:, stats_repo:, match_id:, player_1_id:, player_2_id:, team_number:, won:, elo_change:)
|
|
# Record the partnership game
|
|
games_repo.create(
|
|
match_id: match_id,
|
|
player_1_id: player_1_id,
|
|
player_2_id: player_2_id,
|
|
team_number: team_number,
|
|
won_match: won ? 1 : 0
|
|
)
|
|
|
|
# Update partnership statistics (using consistent ordering)
|
|
stats_repo.update_stats(player_1_id, player_2_id, won: won, elo_change: elo_change)
|
|
end
|
|
|
|
# Get partnership statistics for a player
|
|
def self.get_player_partnerships(player_id)
|
|
stats_repo = EuchreCamp::App["repositories.partnership_stats"]
|
|
players_repo = EuchreCamp::App["repositories.players"]
|
|
|
|
stats = stats_repo.by_player(player_id)
|
|
all_players = players_repo.all.to_h { |p| [p[:id], p] }
|
|
|
|
stats.map do |stat|
|
|
partner_id = stat[:player_1_id] == player_id ? stat[:player_2_id] : stat[:player_1_id]
|
|
partner = all_players[partner_id]
|
|
|
|
{
|
|
partner: partner,
|
|
games_played: stat[:games_played],
|
|
wins: stat[:wins],
|
|
losses: stat[:losses],
|
|
win_rate: stat[:games_played] > 0 ? (stat[:wins].to_f / stat[:games_played]).round(3) : 0,
|
|
total_elo_change: stat[:total_elo_change],
|
|
avg_elo_change: stat[:games_played] > 0 ? (stat[:total_elo_change].to_f / stat[:games_played]).round(2) : 0,
|
|
last_played: stat[:last_played]
|
|
}
|
|
end.sort_by { |p| -p[:games_played] }
|
|
end
|
|
end
|
|
end
|
|
end
|