lib/active_genie/ranking/players_collection.rb



require_relative './player'

module ActiveGenie::Ranking
  class PlayersCollection
    def initialize(param_players)
      @players = build(param_players)
    end
    attr_reader :players

    def coefficient_of_variation
      score_list = eligible.map(&:score).compact
      return nil if score_list.empty?

      mean = score_list.sum.to_f / score_list.size
      return nil if mean == 0

      variance = score_list.map { |num| (num - mean) ** 2 }.sum / score_list.size
      standard_deviation = Math.sqrt(variance)

      (standard_deviation / mean) * 100
    end

    def calc_relegation_tier
      eligible[(tier_size*-1)..-1]
    end

    def calc_defender_tier
      eligible[(tier_size*-2)...(tier_size*-1)]
    end

    def eligible
      sorted.reject(&:eliminated)
    end

    def eligible_size
      @players.reject(&:eliminated).size
    end

    def elo_eligible?
      eligible.size > 15
    end

    def sorted
      sorted_players = @players.sort_by { |p| [-p.ffa_score, -(p.elo || 0), -(p.score || 0)] }
      sorted_players.each_with_index { |p, i| p.rank = i + 1 }
      sorted_players
    end

    def to_h
      sorted.map { |p| p.to_h }
    end

    def method_missing(...)
      @players.send(...)
    end

    private

    def build(param_players)
      param_players.map { |p| Player.new(p) }
    end

    # Returns the number of players to battle in each round
    # based on the eligible size, start fast and go slow until top 10
    # Example:
    #   - 50 eligible, tier_size: 15
    #   - 35 eligible, tier_size: 11 
    #   - 24 eligible, tier_size: 10
    #   - 14 eligible, tier_size: 4
    #  4 rounds to reach top 10 with 50 players
    def tier_size
      [[(eligible_size / 3).ceil, 10].max, eligible_size - 10].min
    end
  end
end