mirror of
https://github.com/furyfire/trueskill.git
synced 2025-03-20 08:47:49 +00:00
First TwoPlayerTrueSkillCalculator unit test passed
This commit is contained in:
19
PHPSkills/Elo/EloRating.php
Normal file
19
PHPSkills/Elo/EloRating.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace Moserware\Skills\Elo;
|
||||
|
||||
require_once(dirname(__FILE__) . '/../Rating.php');
|
||||
|
||||
use Moserware\Skills\Rating;
|
||||
|
||||
/**
|
||||
* An Elo rating represented by a single number (mean).
|
||||
*/
|
||||
class EloRating extends Rating
|
||||
{
|
||||
public function __construct($rating)
|
||||
{
|
||||
parent::__construct($rating, 0);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
41
PHPSkills/Elo/FideEloCalculator.php
Normal file
41
PHPSkills/Elo/FideEloCalculator.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Moserware\Skills\Elo;
|
||||
|
||||
require_once(dirname(__FILE__) . "/TwoPlayerEloCalculator.php");
|
||||
require_once(dirname(__FILE__) . "/FideKFactor.php");
|
||||
|
||||
/** 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, $playerRating, $opponentRating)
|
||||
{
|
||||
$ratingDifference = $opponentRating - $playerRating;
|
||||
|
||||
return 1.0
|
||||
/
|
||||
(
|
||||
1.0 + pow(10.0, $ratingDifference / (2 * $gameInfo->getBeta()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
30
PHPSkills/Elo/FideKFactor.php
Normal file
30
PHPSkills/Elo/FideKFactor.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Moserware\Skills\Elo;
|
||||
|
||||
require_once(dirname(__FILE__) . "/KFactor.php");
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates someone who has played less than 30 games.
|
||||
*/
|
||||
class ProvisionalFideKFactor extends FideKFactor
|
||||
{
|
||||
public function getValueForRating($rating)
|
||||
{
|
||||
return 25;
|
||||
}
|
||||
}
|
||||
?>
|
26
PHPSkills/Elo/GaussianEloCalculator.php
Normal file
26
PHPSkills/Elo/GaussianEloCalculator.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Moserware\Skills\Elo;
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
22
PHPSkills/Elo/KFactor.php
Normal file
22
PHPSkills/Elo/KFactor.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Moserware\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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
105
PHPSkills/Elo/TwoPlayerEloCalculator.php
Normal file
105
PHPSkills/Elo/TwoPlayerEloCalculator.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Moserware\Skills\Elo;
|
||||
|
||||
require_once(dirname(__FILE__) . "/../PairwiseComparison.php");
|
||||
require_once(dirname(__FILE__) . "/../RankSorter.php");
|
||||
require_once(dirname(__FILE__) . "/../SkillCalculator.php");
|
||||
|
||||
require_once(dirname(__FILE__) . "/../PlayersRange.php");
|
||||
require_once(dirname(__FILE__) . "/../TeamsRange.php");
|
||||
|
||||
use Moserware\Skills\PairwiseComparison;
|
||||
use Moserware\Skills\RankSorter;
|
||||
use Moserware\Skills\SkillCalculator;
|
||||
use Moserware\Skills\SkillCalculatorSupportedOptions;
|
||||
|
||||
use Moserware\Skills\PlayersRange;
|
||||
use Moserware\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,
|
||||
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, $playerRating, $opponentRating);
|
||||
|
||||
public function calculateMatchQuality($gameInfo, array $teamsOfPlayerToRatings)
|
||||
{
|
||||
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(getPlayerWinProbability($gameInfo, $player1Rating, $player2Rating) - 0.5);
|
||||
return (0.5 - $deltaFrom50Percent) / 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
Reference in New Issue
Block a user