]>
Commit | Line | Data |
---|---|---|
dc4278d6 MG |
1 | package Games::Ratings::LogisticElo; |
2 | ||
3 | use 5.014000; | |
4 | use strict; | |
5 | use warnings; | |
6 | use parent qw/Exporter Games::Ratings/; | |
7 | ||
8 | our @EXPORT_OK = qw/multi_elo/; | |
9 | our @EXPORT = qw//; | |
10 | our $VERSION = '0.001'; | |
11 | ||
12 | use List::Util qw/sum/; | |
13 | ||
14 | sub get_rating_change { | |
15 | my ($self) = @_; | |
16 | ||
17 | my $own_rating = $self->get_rating; | |
18 | my $K = $self->get_coefficient; | |
19 | ||
20 | my $expected = sum map { | |
21 | my $exp = ($_->{opponent_rating} - $own_rating) / 400; | |
22 | 1 / (1 + 10 ** $exp) | |
23 | } $self->get_all_games; | |
24 | ||
25 | my $actual = sum map { | |
26 | Games::Ratings::_get_numerical_result($_->{result}) | |
27 | } $self->get_all_games; | |
28 | ||
29 | $K * ($actual - $expected) | |
30 | } | |
31 | ||
32 | sub get_new_rating { | |
33 | my ($self) = @_; | |
34 | $self->get_rating + $self->get_rating_change | |
35 | } | |
36 | ||
37 | sub multi_elo { | |
38 | my @args = @_; | |
39 | my $K = ref $args[0] ? 15 : shift @args; | |
40 | ||
41 | my @newratings = map { | |
42 | my $player = __PACKAGE__->new; | |
43 | $player->set_rating($_->[0]); | |
44 | $player->set_coefficient($K); | |
45 | for my $opponent (@args) { | |
46 | $player->add_game({ | |
47 | opponent_rating => $opponent->[0], | |
48 | result => | |
49 | $_->[1] > $opponent->[1] ? 'win' : | |
50 | $_->[1] < $opponent->[1] ? 'loss' : 'draw' | |
51 | }) | |
52 | } | |
53 | $player->get_new_rating | |
54 | } @args; | |
55 | ||
56 | wantarray ? @newratings : \@newratings | |
57 | } | |
58 | ||
59 | 1; | |
60 | __END__ | |
61 | ||
62 | =encoding utf-8 | |
63 | ||
64 | =head1 NAME | |
65 | ||
66 | Games::Ratings::LogisticElo - calculate changes to logistic curve Elo ratings | |
67 | ||
68 | =head1 SYNOPSIS | |
69 | ||
70 | use Games::Ratings::LogisticElo; | |
71 | my $player = Games::Ratings::LogisticElo->new; | |
72 | $player->set_rating(2240); | |
73 | $player->set_coefficient(15); | |
74 | $player->add_game({ | |
75 | opponent_rating => 2114, | |
76 | result => 'win', ## or 'draw' or 'loss' | |
77 | }); | |
78 | say 'Rating change: ' . $player->get_rating_change; | |
79 | say 'New rating: ' . $player->get_new_rating; | |
80 | ||
81 | use Games::Ratings::LogisticElo qw/multi_elo/; | |
82 | my @results = [2240, 3], [2114, 2], [2300, 1]; | |
83 | my @new_ratings = multi_elo 15, @results; | |
84 | say 'Rating changes for this comp: ', join ', ', | |
85 | map { $new_ratings[$_] - $results[$_]->[0] } 0 .. $#results; | |
86 | ||
87 | =head1 DESCRIPTION | |
88 | ||
89 | This module provides methods to calculate Elo rating changes. Unlike | |
90 | L<Games::Ratings::Chess::FIDE>, this Elo implementation uses the | |
91 | logistic distribution instead of the standard distribution. | |
92 | ||
93 | This module can be used both for a single player who played multiple | |
94 | rated games, and for a single competition with an arbitrary number of | |
95 | players. | |
96 | ||
97 | =head1 FUNCTIONS | |
98 | ||
99 | Games::Ratings::LogisticElo inherits from L<Games::Ratings>, see that | |
100 | module's documentation for information about the inherited methods. | |
101 | ||
102 | Nothing is exported by default, the function B<multi_elo> can be | |
103 | exported on request. | |
104 | ||
105 | =over | |
106 | ||
107 | =item B<$self>->I<get_rating_change> | |
108 | ||
109 | Computes and returns how much a player's rating changes after the | |
110 | games added. | |
111 | ||
112 | =item B<$self>->I<get_new_rating> | |
113 | ||
114 | Adds the result of I<get_rating_change> to the old rating of the | |
115 | player and returns this. | |
116 | ||
117 | =item B<multi_elo> [$coefficient], @results | |
118 | ||
119 | Computes the ratings after a competition with an arbitrary number of | |
120 | players. | |
121 | ||
122 | The first argument is the coefficient. It is optional, with the | |
123 | default coefficient being 15. The next arguments are the results of | |
124 | the players. Each result is a 2-element arrayref, the first element | |
125 | being the Elo rating of the player, and the second element being the | |
126 | score that player obtained. The scores are only used to compare | |
127 | players, their absolute values are irrelevant. | |
128 | ||
129 | The return value is a list (in list context) or arrayref (in scalar | |
130 | context) of ratings of all players after the competition, in the same | |
131 | order as the arguments. | |
132 | ||
133 | This function computes the ratings by considering that each player | |
134 | played a game with every other player, with the winner of every game | |
135 | being the player who got the highest score. | |
136 | ||
137 | =back | |
138 | ||
139 | ||
140 | =head1 SEE ALSO | |
141 | ||
142 | L<Games::Ratings::Chess::FIDE>, L<Games::Ratings> | |
143 | ||
144 | L<https://en.wikipedia.org/wiki/Elo_rating> | |
145 | ||
146 | =head1 AUTHOR | |
147 | ||
148 | Marius Gavrilescu <marius@ieval.ro> | |
149 | ||
150 | =head1 COPYRIGHT AND LICENSE | |
151 | ||
152 | Copyright (C) 2016 by Marius Gavrilescu | |
153 | ||
154 | This library is free software; you can redistribute it and/or modify | |
155 | it under the same terms as Perl itself, either Perl version 5.22.2 or, | |
156 | at your option, any later version of Perl 5 you may have available. | |
157 | ||
158 | ||
159 | =cut |