mirror of
				https://github.com/avitex/elixir-glicko
				synced 2025-11-04 07:33:27 +00:00 
			
		
		
		
	Use tuples exclusively to represent players and results
This commit is contained in:
		
							parent
							
								
									61175818ad
								
							
						
					
					
						commit
						faf5218189
					
				@ -6,9 +6,6 @@ defmodule Glicko do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	## Usage
 | 
						## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Players can be represented by either the convenience `Glicko.Player` module or a tuple (see `player_t`).
 | 
					 | 
				
			||||||
	Results can be represented by either the convenience `Glicko.Result` module or a tuple (see `result_t`).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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),
 | 
							iex> results = [Result.new(Player.new_v1([rating: 1400, rating_deviation: 30]), :win),
 | 
				
			||||||
@ -16,13 +13,13 @@ defmodule Glicko do
 | 
				
			|||||||
		...> Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss)]
 | 
							...> Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss)]
 | 
				
			||||||
		iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
							iex> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
				
			||||||
		iex> Glicko.new_rating(player, results, [system_constant: 0.5])
 | 
							iex> Glicko.new_rating(player, results, [system_constant: 0.5])
 | 
				
			||||||
		%Glicko.Player{version: :v1, rating: 1464.0506705393013, rating_deviation: 151.51652412385727, volatility: nil}
 | 
							{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> player = Player.new_v1([rating: 1500, rating_deviation: 200])
 | 
				
			||||||
		iex> Glicko.new_rating(player, [], [system_constant: 0.5])
 | 
							iex> Glicko.new_rating(player, [], [system_constant: 0.5])
 | 
				
			||||||
		%Glicko.Player{version: :v1, rating: 1.5e3, rating_deviation: 200.27141669877065, volatility: nil}
 | 
							{1.5e3, 200.27141669877065}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,18 +31,6 @@ defmodule Glicko do
 | 
				
			|||||||
	@default_system_constant 0.8
 | 
						@default_system_constant 0.8
 | 
				
			||||||
	@default_convergence_tolerance 1.0e-7
 | 
						@default_convergence_tolerance 1.0e-7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@type version_t :: :v1 | :v2
 | 
					 | 
				
			||||||
	@type rating_t :: float
 | 
					 | 
				
			||||||
	@type rating_deviation_t :: float
 | 
					 | 
				
			||||||
	@type volatility_t :: float
 | 
					 | 
				
			||||||
	@type score_t :: float
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@type player_t :: player_v1_t | player_v2_t
 | 
					 | 
				
			||||||
	@type player_v1_t :: {rating :: rating_t, rating_deviation :: rating_deviation_t}
 | 
					 | 
				
			||||||
	@type player_v2_t :: {rating :: rating_t, rating_deviation :: rating_deviation_t, volatility :: volatility_t}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@type result_t :: {opponent :: player_t, score :: score_t}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@type new_rating_opts_t :: [system_constant: float, convergence_tolerance: float]
 | 
						@type new_rating_opts_t :: [system_constant: float, convergence_tolerance: float]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
@ -53,11 +38,16 @@ defmodule Glicko do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	Returns the updated player with the same version given to the function.
 | 
						Returns the updated player with the same version given to the function.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec new_rating(player :: player_t | Player.t, results :: list(result_t | Result.t), opts :: new_rating_opts_t) :: player_t | Player.t
 | 
						@spec new_rating(player :: Player.t, results :: list(Result.t), opts :: new_rating_opts_t) :: Player.t
 | 
				
			||||||
	def new_rating(player, results, opts \\ []) do
 | 
						def new_rating(player, results, opts \\ [])
 | 
				
			||||||
		{cast_from, internal_player} = cast_player_to_internal(player)
 | 
						def new_rating(player, results, opts) when tuple_size(player) == 3 do
 | 
				
			||||||
		internal_player = do_new_rating(internal_player, results, opts)
 | 
							do_new_rating(player, results, opts)
 | 
				
			||||||
		cast_internal_to_player({cast_from, internal_player})
 | 
						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
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defp do_new_rating({player_rating, player_rating_deviation, player_volatility}, [], _) do
 | 
						defp do_new_rating({player_rating, player_rating_deviation, player_volatility}, [], _) do
 | 
				
			||||||
@ -111,21 +101,16 @@ defmodule Glicko do
 | 
				
			|||||||
		{ctx.new_player_rating, ctx.new_player_rating_deviation, ctx.new_player_volatility}
 | 
							{ctx.new_player_rating, ctx.new_player_rating_deviation, ctx.new_player_volatility}
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defp build_internal_result(ctx, {opponent, score}) do
 | 
						defp build_internal_result(ctx, result) do
 | 
				
			||||||
		{_, {opponent_rating, opponent_rating_deviation, _}} = cast_player_to_internal(opponent)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		result =
 | 
							result =
 | 
				
			||||||
			Map.new
 | 
								Map.new
 | 
				
			||||||
			|> Map.put(:score, score)
 | 
								|> Map.put(:score, Result.score(result))
 | 
				
			||||||
			|> Map.put(:opponent_rating, opponent_rating)
 | 
								|> Map.put(:opponent_rating, Result.opponent_rating(result))
 | 
				
			||||||
			|> Map.put(:opponent_rating_deviation, opponent_rating_deviation)
 | 
								|> Map.put(:opponent_rating_deviation, Result.opponent_rating_deviation(result))
 | 
				
			||||||
			|> Map.put(:opponent_rating_deviation_g, calc_g(opponent_rating_deviation))
 | 
								|> Map.put(:opponent_rating_deviation_g, calc_g(Result.opponent_rating_deviation(result)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Map.put(result, :e, calc_e(ctx.player_rating, result))
 | 
							Map.put(result, :e, calc_e(ctx.player_rating, result))
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	defp build_internal_result(ctx, %Result{score: score, opponent: opponent}) do
 | 
					 | 
				
			||||||
		build_internal_result(ctx, {opponent, score})
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Calculation of the estimated variance of the player's rating based on game outcomes
 | 
						# Calculation of the estimated variance of the player's rating based on game outcomes
 | 
				
			||||||
	defp calc_variance_estimate(ctx) do
 | 
						defp calc_variance_estimate(ctx) do
 | 
				
			||||||
@ -218,26 +203,4 @@ defmodule Glicko do
 | 
				
			|||||||
	defp calc_e(player_rating, result) do
 | 
						defp calc_e(player_rating, result) do
 | 
				
			||||||
		1 / (1 + :math.exp(-1 * result.opponent_rating_deviation_g * (player_rating - result.opponent_rating)))
 | 
							1 / (1 + :math.exp(-1 * result.opponent_rating_deviation_g * (player_rating - result.opponent_rating)))
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					 | 
				
			||||||
	defp cast_player_to_internal(player) when is_tuple(player) and tuple_size(player) == 2 do
 | 
					 | 
				
			||||||
		{:v1, Player.new_v1(player) |> Player.to_v2 |> cast_player_to_internal |> elem(1)}
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
	defp cast_player_to_internal(player) when is_tuple(player) and tuple_size(player) == 3 do
 | 
					 | 
				
			||||||
		{:v2, player}
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
	defp cast_player_to_internal(player = %Player{version: :v1}) do
 | 
					 | 
				
			||||||
		{:player_v1, player |> Player.to_v2 |> cast_player_to_internal |> elem(1)}
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
	defp cast_player_to_internal(player = %Player{version: :v2}) do
 | 
					 | 
				
			||||||
		{:player_v2, {player.rating, player.rating_deviation, player.volatility}}
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	defp cast_internal_to_player({:v1, {rating, rating_deviation, _}}), do: {
 | 
					 | 
				
			||||||
		rating |> Player.scale_rating_to(:v1),
 | 
					 | 
				
			||||||
		rating_deviation |> Player.scale_rating_deviation_to(:v1),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defp cast_internal_to_player({:v2, player}), do: player
 | 
					 | 
				
			||||||
	defp cast_internal_to_player({:player_v1, player}), do: Player.new_v2(player) |> Player.to_v1
 | 
					 | 
				
			||||||
	defp cast_internal_to_player({:player_v2, player}), do: Player.new_v2(player)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
@ -1,28 +1,33 @@
 | 
				
			|||||||
defmodule Glicko.Player do
 | 
					defmodule Glicko.Player do
 | 
				
			||||||
	@moduledoc """
 | 
						@moduledoc """
 | 
				
			||||||
	A convenience wrapper that handles conversions between glicko versions one and two.
 | 
						Provides convenience functions that handle conversions between Glicko versions one and two.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	## Usage
 | 
						## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Create a 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}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Create a *v2* player with the default values for an unrated player.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		iex> Player.new_v2
 | 
							iex> Player.new_v2
 | 
				
			||||||
		%Player{version: :v2, rating: 0.0, rating_deviation: 2.014761872416068, volatility: 0.06}
 | 
							{0.0, 2.014761872416068, 0.06}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Create a player with custom values.
 | 
						Create a player with custom values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		iex> Player.new_v2([rating: 1500, rating_deviation: 50, volatility: 0.05])
 | 
							iex> Player.new_v2([rating: 3.0, rating_deviation: 2.0, volatility: 0.05])
 | 
				
			||||||
		%Player{version: :v2, rating: 1500, rating_deviation: 50, 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
 | 
							iex> Player.new_v2 |> Player.to_v1
 | 
				
			||||||
		%Player{version: :v1, rating: 1.5e3, rating_deviation: 350.0, volatility: nil}
 | 
							{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)
 | 
							iex> Player.new_v1 |> Player.to_v2(0.06)
 | 
				
			||||||
		%Player{version: :v2, rating: 0.0, rating_deviation: 2.014761872416068, volatility: 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
 | 
						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.
 | 
						will pass-through unchanged. The volatility arg in this case is ignored.
 | 
				
			||||||
@ -38,89 +43,56 @@ defmodule Glicko.Player do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@type t :: v1_t | v2_t
 | 
						@type t :: v1_t | v2_t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@type v1_t :: %__MODULE__{
 | 
						@type v1_t :: {rating_t, rating_deviation_t}
 | 
				
			||||||
		version: :v1,
 | 
						@type v2_t :: {rating_t, rating_deviation_t, volatility_t}
 | 
				
			||||||
		rating: Glicko.rating_t,
 | 
					 | 
				
			||||||
		rating_deviation: Glicko.rating_deviation_t,
 | 
					 | 
				
			||||||
		volatility: nil,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@type v2_t :: %__MODULE__{
 | 
						@type version_t :: :v1 | :v2
 | 
				
			||||||
		version: :v2,
 | 
						@type rating_t :: float
 | 
				
			||||||
		rating: Glicko.rating_t,
 | 
						@type rating_deviation_t :: float
 | 
				
			||||||
		rating_deviation: Glicko.rating_deviation_t,
 | 
						@type volatility_t :: float
 | 
				
			||||||
		volatility: Glicko.volatility_t,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	defstruct [
 | 
					 | 
				
			||||||
		:version,
 | 
					 | 
				
			||||||
		:rating,
 | 
					 | 
				
			||||||
		:rating_deviation,
 | 
					 | 
				
			||||||
		:volatility,
 | 
					 | 
				
			||||||
	]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	The recommended initial rating value for a new player.
 | 
						The recommended initial rating value for a new player.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec initial_rating(Glicko.version_t) :: Glicko.rating_t
 | 
						@spec initial_rating(version_t) :: rating_t
 | 
				
			||||||
	def initial_rating(:v1), do: 1500.0
 | 
						def initial_rating(_version = :v1), do: 1500.0
 | 
				
			||||||
	def initial_rating(:v2), do: initial_rating(:v1) |> scale_rating_to(:v2)
 | 
						def initial_rating(_version = :v2), do: :v1 |> initial_rating |> scale_rating_to(:v2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	The recommended initial rating deviation value for a new player.
 | 
						The recommended initial rating deviation value for a new player.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec initial_rating_deviation(Glicko.version_t) :: Glicko.rating_deviation_t
 | 
						@spec initial_rating_deviation(version_t) :: rating_deviation_t
 | 
				
			||||||
	def initial_rating_deviation(:v1), do: 350.0
 | 
						def initial_rating_deviation(_version = :v1), do: 350.0
 | 
				
			||||||
	def initial_rating_deviation(:v2), do: initial_rating_deviation(:v1) |> scale_rating_deviation_to(:v2)
 | 
						def initial_rating_deviation(_version = :v2), do: :v1 |> initial_rating_deviation |> scale_rating_deviation_to(:v2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	The recommended initial volatility value for a new player.
 | 
						The recommended initial volatility value for a new player.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec initial_v2_volatility :: Glicko.volatility_t
 | 
						@spec initial_volatility :: volatility_t
 | 
				
			||||||
	def initial_v2_volatility, do: 0.06
 | 
						def initial_volatility, do: 0.06
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Creates a new v1 player.
 | 
						Creates a new v1 player.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	If not overriden, will use the default values for an unrated player.
 | 
						If not overriden, will use the default values for an unrated player.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec new_v1(
 | 
						@spec new_v1([rating: rating_t, rating_deviation: rating_deviation_t]) :: v1_t
 | 
				
			||||||
		{Glicko.rating_t, Glicko.rating_deviation_t} |
 | 
						def new_v1(opts \\ []) when is_list(opts), do: {
 | 
				
			||||||
		[rating: Glicko.rating_t, rating_deviation: Glicko.rating_deviation_t]
 | 
					 | 
				
			||||||
	) :: v1_t
 | 
					 | 
				
			||||||
	def new_v1(opts \\ [])
 | 
					 | 
				
			||||||
	def new_v1({rating, rating_deviation}), do: %__MODULE__{
 | 
					 | 
				
			||||||
		version: :v1,
 | 
					 | 
				
			||||||
		rating: rating,
 | 
					 | 
				
			||||||
		rating_deviation: rating_deviation,
 | 
					 | 
				
			||||||
		volatility: nil,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	def new_v1(opts), do: new_v1({
 | 
					 | 
				
			||||||
		Keyword.get(opts, :rating, initial_rating(:v1)),
 | 
							Keyword.get(opts, :rating, initial_rating(:v1)),
 | 
				
			||||||
		Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v1)),
 | 
							Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v1)),
 | 
				
			||||||
	})
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Creates a new v2 player.
 | 
						Creates a new v2 player.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	If not overriden, will use default values for an unrated player.
 | 
						If not overriden, will use default values for an unrated player.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec new_v2(
 | 
						@spec new_v2([rating: rating_t, rating_deviation: rating_deviation_t, volatility: volatility_t]) :: v2_t
 | 
				
			||||||
		{Glicko.rating_t, Glicko.rating_deviation_t, Glicko.volatility_t} |
 | 
						def new_v2(opts \\ []) when is_list(opts), do: {
 | 
				
			||||||
		[rating: Glicko.rating_t, rating_deviation: Glicko.rating_deviation_t, volatility: Glicko.volatility_t]
 | 
					 | 
				
			||||||
	) :: v2_t
 | 
					 | 
				
			||||||
	def new_v2(opts \\ [])
 | 
					 | 
				
			||||||
	def new_v2({rating, rating_deviation, volatility}), do: %__MODULE__{
 | 
					 | 
				
			||||||
		version: :v2,
 | 
					 | 
				
			||||||
		rating: rating,
 | 
					 | 
				
			||||||
		rating_deviation: rating_deviation,
 | 
					 | 
				
			||||||
		volatility: volatility,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	def new_v2(opts), do: new_v2({
 | 
					 | 
				
			||||||
		Keyword.get(opts, :rating, initial_rating(:v2)),
 | 
							Keyword.get(opts, :rating, initial_rating(:v2)),
 | 
				
			||||||
		Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v2)),
 | 
							Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v2)),
 | 
				
			||||||
		Keyword.get(opts, :volatility, initial_v2_volatility()),
 | 
							Keyword.get(opts, :volatility, initial_volatility()),
 | 
				
			||||||
	})
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Converts a v2 player to a v1.
 | 
						Converts a v2 player to a v1.
 | 
				
			||||||
@ -130,25 +102,57 @@ defmodule Glicko.Player do
 | 
				
			|||||||
	Note the volatility field used in a v2 player will be lost in the conversion.
 | 
						Note the volatility field used in a v2 player will be lost in the conversion.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec to_v1(player :: t) :: v1_t
 | 
						@spec to_v1(player :: t) :: v1_t
 | 
				
			||||||
	def to_v1(player = %__MODULE__{version: :v1}), do: player
 | 
						def to_v1({rating, rating_deviation}), do: {rating, rating_deviation}
 | 
				
			||||||
	def to_v1(player = %__MODULE__{version: :v2}), do: new_v1([
 | 
						def to_v1({rating, rating_deviation, _}), do: {
 | 
				
			||||||
		rating: player.rating |> scale_rating_to(:v1),
 | 
							rating |> scale_rating_to(:v1),
 | 
				
			||||||
		rating_deviation: player.rating_deviation |> scale_rating_deviation_to(:v1),
 | 
							rating_deviation |> scale_rating_deviation_to(:v1),
 | 
				
			||||||
	])
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Converts a v1 player to a v2.
 | 
						Converts a v1 player to a v2.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	A v2 player will pass-through unchanged with the volatility arg ignored.
 | 
						A v2 player will pass-through unchanged with the volatility arg ignored.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec to_v2(player :: t, volatility :: float) :: v2_t
 | 
						@spec to_v2(player :: t, volatility :: volatility_t) :: v2_t
 | 
				
			||||||
	def to_v2(player, volatility \\ initial_v2_volatility())
 | 
						def to_v2(player, volatility \\ initial_volatility())
 | 
				
			||||||
	def to_v2(player = %__MODULE__{version: :v2}, _volatility), do: player
 | 
						def to_v2({rating, rating_deviation, volatility}, _volatility), do: {rating, rating_deviation, volatility}
 | 
				
			||||||
	def to_v2(player = %__MODULE__{version: :v1}, volatility), do: new_v2([
 | 
						def to_v2({rating, rating_deviation}, volatility), do: {
 | 
				
			||||||
		rating: player.rating |> scale_rating_to(:v2),
 | 
							rating |> scale_rating_to(:v2),
 | 
				
			||||||
		rating_deviation: player.rating_deviation |> scale_rating_deviation_to(:v2),
 | 
							rating_deviation |> scale_rating_deviation_to(:v2),
 | 
				
			||||||
		volatility: volatility,
 | 
							volatility,
 | 
				
			||||||
	])
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@doc """
 | 
				
			||||||
 | 
						A version agnostic method for getting a player's rating.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec rating(player :: t, as_version :: version_t) :: rating_t
 | 
				
			||||||
 | 
						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 """
 | 
				
			||||||
 | 
						A version agnostic method for getting a player's rating deviation.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec rating_deviation(player :: t, as_version :: version_t) :: rating_deviation_t
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@doc """
 | 
				
			||||||
 | 
						A version agnostic method for getting a player's volatility.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec volatility(player :: t, default_volatility :: volatility_t) :: volatility_t
 | 
				
			||||||
 | 
						def volatility(player, default_volatility \\ initial_volatility())
 | 
				
			||||||
 | 
						def volatility({_, _}, default_volatility), do: default_volatility
 | 
				
			||||||
 | 
						def volatility({_, _, volatility}, _), do: volatility
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	A convenience function for summarizing a player's strength as a 95%
 | 
						A convenience function for summarizing a player's strength as a 95%
 | 
				
			||||||
@ -166,23 +170,23 @@ defmodule Glicko.Player do
 | 
				
			|||||||
	be 95% confident about a player’s strength being in a small interval of values.
 | 
						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}
 | 
						@spec rating_interval(player :: t) :: {rating_low :: float, rating_high :: float}
 | 
				
			||||||
	def rating_interval(player), do: {
 | 
						def rating_interval(player, as_version \\ nil), do: {
 | 
				
			||||||
		player.rating - player.rating_deviation * 2,
 | 
							rating(player, as_version) - rating_deviation(player, as_version) * 2,
 | 
				
			||||||
		player.rating + player.rating_deviation * 2,
 | 
							rating(player, as_version) + rating_deviation(player, as_version) * 2,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Scales a player's rating.
 | 
						Scales a player's rating.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec scale_rating_to(rating :: Glicko.rating_t, to_version :: Glicko.version_t) :: Glicko.rating_t
 | 
						@spec scale_rating_to(rating :: rating_t, to_version :: version_t) :: rating_t
 | 
				
			||||||
	def scale_rating_to(rating, :v1), do: (rating * @magic_version_scale) + @magic_version_scale_rating
 | 
						def scale_rating_to(rating, _version = :v1), do: (rating * @magic_version_scale) + @magic_version_scale_rating
 | 
				
			||||||
	def scale_rating_to(rating, :v2), do: (rating - @magic_version_scale_rating) / @magic_version_scale
 | 
						def scale_rating_to(rating, _version = :v2), do: (rating - @magic_version_scale_rating) / @magic_version_scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Scales a player's rating deviation.
 | 
						Scales a player's rating deviation.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec scale_rating_deviation_to(rating_deviation :: Glicko.rating_deviation_t, to_version :: Glicko.version_t) :: Glicko.rating_deviation_t
 | 
						@spec scale_rating_deviation_to(rating_deviation :: rating_deviation_t, to_version :: version_t) :: rating_deviation_t
 | 
				
			||||||
	def scale_rating_deviation_to(rating_deviation, :v1), do: rating_deviation * @magic_version_scale
 | 
						def scale_rating_deviation_to(rating_deviation, _version = :v1), do: rating_deviation * @magic_version_scale
 | 
				
			||||||
	def scale_rating_deviation_to(rating_deviation, :v2), do: rating_deviation / @magic_version_scale
 | 
						def scale_rating_deviation_to(rating_deviation, _version = :v2), do: rating_deviation / @magic_version_scale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
@ -1,41 +1,66 @@
 | 
				
			|||||||
defmodule Glicko.Result do
 | 
					defmodule Glicko.Result do
 | 
				
			||||||
	@moduledoc """
 | 
						@moduledoc """
 | 
				
			||||||
	A convenience wrapper representing a result against an opponent.
 | 
						Convenience functions for handling a result against an opponent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	## Usage
 | 
						## Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		iex> opponent = Player.new_v2
 | 
							iex> opponent = Player.new_v2
 | 
				
			||||||
		iex> Result.new(opponent, 0.0)
 | 
							iex> Result.new(opponent, 1.0)
 | 
				
			||||||
		%Result{score: 0.0, opponent: %Player{version: :v2, rating: 0.0, rating_deviation: 2.014761872416068, volatility: 0.06}}
 | 
							{0.0, 2.014761872416068, 1.0}
 | 
				
			||||||
		iex> Result.new(opponent, :win) # With shortcut
 | 
							iex> Result.new(opponent, :draw) # With shortcut
 | 
				
			||||||
		%Result{score: 1.0, opponent: %Player{version: :v2, rating: 0.0, rating_deviation: 2.014761872416068, volatility: 0.06}}
 | 
							{0.0, 2.014761872416068, 0.5}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	alias Glicko.Player
 | 
						alias Glicko.Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	defstruct [
 | 
						@type t :: {Player.rating_t, Player.rating_deviation_t, score_t}
 | 
				
			||||||
		:score,
 | 
					 | 
				
			||||||
		:opponent,
 | 
					 | 
				
			||||||
	]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@type t :: %__MODULE__{score: Glicko.score_t, opponent: Glicko.player_t | Player.t}
 | 
						@type score_t :: float
 | 
				
			||||||
 | 
						@type score_shortcut_t :: :loss | :draw | :win
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@type result_type_t :: :loss | :draw | :win
 | 
						@score_shortcut_map %{loss: 0.0, draw: 0.5, win: 1.0}
 | 
				
			||||||
 | 
						@score_shortcuts Map.keys(@score_shortcut_map)
 | 
				
			||||||
	@result_type_map %{loss: 0.0, draw: 0.5, win: 1.0}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@doc """
 | 
						@doc """
 | 
				
			||||||
	Creates a new Result against an opponent.
 | 
						Creates a new result from an opponent rating, opponent rating deviation and score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
						Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
				
			||||||
	"""
 | 
						"""
 | 
				
			||||||
	@spec new(opponent :: Glicko.player_t | Player.t, result_type_t | float) :: t
 | 
						@spec new(Player.rating_t, Player.rating_deviation_t, score_t | score_shortcut_t) :: t
 | 
				
			||||||
	def new(opponent, result_type) when is_atom(result_type) and result_type in [:loss, :draw, :win] do
 | 
						def new(opponent_rating, opponent_rating_deviation, score) when is_number(score) do
 | 
				
			||||||
		new(opponent, Map.fetch!(@result_type_map, result_type))
 | 
							{opponent_rating, opponent_rating_deviation, score}
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	def new(opponent, score) when is_number(score), do: %__MODULE__{
 | 
						def new(opponent_rating, opponent_rating_deviation, score_type) when is_atom(score_type) and score_type in @score_shortcuts do
 | 
				
			||||||
		score: score,
 | 
							{opponent_rating, opponent_rating_deviation, Map.fetch!(@score_shortcut_map, score_type)}
 | 
				
			||||||
		opponent: opponent,
 | 
						end
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
 | 
						@doc """
 | 
				
			||||||
 | 
						Creates a new result from an opponent and score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec new(opponent :: Player.t, score :: score_t | score_shortcut_t) :: 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.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec opponent_rating(result :: Result.t) :: Player.rating_t
 | 
				
			||||||
 | 
						def opponent_rating(_result = {rating, _, _}), do: rating
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@doc """
 | 
				
			||||||
 | 
						Convenience function for accessing an opponent's rating deviation.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec opponent_rating_deviation(result :: Result.t) :: Player.rating_deviation_t
 | 
				
			||||||
 | 
						def opponent_rating_deviation(_result = {_, rating_deviation, _}), do: rating_deviation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@doc """
 | 
				
			||||||
 | 
						Convenience function for accessing the score.
 | 
				
			||||||
 | 
						"""
 | 
				
			||||||
 | 
						@spec score(result :: Result.t) :: score_t
 | 
				
			||||||
 | 
						def score(_result = {_, _, score}), do: score
 | 
				
			||||||
 | 
					
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
@ -11,8 +11,8 @@ defmodule GlickoTest do
 | 
				
			|||||||
	@player Player.new_v1([rating: 1500, rating_deviation: 200]) |> Player.to_v2
 | 
						@player Player.new_v1([rating: 1500, rating_deviation: 200]) |> Player.to_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@results [
 | 
						@results [
 | 
				
			||||||
		{{1400, 30}, 1.0},
 | 
							Result.new(Player.new_v1([rating: 1400, rating_deviation: 30]), :win),
 | 
				
			||||||
		Result.new({1550, 100}, :loss),
 | 
							Result.new(Player.new_v1([rating: 1550, rating_deviation: 100]), :loss),
 | 
				
			||||||
		Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss),
 | 
							Result.new(Player.new_v1([rating: 1700, rating_deviation: 300]), :loss),
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,17 +23,16 @@ defmodule GlickoTest do
 | 
				
			|||||||
	@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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "new rating (with results)" do
 | 
						test "new rating (with results)" do
 | 
				
			||||||
		%Player{rating: new_rating, rating_deviation: new_rating_deviation, volatility: new_volatility} =
 | 
							player = Glicko.new_rating(@player, @results, [system_constant: 0.5])
 | 
				
			||||||
			Glicko.new_rating(@player, @results, [system_constant: 0.5])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert_in_delta new_rating, @valid_player_rating_after_results, 1.0e-4
 | 
							assert_in_delta Player.rating(player), @valid_player_rating_after_results, 1.0e-4
 | 
				
			||||||
		assert_in_delta new_rating_deviation, @valid_player_rating_deviation_after_results, 1.0e-4
 | 
							assert_in_delta Player.rating_deviation(player), @valid_player_rating_deviation_after_results, 1.0e-4
 | 
				
			||||||
		assert_in_delta new_volatility, @valid_player_volatility_after_results, 1.0e-5
 | 
							assert_in_delta Player.volatility(player), @valid_player_volatility_after_results, 1.0e-5
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "new rating (no results)" do
 | 
						test "new rating (no results)" do
 | 
				
			||||||
		%Player{rating_deviation: new_rating_deviation} = Glicko.new_rating(@player, [])
 | 
							player = Glicko.new_rating(@player, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert_in_delta new_rating_deviation, @valid_player_rating_deviation_after_no_results, 1.0e-4
 | 
							assert_in_delta Player.rating_deviation(player), @valid_player_rating_deviation_after_no_results, 1.0e-4
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
				
			|||||||
@ -5,8 +5,8 @@ defmodule Glicko.PlayerTest do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	doctest Player
 | 
						doctest Player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@valid_v1_base %Player{version: :v1, rating: 1.0, rating_deviation: 2.0, volatility: nil}
 | 
						@valid_v1_base {1.0, 2.0}
 | 
				
			||||||
	@valid_v2_base %Player{version: :v2, rating: 1.0, rating_deviation: 2.0, volatility: 3.0}
 | 
						@valid_v2_base {1.0, 2.0, 3.0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "create v1" do
 | 
						test "create v1" do
 | 
				
			||||||
		assert @valid_v1_base == Player.new_v1([rating: 1.0, rating_deviation: 2.0])
 | 
							assert @valid_v1_base == Player.new_v1([rating: 1.0, rating_deviation: 2.0])
 | 
				
			||||||
@ -17,21 +17,11 @@ defmodule Glicko.PlayerTest do
 | 
				
			|||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "convert player v1 -> v2" do
 | 
						test "convert player v1 -> v2" do
 | 
				
			||||||
		assert %Player{
 | 
							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)
 | 
				
			||||||
			version: :v2,
 | 
					 | 
				
			||||||
			rating: Player.scale_rating_to(1.0, :v2),
 | 
					 | 
				
			||||||
			rating_deviation: Player.scale_rating_deviation_to(2.0, :v2),
 | 
					 | 
				
			||||||
			volatility: 3.0,
 | 
					 | 
				
			||||||
		} == Player.to_v2(@valid_v1_base, 3.0)
 | 
					 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "convert player v2 -> v1" do
 | 
						test "convert player v2 -> v1" do
 | 
				
			||||||
		assert %Player{
 | 
							assert {Player.scale_rating_to(1.0, :v1), Player.scale_rating_deviation_to(2.0, :v1)} == Player.to_v1(@valid_v2_base)
 | 
				
			||||||
			version: :v1,
 | 
					 | 
				
			||||||
			rating: Player.scale_rating_to(1.0, :v1),
 | 
					 | 
				
			||||||
			rating_deviation: Player.scale_rating_deviation_to(2.0, :v1),
 | 
					 | 
				
			||||||
			volatility: nil,
 | 
					 | 
				
			||||||
		} == Player.to_v1(@valid_v2_base)
 | 
					 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "convert player v1 -> v1" do
 | 
						test "convert player v1 -> v1" do
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@ defmodule Glicko.ResultTest do
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@opponent Player.new_v2
 | 
						@opponent Player.new_v2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@valid_game_result %Result{opponent: @opponent, score: 0.0}
 | 
						@valid_game_result Result.new(@opponent, 0.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	test "create game result" do
 | 
						test "create game result" do
 | 
				
			||||||
		assert @valid_game_result == Result.new(@opponent, 0.0)
 | 
							assert @valid_game_result == Result.new(@opponent, 0.0)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user