Files
euchre_camp/app/services/elo_calculator.rb
T
david 1268889a78 Add Elo rating system with match entry and CSV upload
- Implement EloCalculator service for 2v2 Euchre matches
- Create background job processing with GoodJob
- Add single match entry form
- Add CSV upload for batch match entry
- Create player rankings view
- Add database schema for matches and elo snapshots
2026-03-26 16:28:24 -07:00

81 lines
2.9 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)
team_1_wins = team_1_score >= 10
team_2_wins = team_2_score >= 10
unless team_1_wins || team_2_wins
raise ArgumentError, "One team must reach 10 points to win"
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