mirror of
				https://github.com/avitex/elixir-glicko
				synced 2025-11-03 23:23:28 +00:00 
			
		
		
		
	Run mix format
				
					
				
			Add formatter configuration and run `mix format`. Additionally, manually replace leading tabs in documentation examples with four spaces.
This commit is contained in:
		
							parent
							
								
									8612b2aa97
								
							
						
					
					
						commit
						641c661b45
					
				
							
								
								
									
										3
									
								
								.formatter.exs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.formatter.exs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
[
 | 
			
		||||
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										459
									
								
								lib/glicko.ex
									
									
									
									
									
								
							
							
						
						
									
										459
									
								
								lib/glicko.ex
									
									
									
									
									
								
							@ -1,246 +1,311 @@
 | 
			
		||||
defmodule Glicko do
 | 
			
		||||
	@moduledoc """
 | 
			
		||||
	Provides the implementation of the Glicko rating system.
 | 
			
		||||
  @moduledoc """
 | 
			
		||||
  Provides the implementation of the Glicko rating system.
 | 
			
		||||
 | 
			
		||||
	See the [specification](http://www.glicko.net/glicko/glicko2.pdf) for implementation details.
 | 
			
		||||
  See the [specification](http://www.glicko.net/glicko/glicko2.pdf) for implementation details.
 | 
			
		||||
 | 
			
		||||
	## Usage
 | 
			
		||||
  ## Usage
 | 
			
		||||
 | 
			
		||||
	Get a player's new rating after a series of matches in a rating period.
 | 
			
		||||
  Get a player's new rating after a series of matches in a rating period.
 | 
			
		||||
 | 
			
		||||
		iex> results = [Result.new(Player.new_v1([rating: 1400, rating_deviation: 30]), :win),
 | 
			
		||||
		...> Result.new(Player.new_v1([rating: 1550, rating_deviation: 100]), :loss),
 | 
			
		||||
		...> Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss)]
 | 
			
		||||
		iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
			
		||||
		iex> Glicko.new_rating(player, results, [system_constant: 0.5])
 | 
			
		||||
		{1464.0506705393013, 151.51652412385727}
 | 
			
		||||
      iex> results = [Result.new(Player.new_v1([rating: 1400, rating_deviation: 30]), :win),
 | 
			
		||||
      ...> Result.new(Player.new_v1([rating: 1550, rating_deviation: 100]), :loss),
 | 
			
		||||
      ...> Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss)]
 | 
			
		||||
      iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
			
		||||
      iex> Glicko.new_rating(player, results, [system_constant: 0.5])
 | 
			
		||||
      {1464.0506705393013, 151.51652412385727}
 | 
			
		||||
 | 
			
		||||
	Get a player's new rating when they haven't played within a rating period.
 | 
			
		||||
  Get a player's new rating when they haven't played within a rating period.
 | 
			
		||||
 | 
			
		||||
		iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
			
		||||
		iex> Glicko.new_rating(player, [], [system_constant: 0.5])
 | 
			
		||||
		{1.5e3, 200.27141669877065}
 | 
			
		||||
      iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
			
		||||
      iex> Glicko.new_rating(player, [], [system_constant: 0.5])
 | 
			
		||||
      {1.5e3, 200.27141669877065}
 | 
			
		||||
 | 
			
		||||
	Calculate the probability of a player winning against an opponent.
 | 
			
		||||
  Calculate the probability of a player winning against an opponent.
 | 
			
		||||
 | 
			
		||||
		iex> player = Player.new_v1
 | 
			
		||||
		iex> opponent = Player.new_v1
 | 
			
		||||
		iex> Glicko.win_probability(player, opponent)
 | 
			
		||||
		0.5
 | 
			
		||||
      iex> player = Player.new_v1
 | 
			
		||||
      iex> opponent = Player.new_v1
 | 
			
		||||
      iex> Glicko.win_probability(player, opponent)
 | 
			
		||||
      0.5
 | 
			
		||||
 | 
			
		||||
	Calculate the probability of a player drawing against an opponent.
 | 
			
		||||
  Calculate the probability of a player drawing against an opponent.
 | 
			
		||||
 | 
			
		||||
		iex> player = Player.new_v1
 | 
			
		||||
		iex> opponent = Player.new_v1
 | 
			
		||||
		iex> Glicko.draw_probability(player, opponent)
 | 
			
		||||
		1.0
 | 
			
		||||
      iex> player = Player.new_v1
 | 
			
		||||
      iex> opponent = Player.new_v1
 | 
			
		||||
      iex> Glicko.draw_probability(player, opponent)
 | 
			
		||||
      1.0
 | 
			
		||||
 | 
			
		||||
	"""
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
	alias __MODULE__.{
 | 
			
		||||
		Player,
 | 
			
		||||
		Result,
 | 
			
		||||
	}
 | 
			
		||||
  alias __MODULE__.{
 | 
			
		||||
    Player,
 | 
			
		||||
    Result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	@default_system_constant 0.8
 | 
			
		||||
	@default_convergence_tolerance 1.0e-7
 | 
			
		||||
  @default_system_constant 0.8
 | 
			
		||||
  @default_convergence_tolerance 1.0e-7
 | 
			
		||||
 | 
			
		||||
	@type new_rating_opts :: [system_constant: float, convergence_tolerance: float]
 | 
			
		||||
  @type new_rating_opts :: [system_constant: float, convergence_tolerance: float]
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Calculates the probability of a player winning against an opponent.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Calculates the probability of a player winning against an opponent.
 | 
			
		||||
 | 
			
		||||
	Returns a value between `0.0` and `1.0`.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec win_probability(player :: Player.t, opponent :: Player.t) :: float
 | 
			
		||||
	def win_probability(player, opponent) do
 | 
			
		||||
		win_probability(player |> Player.rating(:v2), opponent |> Player.rating(:v2), opponent |> Player.rating_deviation(:v2))
 | 
			
		||||
	end
 | 
			
		||||
  Returns a value between `0.0` and `1.0`.
 | 
			
		||||
  """
 | 
			
		||||
  @spec win_probability(player :: Player.t(), opponent :: Player.t()) :: float
 | 
			
		||||
  def win_probability(player, opponent) do
 | 
			
		||||
    win_probability(
 | 
			
		||||
      player |> Player.rating(:v2),
 | 
			
		||||
      opponent |> Player.rating(:v2),
 | 
			
		||||
      opponent |> Player.rating_deviation(:v2)
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Calculates the probability of a player winning against an opponent from a player rating, opponent rating and opponent rating deviation.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Calculates the probability of a player winning against an opponent from a player rating, opponent rating and opponent rating deviation.
 | 
			
		||||
 | 
			
		||||
	Values provided for the player rating, opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
  Values provided for the player rating, opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
 | 
			
		||||
	Returns a value between `0.0` and `1.0`.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec win_probability(player_rating :: Player.rating, opponent_rating :: Player.rating, opponent_rating_deviation :: Player.rating_deviation) :: float
 | 
			
		||||
	def win_probability(player_rating, opponent_rating, opponent_rating_deviation) do
 | 
			
		||||
		calc_e(player_rating, opponent_rating, calc_g(opponent_rating_deviation))
 | 
			
		||||
	end
 | 
			
		||||
  Returns a value between `0.0` and `1.0`.
 | 
			
		||||
  """
 | 
			
		||||
  @spec win_probability(
 | 
			
		||||
          player_rating :: Player.rating(),
 | 
			
		||||
          opponent_rating :: Player.rating(),
 | 
			
		||||
          opponent_rating_deviation :: Player.rating_deviation()
 | 
			
		||||
        ) :: float
 | 
			
		||||
  def win_probability(player_rating, opponent_rating, opponent_rating_deviation) do
 | 
			
		||||
    calc_e(player_rating, opponent_rating, calc_g(opponent_rating_deviation))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Calculates the probability of a player drawing against an opponent.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Calculates the probability of a player drawing against an opponent.
 | 
			
		||||
 | 
			
		||||
	Returns a value between `0.0` and `1.0`.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec draw_probability(player :: Player.t, opponent :: Player.t) :: float
 | 
			
		||||
	def draw_probability(player, opponent) do
 | 
			
		||||
		draw_probability(player |> Player.rating(:v2), opponent |> Player.rating(:v2), opponent |> Player.rating_deviation(:v2))
 | 
			
		||||
	end
 | 
			
		||||
  Returns a value between `0.0` and `1.0`.
 | 
			
		||||
  """
 | 
			
		||||
  @spec draw_probability(player :: Player.t(), opponent :: Player.t()) :: float
 | 
			
		||||
  def draw_probability(player, opponent) do
 | 
			
		||||
    draw_probability(
 | 
			
		||||
      player |> Player.rating(:v2),
 | 
			
		||||
      opponent |> Player.rating(:v2),
 | 
			
		||||
      opponent |> Player.rating_deviation(:v2)
 | 
			
		||||
    )
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Calculates the probability of a player drawing against an opponent from a player rating, opponent rating and opponent rating deviation.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Calculates the probability of a player drawing against an opponent from a player rating, opponent rating and opponent rating deviation.
 | 
			
		||||
 | 
			
		||||
	Values provided for the player rating, opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
  Values provided for the player rating, opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
 | 
			
		||||
	Returns a value between `0.0` and `1.0`.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec draw_probability(player_rating :: Player.rating, opponent_rating :: Player.rating, opponent_rating_deviation :: Player.rating_deviation) :: float
 | 
			
		||||
	def draw_probability(player_rating, opponent_rating, opponent_rating_deviation) do
 | 
			
		||||
		1 - abs(win_probability(player_rating, opponent_rating, opponent_rating_deviation) - 0.5) / 0.5
 | 
			
		||||
	end
 | 
			
		||||
  Returns a value between `0.0` and `1.0`.
 | 
			
		||||
  """
 | 
			
		||||
  @spec draw_probability(
 | 
			
		||||
          player_rating :: Player.rating(),
 | 
			
		||||
          opponent_rating :: Player.rating(),
 | 
			
		||||
          opponent_rating_deviation :: Player.rating_deviation()
 | 
			
		||||
        ) :: float
 | 
			
		||||
  def draw_probability(player_rating, opponent_rating, opponent_rating_deviation) do
 | 
			
		||||
    1 -
 | 
			
		||||
      abs(win_probability(player_rating, opponent_rating, opponent_rating_deviation) - 0.5) / 0.5
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Generate a new rating from an existing rating and a series (or lack) of results.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Generate a new rating from an existing rating and a series (or lack) of results.
 | 
			
		||||
 | 
			
		||||
	Returns the updated player with the same version given to the function.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec new_rating(player :: Player.t, results :: list(Result.t), opts :: new_rating_opts) :: Player.t
 | 
			
		||||
	def new_rating(player, results, opts \\ [])
 | 
			
		||||
	def new_rating(player, results, opts) when tuple_size(player) == 3 do
 | 
			
		||||
		do_new_rating(player, results, opts)
 | 
			
		||||
	end
 | 
			
		||||
	def new_rating(player, results, opts) when tuple_size(player) == 2 do
 | 
			
		||||
		player
 | 
			
		||||
		|> Player.to_v2
 | 
			
		||||
		|> do_new_rating(results, opts)
 | 
			
		||||
		|> Player.to_v1
 | 
			
		||||
	end
 | 
			
		||||
  Returns the updated player with the same version given to the function.
 | 
			
		||||
  """
 | 
			
		||||
  @spec new_rating(player :: Player.t(), results :: list(Result.t()), opts :: new_rating_opts) ::
 | 
			
		||||
          Player.t()
 | 
			
		||||
  def new_rating(player, results, opts \\ [])
 | 
			
		||||
 | 
			
		||||
	defp do_new_rating({player_r, player_pre_rd, player_v}, [], _) do
 | 
			
		||||
		player_post_rd = calc_player_post_base_rd(:math.pow(player_pre_rd, 2), player_v)
 | 
			
		||||
  def new_rating(player, results, opts) when tuple_size(player) == 3 do
 | 
			
		||||
    do_new_rating(player, results, opts)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
		{player_r, player_post_rd, player_v}
 | 
			
		||||
	end
 | 
			
		||||
	defp do_new_rating({player_pre_r, player_pre_rd, player_pre_v}, results, opts) do
 | 
			
		||||
		sys_const = Keyword.get(opts, :system_constant, @default_system_constant)
 | 
			
		||||
		conv_tol = Keyword.get(opts, :convergence_tolerance, @default_convergence_tolerance)
 | 
			
		||||
  def new_rating(player, results, opts) when tuple_size(player) == 2 do
 | 
			
		||||
    player
 | 
			
		||||
    |> Player.to_v2()
 | 
			
		||||
    |> do_new_rating(results, opts)
 | 
			
		||||
    |> Player.to_v1()
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
		# Initialization (skips steps 1, 2 and 3)
 | 
			
		||||
		player_pre_rd_sq = :math.pow(player_pre_rd, 2)
 | 
			
		||||
		{variance_est, results_effect} = result_calculations(results, player_pre_r)
 | 
			
		||||
		# Step 4
 | 
			
		||||
		delta = calc_delta(results_effect, variance_est)
 | 
			
		||||
		# Step 5.1
 | 
			
		||||
		alpha = calc_alpha(player_pre_v)
 | 
			
		||||
		# Step 5.2
 | 
			
		||||
		k = calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, 1)
 | 
			
		||||
		{initial_a, initial_b} = iterative_algorithm_initial(
 | 
			
		||||
			alpha, delta, player_pre_rd_sq, variance_est, sys_const, k
 | 
			
		||||
		)
 | 
			
		||||
		# Step 5.3
 | 
			
		||||
		initial_fa = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, initial_a)
 | 
			
		||||
		initial_fb = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, initial_b)
 | 
			
		||||
		# Step 5.4
 | 
			
		||||
		a = iterative_algorithm_body(
 | 
			
		||||
			alpha, delta, player_pre_rd_sq, variance_est, sys_const, conv_tol,
 | 
			
		||||
			initial_a, initial_b, initial_fa, initial_fb
 | 
			
		||||
		)
 | 
			
		||||
		# Step 5.5
 | 
			
		||||
		player_post_v = calc_new_player_volatility(a)
 | 
			
		||||
		# Step 6
 | 
			
		||||
		player_post_base_rd = calc_player_post_base_rd(player_pre_rd_sq, player_post_v)
 | 
			
		||||
		# Step 7
 | 
			
		||||
		player_post_rd = calc_new_player_rating_deviation(player_post_base_rd, variance_est)
 | 
			
		||||
		player_post_r = calc_new_player_rating(results_effect, player_pre_r, player_post_rd)
 | 
			
		||||
  defp do_new_rating({player_r, player_pre_rd, player_v}, [], _) do
 | 
			
		||||
    player_post_rd = calc_player_post_base_rd(:math.pow(player_pre_rd, 2), player_v)
 | 
			
		||||
 | 
			
		||||
		{player_post_r, player_post_rd, player_post_v}
 | 
			
		||||
	end
 | 
			
		||||
    {player_r, player_post_rd, player_v}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp result_calculations(results, player_pre_r) do
 | 
			
		||||
		{variance_estimate_acc, result_effect_acc} =
 | 
			
		||||
			Enum.reduce(results, {0.0, 0.0}, fn result, {variance_estimate_acc, result_effect_acc} ->
 | 
			
		||||
				opponent_rd_g =
 | 
			
		||||
					result
 | 
			
		||||
					|> Result.opponent_rating_deviation
 | 
			
		||||
					|> calc_g
 | 
			
		||||
  defp do_new_rating({player_pre_r, player_pre_rd, player_pre_v}, results, opts) do
 | 
			
		||||
    sys_const = Keyword.get(opts, :system_constant, @default_system_constant)
 | 
			
		||||
    conv_tol = Keyword.get(opts, :convergence_tolerance, @default_convergence_tolerance)
 | 
			
		||||
 | 
			
		||||
				win_probability = calc_e(player_pre_r, Result.opponent_rating(result), opponent_rd_g)
 | 
			
		||||
    # Initialization (skips steps 1, 2 and 3)
 | 
			
		||||
    player_pre_rd_sq = :math.pow(player_pre_rd, 2)
 | 
			
		||||
    {variance_est, results_effect} = result_calculations(results, player_pre_r)
 | 
			
		||||
    # Step 4
 | 
			
		||||
    delta = calc_delta(results_effect, variance_est)
 | 
			
		||||
    # Step 5.1
 | 
			
		||||
    alpha = calc_alpha(player_pre_v)
 | 
			
		||||
    # Step 5.2
 | 
			
		||||
    k = calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, 1)
 | 
			
		||||
 | 
			
		||||
				{
 | 
			
		||||
					variance_estimate_acc + :math.pow(opponent_rd_g, 2) * win_probability * (1 - win_probability),
 | 
			
		||||
					result_effect_acc + opponent_rd_g * (Result.score(result) - win_probability)
 | 
			
		||||
				}
 | 
			
		||||
			end)
 | 
			
		||||
    {initial_a, initial_b} =
 | 
			
		||||
      iterative_algorithm_initial(
 | 
			
		||||
        alpha,
 | 
			
		||||
        delta,
 | 
			
		||||
        player_pre_rd_sq,
 | 
			
		||||
        variance_est,
 | 
			
		||||
        sys_const,
 | 
			
		||||
        k
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
		{:math.pow(variance_estimate_acc, -1), result_effect_acc}
 | 
			
		||||
	end
 | 
			
		||||
    # Step 5.3
 | 
			
		||||
    initial_fa = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, initial_a)
 | 
			
		||||
    initial_fb = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, initial_b)
 | 
			
		||||
    # Step 5.4
 | 
			
		||||
    a =
 | 
			
		||||
      iterative_algorithm_body(
 | 
			
		||||
        alpha,
 | 
			
		||||
        delta,
 | 
			
		||||
        player_pre_rd_sq,
 | 
			
		||||
        variance_est,
 | 
			
		||||
        sys_const,
 | 
			
		||||
        conv_tol,
 | 
			
		||||
        initial_a,
 | 
			
		||||
        initial_b,
 | 
			
		||||
        initial_fa,
 | 
			
		||||
        initial_fb
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
	defp calc_delta(results_effect, variance_est) do
 | 
			
		||||
		results_effect * variance_est
 | 
			
		||||
	end
 | 
			
		||||
    # Step 5.5
 | 
			
		||||
    player_post_v = calc_new_player_volatility(a)
 | 
			
		||||
    # Step 6
 | 
			
		||||
    player_post_base_rd = calc_player_post_base_rd(player_pre_rd_sq, player_post_v)
 | 
			
		||||
    # Step 7
 | 
			
		||||
    player_post_rd = calc_new_player_rating_deviation(player_post_base_rd, variance_est)
 | 
			
		||||
    player_post_r = calc_new_player_rating(results_effect, player_pre_r, player_post_rd)
 | 
			
		||||
 | 
			
		||||
	defp calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, x) do
 | 
			
		||||
		:math.exp(x) *
 | 
			
		||||
		(:math.pow(delta, 2) - :math.exp(x) - player_pre_rd_sq - variance_est) /
 | 
			
		||||
		(2 * :math.pow(player_pre_rd_sq + variance_est + :math.exp(x), 2)) -
 | 
			
		||||
		(x - alpha) / :math.pow(sys_const, 2)
 | 
			
		||||
	end
 | 
			
		||||
    {player_post_r, player_post_rd, player_post_v}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp calc_alpha(player_pre_v) do
 | 
			
		||||
		:math.log(:math.pow(player_pre_v, 2))
 | 
			
		||||
	end
 | 
			
		||||
  defp result_calculations(results, player_pre_r) do
 | 
			
		||||
    {variance_estimate_acc, result_effect_acc} =
 | 
			
		||||
      Enum.reduce(results, {0.0, 0.0}, fn result, {variance_estimate_acc, result_effect_acc} ->
 | 
			
		||||
        opponent_rd_g =
 | 
			
		||||
          result
 | 
			
		||||
          |> Result.opponent_rating_deviation()
 | 
			
		||||
          |> calc_g
 | 
			
		||||
 | 
			
		||||
	defp calc_new_player_volatility(a) do
 | 
			
		||||
		:math.exp(a / 2)
 | 
			
		||||
	end
 | 
			
		||||
        win_probability = calc_e(player_pre_r, Result.opponent_rating(result), opponent_rd_g)
 | 
			
		||||
 | 
			
		||||
	defp calc_new_player_rating(results_effect, player_pre_r, player_post_rd) do
 | 
			
		||||
		player_pre_r + :math.pow(player_post_rd, 2) * results_effect
 | 
			
		||||
	end
 | 
			
		||||
        {
 | 
			
		||||
          variance_estimate_acc +
 | 
			
		||||
            :math.pow(opponent_rd_g, 2) * win_probability * (1 - win_probability),
 | 
			
		||||
          result_effect_acc + opponent_rd_g * (Result.score(result) - win_probability)
 | 
			
		||||
        }
 | 
			
		||||
      end)
 | 
			
		||||
 | 
			
		||||
	defp calc_new_player_rating_deviation(player_post_base_rd, variance_est) do
 | 
			
		||||
		1 / :math.sqrt(1 / :math.pow(player_post_base_rd, 2) + 1 / variance_est)
 | 
			
		||||
	end
 | 
			
		||||
    {:math.pow(variance_estimate_acc, -1), result_effect_acc}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp calc_player_post_base_rd(player_pre_rd_sq, player_pre_v) do
 | 
			
		||||
		:math.sqrt((:math.pow(player_pre_v, 2) + player_pre_rd_sq))
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_delta(results_effect, variance_est) do
 | 
			
		||||
    results_effect * variance_est
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp iterative_algorithm_initial(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k) do
 | 
			
		||||
		initial_a = alpha
 | 
			
		||||
		initial_b =
 | 
			
		||||
			if :math.pow(delta, 2) > player_pre_rd_sq + variance_est do
 | 
			
		||||
				:math.log(:math.pow(delta, 2) - player_pre_rd_sq - variance_est)
 | 
			
		||||
			else
 | 
			
		||||
				alpha - k * sys_const
 | 
			
		||||
			end
 | 
			
		||||
  defp calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, x) do
 | 
			
		||||
    :math.exp(x) *
 | 
			
		||||
      (:math.pow(delta, 2) - :math.exp(x) - player_pre_rd_sq - variance_est) /
 | 
			
		||||
      (2 * :math.pow(player_pre_rd_sq + variance_est + :math.exp(x), 2)) -
 | 
			
		||||
      (x - alpha) / :math.pow(sys_const, 2)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
			{initial_a, initial_b}
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_alpha(player_pre_v) do
 | 
			
		||||
    :math.log(:math.pow(player_pre_v, 2))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp iterative_algorithm_body(alpha, delta, player_pre_rd_sq, variance_est, sys_const, conv_tol, a, b, fa, fb) do
 | 
			
		||||
		if abs(b - a) > conv_tol do
 | 
			
		||||
			c = a + (a - b) * fa / (fb - fa)
 | 
			
		||||
			fc = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, c)
 | 
			
		||||
			{a, fa} =
 | 
			
		||||
				if fc * fb < 0 do
 | 
			
		||||
					{b, fb}
 | 
			
		||||
				else
 | 
			
		||||
					{a, fa / 2}
 | 
			
		||||
				end
 | 
			
		||||
			iterative_algorithm_body(alpha, delta, player_pre_rd_sq, variance_est, sys_const, conv_tol, a, c, fa, fc)
 | 
			
		||||
		else
 | 
			
		||||
			a
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_new_player_volatility(a) do
 | 
			
		||||
    :math.exp(a / 2)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	defp calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k) do
 | 
			
		||||
		if calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, alpha - k * sys_const) < 0 do
 | 
			
		||||
			calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k + 1)
 | 
			
		||||
		else
 | 
			
		||||
			k
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_new_player_rating(results_effect, player_pre_r, player_post_rd) do
 | 
			
		||||
    player_pre_r + :math.pow(player_post_rd, 2) * results_effect
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	# g function
 | 
			
		||||
	defp calc_g(rd) do
 | 
			
		||||
		1 / :math.sqrt(1 + 3 * :math.pow(rd, 2) / :math.pow(:math.pi, 2))
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_new_player_rating_deviation(player_post_base_rd, variance_est) do
 | 
			
		||||
    1 / :math.sqrt(1 / :math.pow(player_post_base_rd, 2) + 1 / variance_est)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	# E function
 | 
			
		||||
	defp calc_e(player_pre_r, opponent_r, opponent_rd_g) do
 | 
			
		||||
		1 / (1 + :math.exp(-1 * opponent_rd_g * (player_pre_r - opponent_r)))
 | 
			
		||||
	end
 | 
			
		||||
  defp calc_player_post_base_rd(player_pre_rd_sq, player_pre_v) do
 | 
			
		||||
    :math.sqrt(:math.pow(player_pre_v, 2) + player_pre_rd_sq)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp iterative_algorithm_initial(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k) do
 | 
			
		||||
    initial_a = alpha
 | 
			
		||||
 | 
			
		||||
    initial_b =
 | 
			
		||||
      if :math.pow(delta, 2) > player_pre_rd_sq + variance_est do
 | 
			
		||||
        :math.log(:math.pow(delta, 2) - player_pre_rd_sq - variance_est)
 | 
			
		||||
      else
 | 
			
		||||
        alpha - k * sys_const
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    {initial_a, initial_b}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp iterative_algorithm_body(
 | 
			
		||||
         alpha,
 | 
			
		||||
         delta,
 | 
			
		||||
         player_pre_rd_sq,
 | 
			
		||||
         variance_est,
 | 
			
		||||
         sys_const,
 | 
			
		||||
         conv_tol,
 | 
			
		||||
         a,
 | 
			
		||||
         b,
 | 
			
		||||
         fa,
 | 
			
		||||
         fb
 | 
			
		||||
       ) do
 | 
			
		||||
    if abs(b - a) > conv_tol do
 | 
			
		||||
      c = a + (a - b) * fa / (fb - fa)
 | 
			
		||||
      fc = calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, c)
 | 
			
		||||
 | 
			
		||||
      {a, fa} =
 | 
			
		||||
        if fc * fb < 0 do
 | 
			
		||||
          {b, fb}
 | 
			
		||||
        else
 | 
			
		||||
          {a, fa / 2}
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
      iterative_algorithm_body(
 | 
			
		||||
        alpha,
 | 
			
		||||
        delta,
 | 
			
		||||
        player_pre_rd_sq,
 | 
			
		||||
        variance_est,
 | 
			
		||||
        sys_const,
 | 
			
		||||
        conv_tol,
 | 
			
		||||
        a,
 | 
			
		||||
        c,
 | 
			
		||||
        fa,
 | 
			
		||||
        fc
 | 
			
		||||
      )
 | 
			
		||||
    else
 | 
			
		||||
      a
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k) do
 | 
			
		||||
    if calc_f(alpha, delta, player_pre_rd_sq, variance_est, sys_const, alpha - k * sys_const) < 0 do
 | 
			
		||||
      calc_k(alpha, delta, player_pre_rd_sq, variance_est, sys_const, k + 1)
 | 
			
		||||
    else
 | 
			
		||||
      k
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # g function
 | 
			
		||||
  defp calc_g(rd) do
 | 
			
		||||
    1 / :math.sqrt(1 + 3 * :math.pow(rd, 2) / :math.pow(:math.pi(), 2))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # E function
 | 
			
		||||
  defp calc_e(player_pre_r, opponent_r, opponent_rd_g) do
 | 
			
		||||
    1 / (1 + :math.exp(-1 * opponent_rd_g * (player_pre_r - opponent_r)))
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,192 +1,214 @@
 | 
			
		||||
