mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-10 17:28:40 +00:00
172 lines
4.3 KiB
PHP
172 lines
4.3 KiB
PHP
<?php
|
|
/**
|
|
* Jingga
|
|
*
|
|
* PHP Version 8.2
|
|
*
|
|
* @package phpOMS\Algorithm\Rating
|
|
* @copyright Dennis Eichhorn
|
|
* @license OMS License 2.0
|
|
* @version 1.0.0
|
|
* @link https://jingga.app
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
namespace phpOMS\Algorithm\Rating;
|
|
|
|
/**
|
|
* Elo rating calculation using Glicko-1
|
|
*
|
|
* @package phpOMS\Algorithm\Rating
|
|
* @license OMS License 2.0
|
|
* @link https://jingga.app
|
|
* @see https://en.wikipedia.org/wiki/Glicko_rating_system
|
|
* @see http://www.glicko.net/glicko/glicko.pdf
|
|
* @since 1.0.0
|
|
*/
|
|
final class Glicko1
|
|
{
|
|
/**
|
|
* Helper constant
|
|
*
|
|
* @latex Q = ln(10) / 400
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
private const Q = 0.00575646273;
|
|
|
|
/**
|
|
* Default elo to use for new players
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $DEFAULT_ELO = 1500;
|
|
|
|
/**
|
|
* Default rd to use for new players
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $DEFAULT_RD = 350;
|
|
|
|
/**
|
|
* C (constant) for RD caclulation
|
|
*
|
|
* This is used to adjust the RD value based on the time from the last time a player played a match
|
|
*
|
|
* @latex RD = min\left(\sqrt{RD_0^2 + c^2t}, 350\right)
|
|
*
|
|
* @see calculateC();
|
|
*
|
|
* @var float
|
|
* @since 1.0.0
|
|
*/
|
|
public float $DEFAULT_C = 34.6;
|
|
|
|
/**
|
|
* Lowest elo allowed
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $MIN_ELO = 100;
|
|
|
|
/**
|
|
* Lowest rd allowed
|
|
*
|
|
* @example 50 means that the player rating is probably between -100 / +100 of the current rating
|
|
*
|
|
* @var int
|
|
* @since 1.0.0
|
|
*/
|
|
public int $MIN_RD = 50;
|
|
|
|
/**
|
|
* Calculate the C value.
|
|
*
|
|
* This is only necessary if you change the DEFAULT_RD, want a different rating period or have significantly different average RD values.
|
|
*
|
|
* @param int $ratingPeriods Time without matches until the RD returns to the default RD
|
|
* @param int $avgRD Average RD
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function calculateC(int $ratingPeriods = 100, int $avgRD = 50) : void
|
|
{
|
|
$this->DEFAULT_C = \sqrt(($this->DEFAULT_RD ** 2 - $avgRD ** 2) / $ratingPeriods);
|
|
}
|
|
|
|
/**
|
|
* Calculate the glicko-1 elo
|
|
*
|
|
* @param int $elo Current player "elo"
|
|
* @param int $rdOld Current player deviation (RD)
|
|
* @param int $lastMatchDate Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
|
* @param int $matchDate Match date (usually day)
|
|
* @param int[] $oElo Opponent "elo"
|
|
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
|
* @param int[] $oRd Opponent deviation (RD)
|
|
*
|
|
* @return array{elo:int, rd:int}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
public function rating(
|
|
int $elo = 1500,
|
|
int $rdOld = 50,
|
|
int $lastMatchDate = 0,
|
|
int $matchDate = 0,
|
|
array $oElo = [],
|
|
array $s = [],
|
|
array $oRd = []
|
|
) : array
|
|
{
|
|
// Step 1:
|
|
$E = [];
|
|
$gRD = [];
|
|
|
|
$RD = \min(
|
|
350,
|
|
\max(
|
|
\sqrt(
|
|
$rdOld * $rdOld
|
|
+ $this->DEFAULT_C * $this->DEFAULT_C * \max(0, $matchDate - $lastMatchDate)
|
|
),
|
|
$this->MIN_RD
|
|
)
|
|
);
|
|
|
|
// Step 2:
|
|
foreach ($oElo as $id => $e) {
|
|
$gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI)));
|
|
$gRD[$id] = $gRD_t;
|
|
$E[$id] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
|
|
}
|
|
|
|
$d = 0;
|
|
foreach ($E as $id => $_) {
|
|
$d += $gRD[$id] * $gRD[$id] * $E[$id] * (1 - $E[$id]);
|
|
}
|
|
$d2 = 1 / (self::Q * self::Q * $d);
|
|
|
|
$r = 0;
|
|
foreach ($E as $id => $_) {
|
|
$r += $gRD[$id] * ($s[$id] - $E[$id]);
|
|
}
|
|
$r = $elo + self::Q / (1 / ($RD * $RD) + 1 / $d2) * $r;
|
|
|
|
// Step 3:
|
|
$RD_ = \sqrt(1 / (1 / ($RD * $RD) + 1 / $d2));
|
|
|
|
return [
|
|
'elo' => (int) \max((int) $r, $this->MIN_ELO),
|
|
'rd' => (int) \max($RD_, $this->MIN_RD),
|
|
];
|
|
}
|
|
}
|