mirror of
https://github.com/avitex/elixir-glicko
synced 2024-11-22 03:09:57 +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