defmodule Glicko.Player do
 | 
			
		||||
	@moduledoc """
 | 
			
		||||
	Provides convenience functions that handle conversions between Glicko versions one and two.
 | 
			
		||||
  @moduledoc """
 | 
			
		||||
  Provides convenience functions that handle conversions between Glicko versions one and two.
 | 
			
		||||
 | 
			
		||||
	## Usage
 | 
			
		||||
  ## Usage
 | 
			
		||||
 | 
			
		||||
	Create a *v1* player with the default values for an unrated player.
 | 
			
		||||
  Create a *v1* player with the default values for an unrated player.
 | 
			
		||||
 | 
			
		||||
		iex> Player.new_v1
 | 
			
		||||
		{1.5e3, 350.0}
 | 
			
		||||
      iex> Player.new_v1
 | 
			
		||||
      {1.5e3, 350.0}
 | 
			
		||||
 | 
			
		||||
	Create a *v2* player with the default values for an unrated player.
 | 
			
		||||
  Create a *v2* player with the default values for an unrated player.
 | 
			
		||||
 | 
			
		||||
		iex> Player.new_v2
 | 
			
		||||
		{0.0, 2.014761872416068, 0.06}
 | 
			
		||||
      iex> Player.new_v2
 | 
			
		||||
      {0.0, 2.014761872416068, 0.06}
 | 
			
		||||
 | 
			
		||||
	Create a player with custom values.
 | 
			
		||||
  Create a player with custom values.
 | 
			
		||||
 | 
			
		||||
		iex> Player.new_v2([rating: 3.0, rating_deviation: 2.0, volatility: 0.05])
 | 
			
		||||
		{3.0, 2.0, 0.05}
 | 
			
		||||
      iex> Player.new_v2([rating: 3.0, rating_deviation: 2.0, volatility: 0.05])
 | 
			
		||||
      {3.0, 2.0, 0.05}
 | 
			
		||||
 | 
			
		||||
	Convert a *v2* player to a *v1*. Note this drops the volatility.
 | 
			
		||||
  Convert a *v2* player to a *v1*. Note this drops the volatility.
 | 
			
		||||
 | 
			
		||||
		iex> Player.new_v2 |> Player.to_v1
 | 
			
		||||
		{1.5e3, 350.0}
 | 
			
		||||
      iex> Player.new_v2 |> Player.to_v1
 | 
			
		||||
      {1.5e3, 350.0}
 | 
			
		||||
 | 
			
		||||
	Convert a *v1* player to a *v2*.
 | 
			
		||||
  Convert a *v1* player to a *v2*.
 | 
			
		||||
 | 
			
		||||
		iex> Player.new_v1 |> Player.to_v2(0.06)
 | 
			
		||||
		{0.0, 2.014761872416068, 0.06}
 | 
			
		||||
      iex> Player.new_v1 |> Player.to_v2(0.06)
 | 
			
		||||
      {0.0, 2.014761872416068, 0.06}
 | 
			
		||||
 | 
			
		||||
	Note calling `to_v1` with a *v1* player or likewise with `to_v2` and a *v2* player
 | 
			
		||||
	will pass-through unchanged. The volatility arg in this case is ignored.
 | 
			
		||||
  Note calling `to_v1` with a *v1* player or likewise with `to_v2` and a *v2* player
 | 
			
		||||
  will pass-through unchanged. The volatility arg in this case is ignored.
 | 
			
		||||
 | 
			
		||||
		iex> player_v2 = Player.new_v2
 | 
			
		||||
		iex> player_v2 == Player.to_v2(player_v2)
 | 
			
		||||
		true
 | 
			
		||||
      iex> player_v2 = Player.new_v2
 | 
			
		||||
      iex> player_v2 == Player.to_v2(player_v2)
 | 
			
		||||
      true
 | 
			
		||||
 | 
			
		||||
	"""
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
	@magic_version_scale 173.7178
 | 
			
		||||
	@magic_version_scale_rating 1500.0
 | 
			
		||||
  @magic_version_scale 173.7178
 | 
			
		||||
  @magic_version_scale_rating 1500.0
 | 
			
		||||
 | 
			
		||||
	@type t :: v1 | v2
 | 
			
		||||
  @type t :: v1 | v2
 | 
			
		||||
 | 
			
		||||
	@type v1 :: {rating, rating_deviation}
 | 
			
		||||
	@type v2 :: {rating, rating_deviation, volatility}
 | 
			
		||||
  @type v1 :: {rating, rating_deviation}
 | 
			
		||||
  @type v2 :: {rating, rating_deviation, volatility}
 | 
			
		||||
 | 
			
		||||
	@type version :: :v1 | :v2
 | 
			
		||||
	@type rating :: float
 | 
			
		||||
	@type rating_deviation :: float
 | 
			
		||||
	@type volatility :: float
 | 
			
		||||
  @type version :: :v1 | :v2
 | 
			
		||||
  @type rating :: float
 | 
			
		||||
  @type rating_deviation :: float
 | 
			
		||||
  @type volatility :: float
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	The recommended initial rating value for a new player.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec initial_rating(version) :: rating
 | 
			
		||||
	def initial_rating(_version = :v1), do: 1500.0
 | 
			
		||||
	def initial_rating(_version = :v2), do: :v1 |> initial_rating |> scale_rating_to(:v2)
 | 
			
		||||
  @doc """
 | 
			
		||||
  The recommended initial rating value for a new player.
 | 
			
		||||
  """
 | 
			
		||||
  @spec initial_rating(version) :: rating
 | 
			
		||||
  def initial_rating(_version = :v1), do: 1500.0
 | 
			
		||||
  def initial_rating(_version = :v2), do: :v1 |> initial_rating |> scale_rating_to(:v2)
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	The recommended initial rating deviation value for a new player.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec initial_rating_deviation(version) :: rating_deviation
 | 
			
		||||
	def initial_rating_deviation(_version = :v1), do: 350.0
 | 
			
		||||
	def initial_rating_deviation(_version = :v2), do: :v1 |> initial_rating_deviation |> scale_rating_deviation_to(:v2)
 | 
			
		||||
  @doc """
 | 
			
		||||
  The recommended initial rating deviation value for a new player.
 | 
			
		||||
  """
 | 
			
		||||
  @spec initial_rating_deviation(version) :: rating_deviation
 | 
			
		||||
  def initial_rating_deviation(_version = :v1), do: 350.0
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	The recommended initial volatility value for a new player.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec initial_volatility :: volatility
 | 
			
		||||
	def initial_volatility, do: 0.06
 | 
			
		||||
  def initial_rating_deviation(_version = :v2),
 | 
			
		||||
    do: :v1 |> initial_rating_deviation |> scale_rating_deviation_to(:v2)
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Creates a new v1 player.
 | 
			
		||||
  @doc """
 | 
			
		||||
  The recommended initial volatility value for a new player.
 | 
			
		||||
  """
 | 
			
		||||
  @spec initial_volatility :: volatility
 | 
			
		||||
  def initial_volatility, do: 0.06
 | 
			
		||||
 | 
			
		||||
	If not overriden, will use the default values for an unrated player.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec new_v1([rating: rating, rating_deviation: rating_deviation]) :: v1
 | 
			
		||||
	def new_v1(opts \\ []) when is_list(opts), do: {
 | 
			
		||||
		Keyword.get(opts, :rating, initial_rating(:v1)),
 | 
			
		||||
		Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v1)),
 | 
			
		||||
	}
 | 
			
		||||
  @doc """
 | 
			
		||||
  Creates a new v1 player.
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Creates a new v2 player.
 | 
			
		||||
  If not overriden, will use the default values for an unrated player.
 | 
			
		||||
  """
 | 
			
		||||
  @spec new_v1(rating: rating, rating_deviation: rating_deviation) :: v1
 | 
			
		||||
  def new_v1(opts \\ []) when is_list(opts),
 | 
			
		||||
    do: {
 | 
			
		||||
      Keyword.get(opts, :rating, initial_rating(:v1)),
 | 
			
		||||
      Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v1))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	If not overriden, will use default values for an unrated player.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec new_v2([rating: rating, rating_deviation: rating_deviation, volatility: volatility]) :: v2
 | 
			
		||||
	def new_v2(opts \\ []) when is_list(opts), do: {
 | 
			
		||||
		Keyword.get(opts, :rating, initial_rating(:v2)),
 | 
			
		||||
		Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v2)),
 | 
			
		||||
		Keyword.get(opts, :volatility, initial_volatility()),
 | 
			
		||||
	}
 | 
			
		||||
  @doc """
 | 
			
		||||
  Creates a new v2 player.
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Converts a v2 player to a v1.
 | 
			
		||||
  If not overriden, will use default values for an unrated player.
 | 
			
		||||
  """
 | 
			
		||||
  @spec new_v2(rating: rating, rating_deviation: rating_deviation, volatility: volatility) :: v2
 | 
			
		||||
  def new_v2(opts \\ []) when is_list(opts),
 | 
			
		||||
    do: {
 | 
			
		||||
      Keyword.get(opts, :rating, initial_rating(:v2)),
 | 
			
		||||
      Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v2)),
 | 
			
		||||
      Keyword.get(opts, :volatility, initial_volatility())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	A v1 player will pass-through unchanged.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Converts a v2 player to a v1.
 | 
			
		||||
 | 
			
		||||
	Note the volatility field used in a v2 player will be lost in the conversion.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec to_v1(player :: t) :: v1
 | 
			
		||||
	def to_v1({rating, rating_deviation}), do: {rating, rating_deviation}
 | 
			
		||||
	def to_v1({rating, rating_deviation, _}), do: {
 | 
			
		||||
		rating |> scale_rating_to(:v1),
 | 
			
		||||
		rating_deviation |> scale_rating_deviation_to(:v1),
 | 
			
		||||
	}
 | 
			
		||||
  A v1 player will pass-through unchanged.
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Converts a v1 player to a v2.
 | 
			
		||||
  Note the volatility field used in a v2 player will be lost in the conversion.
 | 
			
		||||
  """
 | 
			
		||||
  @spec to_v1(player :: t) :: v1
 | 
			
		||||
  def to_v1({rating, rating_deviation}), do: {rating, rating_deviation}
 | 
			
		||||
 | 
			
		||||
	A v2 player will pass-through unchanged with the volatility arg ignored.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec to_v2(player :: t, volatility :: volatility) :: v2
 | 
			
		||||
	def to_v2(player, volatility \\ initial_volatility())
 | 
			
		||||
	def to_v2({rating, rating_deviation, volatility}, _volatility), do: {rating, rating_deviation, volatility}
 | 
			
		||||
	def to_v2({rating, rating_deviation}, volatility), do: {
 | 
			
		||||
		rating |> scale_rating_to(:v2),
 | 
			
		||||
		rating_deviation |> scale_rating_deviation_to(:v2),
 | 
			
		||||
		volatility,
 | 
			
		||||
	}
 | 
			
		||||
  def to_v1({rating, rating_deviation, _}),
 | 
			
		||||
    do: {
 | 
			
		||||
      rating |> scale_rating_to(:v1),
 | 
			
		||||
      rating_deviation |> scale_rating_deviation_to(:v1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	A version agnostic method for getting a player's rating.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec rating(player :: t, as_version :: version) :: rating
 | 
			
		||||
	def rating(player, as_version \\ nil)
 | 
			
		||||
	def rating({rating, _}, nil), do: rating
 | 
			
		||||
	def rating({rating, _, _}, nil), do: rating
 | 
			
		||||
	def rating({rating, _}, :v1), do: rating
 | 
			
		||||
	def rating({rating, _}, :v2), do: rating |> scale_rating_to(:v2)
 | 
			
		||||
	def rating({rating, _, _}, :v1), do: rating |> scale_rating_to(:v1)
 | 
			
		||||
	def rating({rating, _, _}, :v2), do: rating
 | 
			
		||||
  @doc """
 | 
			
		||||
  Converts a v1 player to a v2.
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	A version agnostic method for getting a player's rating deviation.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec rating_deviation(player :: t, as_version :: version) :: rating_deviation
 | 
			
		||||
	def rating_deviation(player, as_version \\ nil)
 | 
			
		||||
	def rating_deviation({_, rating_deviation}, nil), do: rating_deviation
 | 
			
		||||
	def rating_deviation({_, rating_deviation, _}, nil), do: rating_deviation
 | 
			
		||||
	def rating_deviation({_, rating_deviation}, :v1), do: rating_deviation
 | 
			
		||||
	def rating_deviation({_, rating_deviation}, :v2), do: rating_deviation |> scale_rating_deviation_to(:v2)
 | 
			
		||||
	def rating_deviation({_, rating_deviation, _}, :v1), do: rating_deviation |> scale_rating_deviation_to(:v1)
 | 
			
		||||
	def rating_deviation({_, rating_deviation, _}, :v2), do: rating_deviation
 | 
			
		||||
  A v2 player will pass-through unchanged with the volatility arg ignored.
 | 
			
		||||
  """
 | 
			
		||||
  @spec to_v2(player :: t, volatility :: volatility) :: v2
 | 
			
		||||
  def to_v2(player, volatility \\ initial_volatility())
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	A version agnostic method for getting a player's volatility.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec volatility(player :: t, default_volatility :: volatility) :: volatility
 | 
			
		||||
	def volatility(player, default_volatility \\ initial_volatility())
 | 
			
		||||
	def volatility({_, _}, default_volatility), do: default_volatility
 | 
			
		||||
	def volatility({_, _, volatility}, _), do: volatility
 | 
			
		||||
  def to_v2({rating, rating_deviation, volatility}, _volatility),
 | 
			
		||||
    do: {rating, rating_deviation, volatility}
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	A convenience function for summarizing a player's strength as a 95%
 | 
			
		||||
	confidence interval.
 | 
			
		||||
  def to_v2({rating, rating_deviation}, volatility),
 | 
			
		||||
    do: {
 | 
			
		||||
      rating |> scale_rating_to(:v2),
 | 
			
		||||
      rating_deviation |> scale_rating_deviation_to(:v2),
 | 
			
		||||
      volatility
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	The lowest value in the interval is the player's rating minus twice the RD,
 | 
			
		||||
	and the highest value is the player's rating plus twice the RD.
 | 
			
		||||
	The volatility measure does not appear in the calculation of this interval.
 | 
			
		||||
  @doc """
 | 
			
		||||
  A version agnostic method for getting a player's rating.
 | 
			
		||||
  """
 | 
			
		||||
  @spec rating(player :: t, as_version :: version) :: rating
 | 
			
		||||
  def rating(player, as_version \\ nil)
 | 
			
		||||
  def rating({rating, _}, nil), do: rating
 | 
			
		||||
  def rating({rating, _, _}, nil), do: rating
 | 
			
		||||
  def rating({rating, _}, :v1), do: rating
 | 
			
		||||
  def rating({rating, _}, :v2), do: rating |> scale_rating_to(:v2)
 | 
			
		||||
  def rating({rating, _, _}, :v1), do: rating |> scale_rating_to(:v1)
 | 
			
		||||
  def rating({rating, _, _}, :v2), do: rating
 | 
			
		||||
 | 
			
		||||
	An example would be if a player's rating is 1850 and the RD is 50,
 | 
			
		||||
	the interval would range from 1750 to 1950. We would then say that we're 95%
 | 
			
		||||
	confident that the player's actual strength is between 1750 and 1950.
 | 
			
		||||
  @doc """
 | 
			
		||||
  A version agnostic method for getting a player's rating deviation.
 | 
			
		||||
  """
 | 
			
		||||
  @spec rating_deviation(player :: t, as_version :: version) :: rating_deviation
 | 
			
		||||
  def rating_deviation(player, as_version \\ nil)
 | 
			
		||||
  def rating_deviation({_, rating_deviation}, nil), do: rating_deviation
 | 
			
		||||
  def rating_deviation({_, rating_deviation, _}, nil), do: rating_deviation
 | 
			
		||||
  def rating_deviation({_, rating_deviation}, :v1), do: rating_deviation
 | 
			
		||||
 | 
			
		||||
	When a player has a low RD, the interval would be narrow, so that we would
 | 
			
		||||
	be 95% confident about a player’s strength being in a small interval of values.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec rating_interval(player :: t) :: {rating_low :: float, rating_high :: float}
 | 
			
		||||
	def rating_interval(player, as_version \\ nil), do: {
 | 
			
		||||
		rating(player, as_version) - rating_deviation(player, as_version) * 2,
 | 
			
		||||
		rating(player, as_version) + rating_deviation(player, as_version) * 2,
 | 
			
		||||
	}
 | 
			
		||||
  def rating_deviation({_, rating_deviation}, :v2),
 | 
			
		||||
    do: rating_deviation |> scale_rating_deviation_to(:v2)
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Scales a player's rating.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec scale_rating_to(rating :: rating, to_version :: version) :: rating
 | 
			
		||||
	def scale_rating_to(rating, _version = :v1), do: (rating * @magic_version_scale) + @magic_version_scale_rating
 | 
			
		||||
	def scale_rating_to(rating, _version = :v2), do: (rating - @magic_version_scale_rating) / @magic_version_scale
 | 
			
		||||
  def rating_deviation({_, rating_deviation, _}, :v1),
 | 
			
		||||
    do: rating_deviation |> scale_rating_deviation_to(:v1)
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Scales a player's rating deviation.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec scale_rating_deviation_to(rating_deviation :: rating_deviation, to_version :: version) :: rating_deviation
 | 
			
		||||
	def scale_rating_deviation_to(rating_deviation, _version = :v1), do: rating_deviation * @magic_version_scale
 | 
			
		||||
	def scale_rating_deviation_to(rating_deviation, _version = :v2), do: rating_deviation / @magic_version_scale
 | 
			
		||||
  def rating_deviation({_, rating_deviation, _}, :v2), do: rating_deviation
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  A version agnostic method for getting a player's volatility.
 | 
			
		||||
  """
 | 
			
		||||
  @spec volatility(player :: t, default_volatility :: volatility) :: volatility
 | 
			
		||||
  def volatility(player, default_volatility \\ initial_volatility())
 | 
			
		||||
  def volatility({_, _}, default_volatility), do: default_volatility
 | 
			
		||||
  def volatility({_, _, volatility}, _), do: volatility
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  A convenience function for summarizing a player's strength as a 95%
 | 
			
		||||
  confidence interval.
 | 
			
		||||
 | 
			
		||||
  The lowest value in the interval is the player's rating minus twice the RD,
 | 
			
		||||
  and the highest value is the player's rating plus twice the RD.
 | 
			
		||||
  The volatility measure does not appear in the calculation of this interval.
 | 
			
		||||
 | 
			
		||||
  An example would be if a player's rating is 1850 and the RD is 50,
 | 
			
		||||
  the interval would range from 1750 to 1950. We would then say that we're 95%
 | 
			
		||||
  confident that the player's actual strength is between 1750 and 1950.
 | 
			
		||||
 | 
			
		||||
  When a player has a low RD, the interval would be narrow, so that we would
 | 
			
		||||
  be 95% confident about a player’s strength being in a small interval of values.
 | 
			
		||||
  """
 | 
			
		||||
  @spec rating_interval(player :: t) :: {rating_low :: float, rating_high :: float}
 | 
			
		||||
  def rating_interval(player, as_version \\ nil),
 | 
			
		||||
    do: {
 | 
			
		||||
      rating(player, as_version) - rating_deviation(player, as_version) * 2,
 | 
			
		||||
      rating(player, as_version) + rating_deviation(player, as_version) * 2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  Scales a player's rating.
 | 
			
		||||
  """
 | 
			
		||||
  @spec scale_rating_to(rating :: rating, to_version :: version) :: rating
 | 
			
		||||
  def scale_rating_to(rating, _version = :v1),
 | 
			
		||||
    do: rating * @magic_version_scale + @magic_version_scale_rating
 | 
			
		||||
 | 
			
		||||
  def scale_rating_to(rating, _version = :v2),
 | 
			
		||||
    do: (rating - @magic_version_scale_rating) / @magic_version_scale
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  Scales a player's rating deviation.
 | 
			
		||||
  """
 | 
			
		||||
  @spec scale_rating_deviation_to(rating_deviation :: rating_deviation, to_version :: version) ::
 | 
			
		||||
          rating_deviation
 | 
			
		||||
  def scale_rating_deviation_to(rating_deviation, _version = :v1),
 | 
			
		||||
    do: rating_deviation * @magic_version_scale
 | 
			
		||||
 | 
			
		||||
  def scale_rating_deviation_to(rating_deviation, _version = :v2),
 | 
			
		||||
    do: rating_deviation / @magic_version_scale
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,68 +1,69 @@
 | 
			
		||||
defmodule Glicko.Result do
 | 
			
		||||
	@moduledoc """
 | 
			
		||||
	Provides convenience functions for handling a result against an opponent.
 | 
			
		||||
  @moduledoc """
 | 
			
		||||
  Provides convenience functions for handling a result against an opponent.
 | 
			
		||||
 | 
			
		||||
	## Usage
 | 
			
		||||
  ## Usage
 | 
			
		||||
 | 
			
		||||
		iex> opponent = Player.new_v2
 | 
			
		||||
		iex> Result.new(opponent, 1.0)
 | 
			
		||||
		{0.0, 2.014761872416068, 1.0}
 | 
			
		||||
		iex> Result.new(opponent, :draw) # With shortcut
 | 
			
		||||
		{0.0, 2.014761872416068, 0.5}
 | 
			
		||||
      iex> opponent = Player.new_v2
 | 
			
		||||
      iex> Result.new(opponent, 1.0)
 | 
			
		||||
      {0.0, 2.014761872416068, 1.0}
 | 
			
		||||
      iex> Result.new(opponent, :draw) # With shortcut
 | 
			
		||||
      {0.0, 2.014761872416068, 0.5}
 | 
			
		||||
 | 
			
		||||
	"""
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
	alias Glicko.Player
 | 
			
		||||
  alias Glicko.Player
 | 
			
		||||
 | 
			
		||||
	@type t :: {Player.rating, Player.rating_deviation, score}
 | 
			
		||||
  @type t :: {Player.rating(), Player.rating_deviation(), score}
 | 
			
		||||
 | 
			
		||||
	@type score :: float
 | 
			
		||||
	@type score_shortcut :: :loss | :draw | :win
 | 
			
		||||
  @type score :: float
 | 
			
		||||
  @type score_shortcut :: :loss | :draw | :win
 | 
			
		||||
 | 
			
		||||
	@score_shortcut_map %{loss: 0.0, draw: 0.5, win: 1.0}
 | 
			
		||||
	@score_shortcuts Map.keys(@score_shortcut_map)
 | 
			
		||||
  @score_shortcut_map %{loss: 0.0, draw: 0.5, win: 1.0}
 | 
			
		||||
  @score_shortcuts Map.keys(@score_shortcut_map)
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Creates a new result from an opponent rating, opponent rating deviation and score.
 | 
			
		||||
  @doc """
 | 
			
		||||
  Creates a new result from an opponent rating, opponent rating deviation and score.
 | 
			
		||||
 | 
			
		||||
	Values provided for the opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
  Values provided for the opponent rating and opponent rating deviation must be *v2* based.
 | 
			
		||||
 | 
			
		||||
	Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec new(Player.rating, Player.rating_deviation, score | score_shortcut) :: t
 | 
			
		||||
	def new(opponent_rating, opponent_rating_deviation, score) when is_number(score) do
 | 
			
		||||
		{opponent_rating, opponent_rating_deviation, score}
 | 
			
		||||
	end
 | 
			
		||||
	def new(opponent_rating, opponent_rating_deviation, score_type) when is_atom(score_type) and score_type in @score_shortcuts do
 | 
			
		||||
		{opponent_rating, opponent_rating_deviation, Map.fetch!(@score_shortcut_map, score_type)}
 | 
			
		||||
	end
 | 
			
		||||
  Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
			
		||||
  """
 | 
			
		||||
  @spec new(Player.rating(), Player.rating_deviation(), score | score_shortcut) :: t
 | 
			
		||||
  def new(opponent_rating, opponent_rating_deviation, score) when is_number(score) do
 | 
			
		||||
    {opponent_rating, opponent_rating_deviation, score}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Creates a new result from an opponent and score.
 | 
			
		||||
  def new(opponent_rating, opponent_rating_deviation, score_type)
 | 
			
		||||
      when is_atom(score_type) and score_type in @score_shortcuts do
 | 
			
		||||
    {opponent_rating, opponent_rating_deviation, Map.fetch!(@score_shortcut_map, score_type)}
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec new(opponent :: Player.t, score :: score | score_shortcut) :: t
 | 
			
		||||
	def new(opponent, score) do
 | 
			
		||||
		new(Player.rating(opponent, :v2), Player.rating_deviation(opponent, :v2), score)
 | 
			
		||||
	end
 | 
			
		||||
  @doc """
 | 
			
		||||
  Creates a new result from an opponent and score.
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Convenience function for accessing an opponent's rating.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec opponent_rating(result :: Result.t) :: Player.rating
 | 
			
		||||
	def opponent_rating(_result = {rating, _, _}), do: rating
 | 
			
		||||
  Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
			
		||||
  """
 | 
			
		||||
  @spec new(opponent :: Player.t(), score :: score | score_shortcut) :: t
 | 
			
		||||
  def new(opponent, score) do
 | 
			
		||||
    new(Player.rating(opponent, :v2), Player.rating_deviation(opponent, :v2), score)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Convenience function for accessing an opponent's rating deviation.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec opponent_rating_deviation(result :: Result.t) :: Player.rating_deviation
 | 
			
		||||
	def opponent_rating_deviation(_result = {_, rating_deviation, _}), do: rating_deviation
 | 
			
		||||
  @doc """
 | 
			
		||||
  Convenience function for accessing an opponent's rating.
 | 
			
		||||
  """
 | 
			
		||||
  @spec opponent_rating(result :: Result.t()) :: Player.rating()
 | 
			
		||||
  def opponent_rating(_result = {rating, _, _}), do: rating
 | 
			
		||||
 | 
			
		||||
	@doc """
 | 
			
		||||
	Convenience function for accessing the score.
 | 
			
		||||
	"""
 | 
			
		||||
	@spec score(result :: Result.t) :: score
 | 
			
		||||
	def score(_result = {_, _, score}), do: score
 | 
			
		||||
  @doc """
 | 
			
		||||
  Convenience function for accessing an opponent's rating deviation.
 | 
			
		||||
  """
 | 
			
		||||
  @spec opponent_rating_deviation(result :: Result.t()) :: Player.rating_deviation()
 | 
			
		||||
  def opponent_rating_deviation(_result = {_, rating_deviation, _}), do: rating_deviation
 | 
			
		||||
 | 
			
		||||
  @doc """
 | 
			
		||||
  Convenience function for accessing the score.
 | 
			
		||||
  """
 | 
			
		||||
  @spec score(result :: Result.t()) :: score
 | 
			
		||||
  def score(_result = {_, _, score}), do: score
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								mix.exs
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								mix.exs
									
									
									
									
									
								
							@ -1,30 +1,33 @@
 | 
			
		||||
defmodule Glicko.Mixfile do
 | 
			
		||||
	use Mix.Project
 | 
			
		||||
  use Mix.Project
 | 
			
		||||
 | 
			
		||||
	@description """
 | 
			
		||||
	Implementation of the Glicko rating system
 | 
			
		||||
	"""
 | 
			
		||||
  @description """
 | 
			
		||||
  Implementation of the Glicko rating system
 | 
			
		||||
  """
 | 
			
		||||
 | 
			
		||||
	def project, do: [
 | 
			
		||||
		app: :glicko,
 | 
			
		||||
		version: "0.6.0",
 | 
			
		||||
		elixir: "~> 1.5",
 | 
			
		||||
		start_permanent: Mix.env == :prod,
 | 
			
		||||
		deps: deps(),
 | 
			
		||||
		package: package(),
 | 
			
		||||
		description: @description,
 | 
			
		||||
	]
 | 
			
		||||
  def project,
 | 
			
		||||
    do: [
 | 
			
		||||
      app: :glicko,
 | 
			
		||||
      version: "0.6.0",
 | 
			
		||||
      elixir: "~> 1.5",
 | 
			
		||||
      start_permanent: Mix.env() == :prod,
 | 
			
		||||
      deps: deps(),
 | 
			
		||||
      package: package(),
 | 
			
		||||
      description: @description
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
	defp deps, do: [
 | 
			
		||||
		{:inch_ex, "~> 0.5", only: :docs},
 | 
			
		||||
		{:ex_doc, "~> 0.16", only: :dev, runtime: false},
 | 
			
		||||
		{:credo, "~> 0.8", only: [:dev, :test], runtime: false},
 | 
			
		||||
	]
 | 
			
		||||
  defp deps,
 | 
			
		||||
    do: [
 | 
			
		||||
      {:inch_ex, "~> 0.5", only: :docs},
 | 
			
		||||
      {:ex_doc, "~> 0.16", only: :dev, runtime: false},
 | 
			
		||||
      {:credo, "~> 0.8", only: [:dev, :test], runtime: false}
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
	defp package, do: [
 | 
			
		||||
		name: :glicko,
 | 
			
		||||
		maintainers: ["James Dyson"],
 | 
			
		||||
		licenses: ["MIT"],
 | 
			
		||||
		links: %{"GitHub" => "https://github.com/avitex/elixir-glicko"},
 | 
			
		||||
	]
 | 
			
		||||
  defp package,
 | 
			
		||||
    do: [
 | 
			
		||||
      name: :glicko,
 | 
			
		||||
      maintainers: ["James Dyson"],
 | 
			
		||||
      licenses: ["MIT"],
 | 
			
		||||
      links: %{"GitHub" => "https://github.com/avitex/elixir-glicko"}
 | 
			
		||||
    ]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,68 +1,77 @@
 | 
			
		||||
defmodule GlickoTest do
 | 
			
		||||
	use ExUnit.Case
 | 
			
		||||
  use ExUnit.Case
 | 
			
		||||
 | 
			
		||||
	alias Glicko.{
 | 
			
		||||
		Player,
 | 
			
		||||
		Result,
 | 
			
		||||
	}
 | 
			
		||||
  alias Glicko.{
 | 
			
		||||
    Player,
 | 
			
		||||
    Result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	doctest Glicko
 | 
			
		||||
  doctest Glicko
 | 
			
		||||
 | 
			
		||||
	@player [rating: 1500, rating_deviation: 200] |> Player.new_v1 |> Player.to_v2
 | 
			
		||||
  @player [rating: 1500, rating_deviation: 200] |> Player.new_v1() |> Player.to_v2()
 | 
			
		||||
 | 
			
		||||
	@results [
 | 
			
		||||
		Result.new(Player.new_v1([rating: 1400, rating_deviation: 30]), :win),
 | 
			
		||||
		Result.new(Player.new_v1([rating: 1550, rating_deviation: 100]), :loss),
 | 
			
		||||
		Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss),
 | 
			
		||||
	]
 | 
			
		||||
  @results [
 | 
			
		||||
    Result.new(Player.new_v1(rating: 1400, rating_deviation: 30), :win),
 | 
			
		||||
    Result.new(Player.new_v1(rating: 1550, rating_deviation: 100), :loss),
 | 
			
		||||
    Result.new(Player.new_v1(rating: 1700, rating_deviation: 300), :loss)
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
	@valid_player_rating_after_results 1464.06 |> Player.scale_rating_to(:v2)
 | 
			
		||||
	@valid_player_rating_deviation_after_results 151.52 |> Player.scale_rating_deviation_to(:v2)
 | 
			
		||||
	@valid_player_volatility_after_results 0.05999
 | 
			
		||||
  @valid_player_rating_after_results 1464.06 |> Player.scale_rating_to(:v2)
 | 
			
		||||
  @valid_player_rating_deviation_after_results 151.52 |> Player.scale_rating_deviation_to(:v2)
 | 
			
		||||
  @valid_player_volatility_after_results 0.05999
 | 
			
		||||
 | 
			
		||||
	@valid_player_rating_deviation_after_no_results 200.2714 |> Player.scale_rating_deviation_to(:v2)
 | 
			
		||||
  @valid_player_rating_deviation_after_no_results 200.2714
 | 
			
		||||
                                                  |> Player.scale_rating_deviation_to(:v2)
 | 
			
		||||
 | 
			
		||||
	describe "new rating" do
 | 
			
		||||
		test "with results" do
 | 
			
		||||
			player = Glicko.new_rating(@player, @results, [system_constant: 0.5])
 | 
			
		||||
	
 | 
			
		||||
			assert_in_delta Player.rating(player), @valid_player_rating_after_results, 1.0e-4
 | 
			
		||||
			assert_in_delta Player.rating_deviation(player), @valid_player_rating_deviation_after_results, 1.0e-4
 | 
			
		||||
			assert_in_delta Player.volatility(player), @valid_player_volatility_after_results, 1.0e-5
 | 
			
		||||
		end
 | 
			
		||||
	
 | 
			
		||||
		test "no results" do
 | 
			
		||||
			player = Glicko.new_rating(@player, [])
 | 
			
		||||
	
 | 
			
		||||
			assert_in_delta Player.rating_deviation(player), @valid_player_rating_deviation_after_no_results, 1.0e-4
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
  describe "new rating" do
 | 
			
		||||
    test "with results" do
 | 
			
		||||
      player = Glicko.new_rating(@player, @results, system_constant: 0.5)
 | 
			
		||||
 | 
			
		||||
	describe "win probability" do
 | 
			
		||||
		test "with same ratings" do
 | 
			
		||||
			assert Glicko.win_probability(Player.new_v1, Player.new_v1) == 0.5
 | 
			
		||||
		end
 | 
			
		||||
	
 | 
			
		||||
		test "with better opponent" do
 | 
			
		||||
			assert Glicko.win_probability(Player.new_v1([rating: 1500]), Player.new_v1([rating: 1600])) < 0.5
 | 
			
		||||
		end
 | 
			
		||||
	
 | 
			
		||||
		test "with better player" do
 | 
			
		||||
			assert Glicko.win_probability(Player.new_v1([rating: 1600]), Player.new_v1([rating: 1500])) > 0.5
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
	describe "draw probability" do
 | 
			
		||||
		test "with same ratings" do
 | 
			
		||||
			assert Glicko.draw_probability(Player.new_v1, Player.new_v1) == 1
 | 
			
		||||
		end
 | 
			
		||||
	
 | 
			
		||||
		test "with better opponent" do
 | 
			
		||||
			assert Glicko.draw_probability(Player.new_v1([rating: 1500]), Player.new_v1([rating: 1600])) < 1
 | 
			
		||||
		end
 | 
			
		||||
	
 | 
			
		||||
		test "with better player" do
 | 
			
		||||
			assert Glicko.draw_probability(Player.new_v1([rating: 1600]), Player.new_v1([rating: 1500])) < 1
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
      assert_in_delta Player.rating(player), @valid_player_rating_after_results, 1.0e-4
 | 
			
		||||
 | 
			
		||||
      assert_in_delta Player.rating_deviation(player),
 | 
			
		||||
                      @valid_player_rating_deviation_after_results,
 | 
			
		||||
                      1.0e-4
 | 
			
		||||
 | 
			
		||||
      assert_in_delta Player.volatility(player), @valid_player_volatility_after_results, 1.0e-5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "no results" do
 | 
			
		||||
      player = Glicko.new_rating(@player, [])
 | 
			
		||||
 | 
			
		||||
      assert_in_delta Player.rating_deviation(player),
 | 
			
		||||
                      @valid_player_rating_deviation_after_no_results,
 | 
			
		||||
                      1.0e-4
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "win probability" do
 | 
			
		||||
    test "with same ratings" do
 | 
			
		||||
      assert Glicko.win_probability(Player.new_v1(), Player.new_v1()) == 0.5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with better opponent" do
 | 
			
		||||
      assert Glicko.win_probability(Player.new_v1(rating: 1500), Player.new_v1(rating: 1600)) <
 | 
			
		||||
               0.5
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with better player" do
 | 
			
		||||
      assert Glicko.win_probability(Player.new_v1(rating: 1600), Player.new_v1(rating: 1500)) >
 | 
			
		||||
               0.5
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "draw probability" do
 | 
			
		||||
    test "with same ratings" do
 | 
			
		||||
      assert Glicko.draw_probability(Player.new_v1(), Player.new_v1()) == 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with better opponent" do
 | 
			
		||||
      assert Glicko.draw_probability(Player.new_v1(rating: 1500), Player.new_v1(rating: 1600)) < 1
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    test "with better player" do
 | 
			
		||||
      assert Glicko.draw_probability(Player.new_v1(rating: 1600), Player.new_v1(rating: 1500)) < 1
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,60 +1,62 @@
 | 
			
		||||
defmodule Glicko.PlayerTest do
 | 
			
		||||
	use ExUnit.Case
 | 
			
		||||
  use ExUnit.Case
 | 
			
		||||
 | 
			
		||||
	alias Glicko.Player
 | 
			
		||||
  alias Glicko.Player
 | 
			
		||||
 | 
			
		||||
	doctest Player
 | 
			
		||||
  doctest Player
 | 
			
		||||
 | 
			
		||||
	@valid_v1_base {1.0, 2.0}
 | 
			
		||||
	@valid_v2_base {1.0, 2.0, 3.0}
 | 
			
		||||
  @valid_v1_base {1.0, 2.0}
 | 
			
		||||
  @valid_v2_base {1.0, 2.0, 3.0}
 | 
			
		||||
 | 
			
		||||
	test "create v1" do
 | 
			
		||||
		assert @valid_v1_base == Player.new_v1([rating: 1.0, rating_deviation: 2.0])
 | 
			
		||||
	end
 | 
			
		||||
  test "create v1" do
 | 
			
		||||
    assert @valid_v1_base == Player.new_v1(rating: 1.0, rating_deviation: 2.0)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "create v2" do
 | 
			
		||||
		assert @valid_v2_base == Player.new_v2([rating: 1.0, rating_deviation: 2.0, volatility: 3.0])
 | 
			
		||||
	end
 | 
			
		||||
  test "create v2" do
 | 
			
		||||
    assert @valid_v2_base == Player.new_v2(rating: 1.0, rating_deviation: 2.0, volatility: 3.0)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "convert player v1 -> v2" do
 | 
			
		||||
		assert {Player.scale_rating_to(1.0, :v2), Player.scale_rating_deviation_to(2.0, :v2), 3.0} == Player.to_v2(@valid_v1_base, 3.0)
 | 
			
		||||
	end
 | 
			
		||||
  test "convert player v1 -> v2" do
 | 
			
		||||
    assert {Player.scale_rating_to(1.0, :v2), Player.scale_rating_deviation_to(2.0, :v2), 3.0} ==
 | 
			
		||||
             Player.to_v2(@valid_v1_base, 3.0)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "convert player v2 -> v1" do
 | 
			
		||||
		assert {Player.scale_rating_to(1.0, :v1), Player.scale_rating_deviation_to(2.0, :v1)} == Player.to_v1(@valid_v2_base)
 | 
			
		||||
	end
 | 
			
		||||
  test "convert player v2 -> v1" do
 | 
			
		||||
    assert {Player.scale_rating_to(1.0, :v1), Player.scale_rating_deviation_to(2.0, :v1)} ==
 | 
			
		||||
             Player.to_v1(@valid_v2_base)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "convert player v1 -> v1" do
 | 
			
		||||
		assert @valid_v1_base == Player.to_v1(@valid_v1_base)
 | 
			
		||||
	end
 | 
			
		||||
  test "convert player v1 -> v1" do
 | 
			
		||||
    assert @valid_v1_base == Player.to_v1(@valid_v1_base)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "convert player v2 -> v2" do
 | 
			
		||||
		assert @valid_v2_base == Player.to_v2(@valid_v2_base)
 | 
			
		||||
	end
 | 
			
		||||
  test "convert player v2 -> v2" do
 | 
			
		||||
    assert @valid_v2_base == Player.to_v2(@valid_v2_base)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "scale rating v1 -> v2" do
 | 
			
		||||
		assert_in_delta Player.scale_rating_to(1673.7178, :v2), 1.0, 0.1
 | 
			
		||||
	end
 | 
			
		||||
  test "scale rating v1 -> v2" do
 | 
			
		||||
    assert_in_delta Player.scale_rating_to(1673.7178, :v2), 1.0, 0.1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "scale rating v2 -> v1" do
 | 
			
		||||
		assert_in_delta Player.scale_rating_to(1.0, :v1), 1673.7178, 0.1
 | 
			
		||||
	end
 | 
			
		||||
  test "scale rating v2 -> v1" do
 | 
			
		||||
    assert_in_delta Player.scale_rating_to(1.0, :v1), 1673.7178, 0.1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "scale rating deviation v1 -> v2" do
 | 
			
		||||
		assert_in_delta Player.scale_rating_deviation_to(173.7178, :v2), 1.0, 0.1
 | 
			
		||||
	end
 | 
			
		||||
  test "scale rating deviation v1 -> v2" do
 | 
			
		||||
    assert_in_delta Player.scale_rating_deviation_to(173.7178, :v2), 1.0, 0.1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "scale rating deviation v2 -> v1" do
 | 
			
		||||
		assert_in_delta Player.scale_rating_deviation_to(1.0, :v1), 173.7178, 0.1
 | 
			
		||||
	end
 | 
			
		||||
  test "scale rating deviation v2 -> v1" do
 | 
			
		||||
    assert_in_delta Player.scale_rating_deviation_to(1.0, :v1), 173.7178, 0.1
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "rating interval" do
 | 
			
		||||
		assert {rating_low, rating_high} =
 | 
			
		||||
			[rating: 1850, rating_deviation: 50]
 | 
			
		||||
			|> Player.new_v2
 | 
			
		||||
			|> Player.rating_interval
 | 
			
		||||
  test "rating interval" do
 | 
			
		||||
    assert {rating_low, rating_high} =
 | 
			
		||||
             [rating: 1850, rating_deviation: 50]
 | 
			
		||||
             |> Player.new_v2()
 | 
			
		||||
             |> Player.rating_interval()
 | 
			
		||||
 | 
			
		||||
		assert_in_delta rating_low, 1750, 0.1
 | 
			
		||||
		assert_in_delta rating_high, 1950, 0.1
 | 
			
		||||
	end
 | 
			
		||||
    assert_in_delta rating_low, 1750, 0.1
 | 
			
		||||
    assert_in_delta rating_high, 1950, 0.1
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1,22 +1,22 @@
 | 
			
		||||
defmodule Glicko.ResultTest do
 | 
			
		||||
	use ExUnit.Case
 | 
			
		||||
  use ExUnit.Case
 | 
			
		||||
 | 
			
		||||
	alias Glicko.{
 | 
			
		||||
		Player,
 | 
			
		||||
		Result,
 | 
			
		||||
	}
 | 
			
		||||
  alias Glicko.{
 | 
			
		||||
    Player,
 | 
			
		||||
    Result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
	doctest Result
 | 
			
		||||
  doctest Result
 | 
			
		||||
 | 
			
		||||
	@opponent Player.new_v2
 | 
			
		||||
  @opponent Player.new_v2()
 | 
			
		||||
 | 
			
		||||
	@valid_game_result Result.new(@opponent, 0.0)
 | 
			
		||||
  @valid_game_result Result.new(@opponent, 0.0)
 | 
			
		||||
 | 
			
		||||
	test "create game result" do
 | 
			
		||||
		assert @valid_game_result == Result.new(@opponent, 0.0)
 | 
			
		||||
	end
 | 
			
		||||
  test "create game result" do
 | 
			
		||||
    assert @valid_game_result == Result.new(@opponent, 0.0)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
	test "create game result with shortcut" do
 | 
			
		||||
		assert @valid_game_result == Result.new(@opponent, :loss)
 | 
			
		||||
	end
 | 
			
		||||
  test "create game result with shortcut" do
 | 
			
		||||
    assert @valid_game_result == Result.new(@opponent, :loss)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user