|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Alcalyn\Elo; |
|
4
|
|
|
|
|
5
|
|
|
use Alcalyn\Elo\Exception\EloCoefficientException; |
|
6
|
|
|
|
|
7
|
|
|
class EloSystem |
|
8
|
|
|
{ |
|
9
|
|
|
/** |
|
10
|
|
|
* F Factor. Determines how quickly elo scores change. |
|
11
|
|
|
* Default is 16 |
|
12
|
|
|
* |
|
13
|
|
|
* @var integer |
|
14
|
|
|
*/ |
|
15
|
|
|
private $kFactor; |
|
16
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* Defines the range of elo. |
|
19
|
|
|
* Default is 400 |
|
20
|
|
|
* |
|
21
|
|
|
* @var integer |
|
22
|
|
|
*/ |
|
23
|
|
|
private $interval; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* Defines approximatively the power between intervals. |
|
27
|
|
|
* Default is 10 |
|
28
|
|
|
* |
|
29
|
|
|
* @var integer |
|
30
|
|
|
*/ |
|
31
|
|
|
private $pow; |
|
32
|
|
|
|
|
33
|
|
|
/** |
|
34
|
|
|
* Constructor |
|
35
|
|
|
* |
|
36
|
|
|
* @param integer $kFactor |
|
37
|
|
|
* @param integer $interval |
|
38
|
|
|
* @param integer $pow |
|
39
|
|
|
*/ |
|
40
|
|
|
public function __construct($kFactor = 16, $interval = 400, $pow = 10) |
|
41
|
|
|
{ |
|
42
|
|
|
$this->kFactor = $kFactor; |
|
43
|
|
|
$this->interval = $interval; |
|
44
|
|
|
$this->pow = $pow; |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Calculate new elo scores |
|
49
|
|
|
* |
|
50
|
|
|
* @param double $elo0 elo score of player 0 |
|
51
|
|
|
* @param double $elo1 elo score of player 1 |
|
52
|
|
|
* @param double $win win coef for player 0. Set 1 to say that player 0 won |
|
53
|
|
|
* @param double $reliability0 elo reliability for player 0 |
|
54
|
|
|
* @param double $reliability1 elo reliability for player 1 |
|
55
|
|
|
* @param integer $kFactor0 override k factor for player 0 |
|
56
|
|
|
* @param integer $kFactor1 override k factor for player 1 |
|
57
|
|
|
* |
|
58
|
|
|
* @return double[] with new elo score at indexes 0 and 1 for player 0 and 1 |
|
59
|
|
|
*/ |
|
60
|
|
|
public function calculate($elo0, $elo1, $win, $reliability0 = 1.0, $reliability1 = 1.0, $kFactor0 = null, $kFactor1 = null) |
|
61
|
|
|
{ |
|
62
|
|
|
self::checkCoef($reliability0, 'reliability0'); |
|
63
|
|
|
self::checkCoef($reliability1, 'reliability1'); |
|
64
|
|
|
self::checkCoef($win, 'win'); |
|
65
|
|
|
|
|
66
|
|
|
$kFactor0 = (null === $kFactor0) ? $this->kFactor : $kFactor0; |
|
67
|
|
|
$kFactor1 = (null === $kFactor1) ? $this->kFactor : $kFactor1; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Calculate probability 0 have to beat 1 |
|
71
|
|
|
*/ |
|
72
|
|
|
$proba = $this->proba($elo0, $elo1); |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* Calculate elo changement |
|
76
|
|
|
*/ |
|
77
|
|
|
$eloUpdate0 = $win - $proba; |
|
78
|
|
|
$eloUpdate1 = -$eloUpdate0; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* Calculate local reliability to avoid 0 and 0 reliability for new players |
|
82
|
|
|
* (if two new players have 0 and 0.1 reliability, they are rectified to 0.9 and 1) |
|
83
|
|
|
*/ |
|
84
|
|
|
self::rectifyReliabilityCoefs($reliability0, $reliability1); |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Apply coefs K-factor and reliability of each other |
|
88
|
|
|
*/ |
|
89
|
|
|
$eloUpdate0 *= $kFactor0 * $reliability1 ; |
|
90
|
|
|
$eloUpdate1 *= $kFactor1 * $reliability0 ; |
|
91
|
|
|
|
|
92
|
|
|
return array($elo0 + $eloUpdate0, $elo1 + $eloUpdate1); |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* Player 0 beat player 1 |
|
97
|
|
|
* |
|
98
|
|
|
* @param double $elo0 elo score of player 0 |
|
99
|
|
|
* @param double $elo1 elo score of player 1 |
|
100
|
|
|
* @param double $reliability0 elo reliability for player 0 |
|
101
|
|
|
* @param double $reliability1 elo reliability for player 1 |
|
102
|
|
|
* |
|
103
|
|
|
* @return double[] with new elo score at indexes 0 and 1 for player 0 and 1 |
|
104
|
|
|
*/ |
|
105
|
|
|
public function win($elo0, $elo1, $reliability0 = 1.0, $reliability1 = 1.0, $kFactor0 = null, $kFactor1 = null) |
|
106
|
|
|
{ |
|
107
|
|
|
return $this->calculate($elo0, $elo1, 1, $reliability0, $reliability1, $kFactor0, $kFactor1); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* Player 0 lose against player 1 |
|
112
|
|
|
* |
|
113
|
|
|
* @param double $elo0 elo score of player 0 |
|
114
|
|
|
* @param double $elo1 elo score of player 1 |
|
115
|
|
|
* @param double $reliability0 elo reliability for player 0 |
|
116
|
|
|
* @param double $reliability1 elo reliability for player 1 |
|
117
|
|
|
* |
|
118
|
|
|
* @return double[] with new elo score at indexes 0 and 1 for player 0 and 1 |
|
119
|
|
|
*/ |
|
120
|
|
|
public function lose($elo0, $elo1, $reliability0 = 1.0, $reliability1 = 1.0, $kFactor0 = null, $kFactor1 = null) |
|
121
|
|
|
{ |
|
122
|
|
|
return $this->calculate($elo0, $elo1, 0, $reliability0, $reliability1, $kFactor0, $kFactor1); |
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Player 0 and player 1 have made a draw game |
|
127
|
|
|
* |
|
128
|
|
|
* @param double $elo0 elo score of player 0 |
|
129
|
|
|
* @param double $elo1 elo score of player 1 |
|
130
|
|
|
* @param double $reliability0 elo reliability for player 0 |
|
131
|
|
|
* @param double $reliability1 elo reliability for player 1 |
|
132
|
|
|
* |
|
133
|
|
|
* @return double[] with new elo score at indexes 0 and 1 for player 0 and 1 |
|
134
|
|
|
*/ |
|
135
|
|
|
public function draw($elo0, $elo1, $reliability0 = 1.0, $reliability1 = 1.0, $kFactor0 = null, $kFactor1 = null) |
|
136
|
|
|
{ |
|
137
|
|
|
return $this->calculate($elo0, $elo1, 0.5, $reliability0, $reliability1, $kFactor0, $kFactor1); |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* Return probability rate that $elo0 beat $elo1 |
|
142
|
|
|
* |
|
143
|
|
|
* @param double $elo0 |
|
144
|
|
|
* @param double $elo1 |
|
145
|
|
|
* |
|
146
|
|
|
* @return double |
|
147
|
|
|
*/ |
|
148
|
|
|
public function proba($elo0, $elo1) |
|
149
|
|
|
{ |
|
150
|
|
|
return 1 / (1 + pow($this->pow, ($elo1 - $elo0) / $this->interval)) ; |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
/** |
|
154
|
|
|
* Increase coefficients the same value so one of them reaches 1 |
|
155
|
|
|
* |
|
156
|
|
|
* @param double $coef0 |
|
157
|
|
|
* @param double $coef1 |
|
158
|
|
|
*/ |
|
159
|
|
|
private static function rectifyReliabilityCoefs(&$coef0, &$coef1) |
|
160
|
|
|
{ |
|
161
|
|
|
$reliabilityRectification = 1 - max($coef0, $coef1); |
|
162
|
|
|
|
|
163
|
|
|
$coef0 += $reliabilityRectification; |
|
164
|
|
|
$coef1 += $reliabilityRectification; |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
/** |
|
168
|
|
|
* Check if $coef is in range [0;1] |
|
169
|
|
|
* |
|
170
|
|
|
* @param double $coef |
|
171
|
|
|
* @param string $variableName |
|
172
|
|
|
*/ |
|
173
|
|
|
private static function checkCoef($coef, $variableName) |
|
174
|
|
|
{ |
|
175
|
|
|
if (($coef < 0) || ($coef > 1)) { |
|
176
|
|
|
throw new EloCoefficientException($coef, $variableName); |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
} |
|
180
|
|
|
|