Files
euchre_camp/app/services/elo_calculator.rb
T
david 119905685f fix: correct Elo calculator winner validation logic
The Elo calculator was incorrectly rejecting valid scores (10-7) because
it checked if team score >= 10 without verifying the opposing team's score.

Changes:
- Update winner determination to check both score >= 10 AND score > opponent
- Add detailed error message with actual scores for debugging
- Ensure only one team can win (not both reaching 10 points)
2026-03-27 12:24:46 -07:00

82 lines
3.1 KiB
Ruby

# frozen_string_literal: true
module EuchreCamp
module Services
class EloCalculator
# Standard Elo K-factor
K_FACTOR = 32
# Calculate Elo ratings for a 2v2 Euchre match
#
# @param player_ratings [Hash] player_id => current_rating
# @param team_1_players [Array<Integer>] player IDs for team 1
# @param team_2_players [Array<Integer>] player IDs for team 2
# @param team_1_score [Integer] score for team 1
# @param team_2_score [Integer] score for team 2
# @return [Hash] player_id => { rating_before:, rating_after:, rating_change: }
def self.calculate(player_ratings:, team_1_players:, team_2_players:, team_1_score:, team_2_score:)
# Validate inputs
raise ArgumentError, "Team 1 must have exactly 2 players" unless team_1_players.length == 2
raise ArgumentError, "Team 2 must have exactly 2 players" unless team_2_players.length == 2
# Determine winner (first to 10 points)
# Allow for exact score tie at 10-10 if provided
team_1_wins = team_1_score > team_2_score && team_1_score >= 10
team_2_wins = team_2_score > team_1_score && team_2_score >= 10
unless team_1_wins || team_2_wins
raise ArgumentError, "One team must reach 10 points to win (Team 1: #{team_1_score}, Team 2: #{team_2_score})"
end
if team_1_wins && team_2_wins
raise ArgumentError, "Only one team can win (both reached 10 points)"
end
winner = team_1_wins ? :team_1 : :team_2
# Calculate average ratings for each team
team_1_avg = team_1_players.map { |id| player_ratings[id] }.sum / 2.0
team_2_avg = team_2_players.map { |id| player_ratings[id] }.sum / 2.0
# Calculate expected scores using Elo formula
# E = 1 / (1 + 10^((R_opp - R_team) / 400))
expected_team_1 = 1.0 / (1.0 + 10.0**((team_2_avg - team_1_avg) / 400.0))
expected_team_2 = 1.0 / (1.0 + 10.0**((team_1_avg - team_2_avg) / 400.0))
# Actual scores (1 for win, 0 for loss)
actual_team_1 = team_1_wins ? 1.0 : 0.0
actual_team_2 = team_2_wins ? 1.0 : 0.0
# Calculate rating changes for each player
results = {}
# Team 1 players
team_1_players.each do |player_id|
old_rating = player_ratings[player_id]
rating_change = K_FACTOR * (actual_team_1 - expected_team_1)
new_rating = (old_rating + rating_change).round
results[player_id] = {
rating_before: old_rating,
rating_after: new_rating,
rating_change: rating_change.round
}
end
# Team 2 players
team_2_players.each do |player_id|
old_rating = player_ratings[player_id]
rating_change = K_FACTOR * (actual_team_2 - expected_team_2)
new_rating = (old_rating + rating_change).round
results[player_id] = {
rating_before: old_rating,
rating_after: new_rating,
rating_change: rating_change.round
}
end
results
end
end
end
end