PHPUnit version bump and removed ELO

This commit is contained in:
Alex Wulf 2022-07-05 15:42:21 +02:00
parent 97609a3eac
commit afce4e56ca
14 changed files with 1285 additions and 718 deletions

@ -6,7 +6,7 @@
"php": "^8.1" "php": "^8.1"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.0" "phpunit/phpunit": "^9.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

1674
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -1,21 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
backupStaticAttributes="false" <coverage>
bootstrap="vendor/autoload.php" <include>
colors="true" <directory suffix=".php">src/</directory>
convertErrorsToExceptions="true" </include>
convertNoticesToExceptions="true" </coverage>
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites> <testsuites>
<testsuite name="PHPSkills Test Suite"> <testsuite name="PHPSkills Test Suite">
<directory>./tests/</directory> <directory>./tests/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit> </phpunit>

@ -1,14 +0,0 @@
<?php namespace DNW\Skills\Elo;
use DNW\Skills\Rating;
/**
* An Elo rating represented by a single number (mean).
*/
class EloRating extends Rating
{
public function __construct($rating)
{
parent::__construct($rating, 0);
}
}

@ -1,37 +0,0 @@
<?php namespace DNW\Skills\Elo;
use DNW\Skills\GameInfo;
/**
* Including Elo's scheme as a simple comparison.
* See http://en.wikipedia.org/wiki/Elo_rating_system#Theory
* for more details
*/
class FideEloCalculator extends TwoPlayerEloCalculator
{
public function __construct(FideKFactor $kFactor)
{
parent::__construct($kFactor);
}
public static function createWithDefaultKFactor()
{
return new FideEloCalculator(new FideKFactor());
}
public static function createWithProvisionalKFactor()
{
return new FideEloCalculator(new ProvisionalFideKFactor());
}
public function getPlayerWinProbability(GameInfo $gameInfo, $playerRating, $opponentRating)
{
$ratingDifference = $opponentRating - $playerRating;
return 1.0
/
(
1.0 + pow(10.0, $ratingDifference / (2 * $gameInfo->getBeta()))
);
}
}

@ -1,14 +0,0 @@
<?php namespace DNW\Skills\Elo;
// see http://ratings.fide.com/calculator_rtd.phtml for details
class FideKFactor extends KFactor
{
public function getValueForRating($rating)
{
if ($rating < 2400) {
return 15;
}
return 10;
}
}

@ -1,27 +0,0 @@
<?php namespace DNW\Skills\Elo;
use DNW\Skills\GameInfo;
use DNW\Skills\Numerics\GaussianDistribution;
class GaussianEloCalculator extends TwoPlayerEloCalculator
{
// From the paper
const STABLE_KFACTOR = 24;
public function __construct()
{
parent::__construct(new KFactor(self::STABLE_KFACTOR));
}
public function getPlayerWinProbability(GameInfo $gameInfo, $playerRating, $opponentRating)
{
$ratingDifference = $playerRating - $opponentRating;
// See equation 1.1 in the TrueSkill paper
return GaussianDistribution::cumulativeTo(
$ratingDifference
/
(sqrt(2) * $gameInfo->getBeta())
);
}
}

@ -1,18 +0,0 @@
<?php namespace DNW\Skills\Elo;
class KFactor
{
const DEFAULT_KFACTOR = 24;
private $_value;
public function __construct($exactKFactor = self::DEFAULT_KFACTOR)
{
$this->_value = $exactKFactor;
}
public function getValueForRating($rating)
{
return $this->_value;
}
}

@ -1,12 +0,0 @@
<?php namespace DNW\Skills\Elo;
/**
* Indicates someone who has played less than 30 games.
*/
class ProvisionalFideKFactor extends FideKFactor
{
public function getValueForRating($rating)
{
return 25;
}
}

@ -1,93 +0,0 @@
<?php namespace DNW\Skills\Elo;
use Exception;
use DNW\Skills\GameInfo;
use DNW\Skills\PairwiseComparison;
use DNW\Skills\RankSorter;
use DNW\Skills\SkillCalculator;
use DNW\Skills\SkillCalculatorSupportedOptions;
use DNW\Skills\PlayersRange;
use DNW\Skills\TeamsRange;
abstract class TwoPlayerEloCalculator extends SkillCalculator
{
protected $_kFactor;
protected function __construct(KFactor $kFactor)
{
parent::__construct(SkillCalculatorSupportedOptions::NONE, TeamsRange::exactly(2), PlayersRange::exactly(1));
$this->_kFactor = $kFactor;
}
public function calculateNewRatings(GameInfo $gameInfo, array $teamsOfPlayerToRatings, array $teamRanks)
{
$this->validateTeamCountAndPlayersCountPerTeam($teamsOfPlayerToRatings);
RankSorter::sort($teamsOfPlayerToRatings, $teamRanks);
$result = array();
$isDraw = ($teamRanks[0] === $teamRanks[1]);
$team1 = $teamsOfPlayerToRatings[0];
$team2 = $teamsOfPlayerToRatings[1];
$player1 = each($team1);
$player2 = each($team2);
$player1Rating = $player1["value"]->getMean();
$player2Rating = $player2["value"]->getMean();
$result[$player1["key"]] = $this->calculateNewRating($gameInfo, $player1Rating, $player2Rating, $isDraw ? PairwiseComparison::DRAW : PairwiseComparison::WIN);
$result[$player2["key"]] = $this->calculateNewRating($gameInfo, $player2Rating, $player1Rating, $isDraw ? PairwiseComparison::DRAW : PairwiseComparison::LOSE);
return $result;
}
protected function calculateNewRating($gameInfo, $selfRating, $opponentRating, $selfToOpponentComparison)
{
$expectedProbability = $this->getPlayerWinProbability($gameInfo, $selfRating, $opponentRating);
$actualProbability = $this->getScoreFromComparison($selfToOpponentComparison);
$k = $this->_kFactor->getValueForRating($selfRating);
$ratingChange = $k * ($actualProbability - $expectedProbability);
$newRating = $selfRating + $ratingChange;
return new EloRating($newRating);
}
private static function getScoreFromComparison($comparison)
{
switch ($comparison)
{
case PairwiseComparison::WIN:
return 1;
case PairwiseComparison::DRAW:
return 0.5;
case PairwiseComparison::LOSE:
return 0;
default:
throw new Exception("Unexpected comparison");
}
}
public abstract function getPlayerWinProbability(GameInfo $gameInfo, $playerRating, $opponentRating);
public function calculateMatchQuality(GameInfo $gameInfo, array $teamsOfPlayerToRatings)
{
$this->validateTeamCountAndPlayersCountPerTeam($teamsOfPlayerToRatings);
$team1 = $teamsOfPlayerToRatings[0];
$team2 = $teamsOfPlayerToRatings[1];
$player1 = $team1[0];
$player2 = $team2[0];
$player1Rating = $player1[1]->getMean();
$player2Rating = $player2[1]->getMean();
$ratingDifference = $player1Rating - $player2Rating;
// The TrueSkill paper mentions that they used s1 - s2 (rating difference) to
// determine match quality. I convert that to a percentage as a delta from 50%
// using the cumulative density function of the specific curve being used
$deltaFrom50Percent = abs($this->getPlayerWinProbability($gameInfo, $player1Rating, $player2Rating) - 0.5);
return (0.5 - $deltaFrom50Percent) / 0.5;
}
}

