Run `mix format` (#1)

Add formatter configuration and run `mix format`.

Additionally, manually replace leading tabs in documentation examples
with four spaces.
This commit is contained in:
Mikael Muszynski 2020-01-19 14:58:40 +01:00 committed by James
parent 8612b2aa97
commit e22c34f2ff
8 changed files with 641 additions and 536 deletions

3
.formatter.exs Normal file
View File

@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

View File

@ -39,7 +39,7 @@ defmodule Glicko do
alias __MODULE__.{
Player,
Result,
Result
}
@default_system_constant 0.8
@ -52,9 +52,13 @@ defmodule Glicko do
Returns a value between `0.0` and `1.0`.
"""
@spec win_probability(player :: Player.t, opponent :: Player.t) :: float
@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))
win_probability(
player |> Player.rating(:v2),
opponent |> Player.rating(:v2),
opponent |> Player.rating_deviation(:v2)
)
end
@doc """
@ -64,7 +68,11 @@ defmodule Glicko do
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
@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
@ -74,9 +82,13 @@ defmodule Glicko do
Returns a value between `0.0` and `1.0`.
"""
@spec draw_probability(player :: Player.t, opponent :: Player.t) :: float
@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))
draw_probability(
player |> Player.rating(:v2),
opponent |> Player.rating(:v2),
opponent |> Player.rating_deviation(:v2)
)
end
@doc """
@ -86,9 +98,14 @@ defmodule Glicko do
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
@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
1 -
abs(win_probability(player_rating, opponent_rating, opponent_rating_deviation) - 0.5) / 0.5
end
@doc """
@ -96,16 +113,19 @@ defmodule Glicko do
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
@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
|> Player.to_v2()
|> do_new_rating(results, opts)
|> Player.to_v1
|> Player.to_v1()
end
defp do_new_rating({player_r, player_pre_rd, player_v}, [], _) do
@ -113,6 +133,7 @@ defmodule Glicko do
{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)
@ -126,17 +147,35 @@ defmodule Glicko do
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
{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
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
@ -153,13 +192,14 @@ defmodule Glicko do
Enum.reduce(results, {0.0, 0.0}, fn result, {variance_estimate_acc, result_effect_acc} ->
opponent_rd_g =
result
|> Result.opponent_rating_deviation
|> Result.opponent_rating_deviation()
|> calc_g
win_probability = calc_e(player_pre_r, Result.opponent_rating(result), opponent_rd_g)
{
variance_estimate_acc + :math.pow(opponent_rd_g, 2) * win_probability * (1 - win_probability),
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)
@ -195,11 +235,12 @@ defmodule Glicko do
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))
: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)
@ -210,17 +251,41 @@ defmodule Glicko do
{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
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)
iterative_algorithm_body(
alpha,
delta,
player_pre_rd_sq,
variance_est,
sys_const,
conv_tol,
a,
c,
fa,
fc
)
else
a
end
@ -236,7 +301,7 @@ defmodule Glicko do
# g function
defp calc_g(rd) do
1 / :math.sqrt(1 + 3 * :math.pow(rd, 2) / :math.pow(:math.pi, 2))
1 / :math.sqrt(1 + 3 * :math.pow(rd, 2) / :math.pow(:math.pi(), 2))
end
# E function

View File

@ -63,7 +63,9 @@ defmodule Glicko.Player do
"""
@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)
def initial_rating_deviation(_version = :v2),
do: :v1 |> initial_rating_deviation |> scale_rating_deviation_to(:v2)
@doc """
The recommended initial volatility value for a new player.
@ -76,10 +78,11 @@ defmodule Glicko.Player do
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: {
@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)),
Keyword.get(opts, :rating_deviation, initial_rating_deviation(:v1))
}
@doc """
@ -87,11 +90,12 @@ defmodule Glicko.Player do
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: {
@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()),
Keyword.get(opts, :volatility, initial_volatility())
}
@doc """
@ -103,9 +107,11 @@ defmodule Glicko.Player do
"""
@spec to_v1(player :: t) :: v1
def to_v1({rating, rating_deviation}), do: {rating, rating_deviation}
def to_v1({rating, rating_deviation, _}), do: {
def to_v1({rating, rating_deviation, _}),
do: {
rating |> scale_rating_to(:v1),
rating_deviation |> scale_rating_deviation_to(:v1),
rating_deviation |> scale_rating_deviation_to(:v1)
}
@doc """
@ -115,11 +121,15 @@ defmodule Glicko.Player do
"""
@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: {
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,
volatility
}
@doc """
@ -142,8 +152,13 @@ defmodule Glicko.Player do
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 |> 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 """
@ -170,23 +185,30 @@ defmodule Glicko.Player do
be 95% confident about a players 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: {
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,
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
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
@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

View File

@ -14,7 +14,7 @@ defmodule Glicko.Result do
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
@ -29,11 +29,13 @@ defmodule Glicko.Result do
Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
"""
@spec new(Player.rating, Player.rating_deviation, score | score_shortcut) :: t
@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
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
@ -42,7 +44,7 @@ defmodule Glicko.Result do
Supports passing either `:loss`, `:draw`, or `:win` as shortcuts.
"""
@spec new(opponent :: Player.t, score :: score | score_shortcut) :: t
@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
@ -50,19 +52,18 @@ defmodule Glicko.Result do
@doc """
Convenience function for accessing an opponent's rating.
"""
@spec opponent_rating(result :: Result.t) :: Player.rating
@spec opponent_rating(result :: Result.t()) :: Player.rating()
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
@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
@spec score(result :: Result.t()) :: score
def score(_result = {_, _, score}), do: score
end

17
mix.exs
View File

@ -5,26 +5,29 @@ defmodule Glicko.Mixfile do
Implementation of the Glicko rating system
"""
def project, do: [
def project,
do: [
app: :glicko,
version: "0.6.0",
elixir: "~> 1.5",
start_permanent: Mix.env == :prod,
start_permanent: Mix.env() == :prod,
deps: deps(),
package: package(),
description: @description,
description: @description
]
defp deps, do: [
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},
{:credo, "~> 0.8", only: [:dev, :test], runtime: false}
]
defp package, do: [
defp package,
do: [
name: :glicko,
maintainers: ["James Dyson"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/avitex/elixir-glicko"},
links: %{"GitHub" => "https://github.com/avitex/elixir-glicko"}
]
end

View File

@ -3,66 +3,75 @@ defmodule GlickoTest do
alias Glicko.{
Player,
Result,
Result
}
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),
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_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])
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.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
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
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
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
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
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
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
assert Glicko.draw_probability(Player.new_v1(rating: 1600), Player.new_v1(rating: 1500)) < 1
end
end
end

View File

@ -9,19 +9,21 @@ defmodule Glicko.PlayerTest do
@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])
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])
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)
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)
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
@ -51,8 +53,8 @@ defmodule Glicko.PlayerTest do
test "rating interval" do
assert {rating_low, rating_high} =
[rating: 1850, rating_deviation: 50]
|> Player.new_v2
|> Player.rating_interval
|> Player.new_v2()
|> Player.rating_interval()
assert_in_delta rating_low, 1750, 0.1
assert_in_delta rating_high, 1950, 0.1

View File

@ -3,12 +3,12 @@ defmodule Glicko.ResultTest do
alias Glicko.{
Player,
Result,
Result
}
doctest Result
@opponent Player.new_v2
@opponent Player.new_v2()
@valid_game_result Result.new(@opponent, 0.0)