class ActiveGenie::Ranking::Ranking
def self.call(...)
def self.call(...) new(...).call end
def call
def call initial_log set_initial_player_scores! eliminate_obvious_bad_players! while @players.elo_eligible? elo_report = run_elo_round! eliminate_relegation_players! rebalance_players!(elo_report) end run_free_for_all! final_logs @players.sorted end
def eliminate_obvious_bad_players!
def eliminate_obvious_bad_players! while @players.coefficient_of_variation >= SCORE_VARIATION_THRESHOLD @players.eligible.last.eliminated = ELIMINATION_VARIATION end end
def eliminate_relegation_players!
def eliminate_relegation_players! @players.calc_relegation_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION } end
def final_logs
def final_logs ActiveGenie::Logger.debug({ code: :ranking_final, players: @players.sorted.map(&:to_h) }) ActiveGenie::Logger.info({ code: :ranking, **report }) end
def initial_log
def initial_log @players.each { |p| ActiveGenie::Logger.debug({ code: :new_player, player: p.to_h }) } end
def initialize(param_players, criteria, reviewers: [], config: {})
def initialize(param_players, criteria, reviewers: [], config: {}) @criteria = criteria @reviewers = Array(reviewers).compact.uniq @config = ActiveGenie::Configuration.to_h(config) @players = PlayersCollection.new(param_players) @elo_rounds_played = 0 @elo_round_battle_count = 0 @free_for_all_battle_count = 0 @total_tokens = 0 @start_time = Time.now end
def log_context
def log_context { config: @config[:log], ranking_id: } end
def ranking_id
def ranking_id player_ids = @players.map(&:id).join(',') ranking_unique_key = [player_ids, @criteria, @config.to_json].join('-') Digest::MD5.hexdigest(ranking_unique_key) end
def rebalance_players!(elo_report)
def rebalance_players!(elo_report) return if elo_report[:highest_elo_diff].negative? @players.eligible.each do |player| next if elo_report[:players_in_round].include?(player.id) player.elo += elo_report[:highest_elo_diff] end end
def report
def report { ranking_id: ranking_id, players_count: @players.size, variation_too_high: @players.select { |player| player.eliminated == ELIMINATION_VARIATION }.size, elo_rounds_played: @elo_rounds_played, elo_round_battle_count: @elo_round_battle_count, relegation_tier: @players.select { |player| player.eliminated == ELIMINATION_RELEGATION }.size, ffa_round_battle_count: @free_for_all_battle_count, top3: @players.eligible[0..2].map(&:id), total_tokens: @total_tokens, duration_seconds: Time.now - @start_time, } end
def run_elo_round!
def run_elo_round! @elo_rounds_played += 1 elo_report = EloRound.call(@players, @criteria, config: @config) @elo_round_battle_count += elo_report[:battles_count] elo_report end
def run_free_for_all!
def run_free_for_all! ffa_report = FreeForAll.call(@players, @criteria, config: @config) @free_for_all_battle_count += ffa_report[:battles_count] end
def set_initial_player_scores!
def set_initial_player_scores! RankingScoring.call(@players, @criteria, reviewers: @reviewers, config: @config) end