@ -57,10 +57,6 @@ class Player implements ISupportPartialPlay, ISupportPartialUpdate
public function __toString() public function __toString()
{ {
if ($this->_Id != null) {
return (string)$this->_Id; return (string)$this->_Id;
} }
return parent::__toString();
}
} }

@ -1,43 +0,0 @@
<?php namespace DNW\Skills\Tests\Elo;
use DNW\Skills\Elo\EloRating;
use DNW\Skills\Elo\FideEloCalculator;
use DNW\Skills\GameInfo;
use DNW\Skills\PairwiseComparison;
use DNW\Skills\Tests\TestCase;
class EloAssert
{
const ERROR_TOLERANCE = 0.1;
public static function assertChessRating(
TestCase $testClass,
FideEloCalculator $twoPlayerEloCalculator,
$player1BeforeRating,
$player2BeforeRating,
$player1Result,
$player1AfterRating,
$player2AfterRating)
{
$player1 = "Player1";
$player2 = "Player2";
$teams = array(
array($player1 => new EloRating($player1BeforeRating)),
array($player2 => new EloRating($player2BeforeRating))
);
$chessGameInfo = new GameInfo(1200, 0, 200);
$ranks = PairwiseComparison::getRankFromComparison($player1Result);
$result = $twoPlayerEloCalculator->calculateNewRatings(
$chessGameInfo,
$teams,
$ranks
);
$testClass->assertEquals($player1AfterRating, $result[$player1]->getMean(), '', self::ERROR_TOLERANCE);
$testClass->assertEquals($player2AfterRating, $result[$player2]->getMean(), '', self::ERROR_TOLERANCE);
}
}

@ -1,33 +0,0 @@
<?php namespace DNW\Skills\Tests\Elo;
use DNW\Skills\Elo\FideEloCalculator;
use DNW\Skills\Elo\ProvisionalFideKFactor;
use DNW\Skills\PairwiseComparison;
use DNW\Skills\Tests\TestCase;
class FideEloCalculatorTest extends TestCase
{
public function testFideProvisionalEloCalculator()
{
// verified against http://ratings.fide.com/calculator_rtd.phtml
$calc = new FideEloCalculator(new ProvisionalFideKFactor());
EloAssert::assertChessRating($this, $calc, 1200, 1500, PairwiseComparison::WIN, 1221.25, 1478.75);
EloAssert::assertChessRating($this, $calc, 1200, 1500, PairwiseComparison::DRAW, 1208.75, 1491.25);
EloAssert::assertChessRating($this, $calc, 1200, 1500, PairwiseComparison::LOSE, 1196.25, 1503.75);
}
public function testFideNonProvisionalEloCalculator()
{
// verified against http://ratings.fide.com/calculator_rtd.phtml
$calc = FideEloCalculator::createWithDefaultKFactor();
EloAssert::assertChessRating($this, $calc, 1200, 1200, PairwiseComparison::WIN, 1207.5, 1192.5);
EloAssert::assertChessRating($this, $calc, 1200, 1200, PairwiseComparison::DRAW, 1200, 1200);
EloAssert::assertChessRating($this, $calc, 1200, 1200, PairwiseComparison::LOSE, 1192.5, 1207.5);
EloAssert::assertChessRating($this, $calc, 2600, 2500, PairwiseComparison::WIN, 2603.6, 2496.4);
EloAssert::assertChessRating($this, $calc, 2600, 2500, PairwiseComparison::DRAW, 2598.6, 2501.4);
EloAssert::assertChessRating($this, $calc, 2600, 2500, PairwiseComparison::LOSE, 2593.6, 2506.4);
}
}

@ -1,5 +1,5 @@
<?php namespace DNW\Skills\Tests; <?php namespace DNW\Skills\Tests;
class TestCase extends \PHPUnit_Framework_TestCase class TestCase extends \PHPUnit\Framework\TestCase
{ {
} }