Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.83% |
46 / 48 |
|
86.67% |
13 / 15 |
CRAP | |
0.00% |
0 / 1 |
| Rating | |
95.74% |
45 / 47 |
|
86.67% |
13 / 15 |
28 | |
0.00% |
0 / 1 |
| getReadableRank | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getRankFromReadableRank | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
| getRankFromRating | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getReadableRankFromRating | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRankMinimalRating | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getRankMinimalRatingFromReadableRank | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRankMiddleRatingFromRank | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRankMiddleRatingFromReadableRank | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| beta | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| calculateRatingChange | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| ratingToXP | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| ratingToXPFloat | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| parseRatingOrReadableRank | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
| isReasonableRating | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| getReadableRankFromRatingWhenPossible | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
| 1 | <?php |
| 2 | |
| 3 | use function PHPUnit\Framework\isNull; |
| 4 | |
| 5 | App::uses('RatingParseException', 'Utility'); |
| 6 | |
| 7 | class Rating |
| 8 | { |
| 9 | public static function getReadableRank(int $rank): string |
| 10 | { |
| 11 | if ($rank <= 30) |
| 12 | return (string) (31 - $rank) . 'k'; |
| 13 | |
| 14 | return (string) ($rank - 30) . 'd'; |
| 15 | } |
| 16 | |
| 17 | public static function getRankFromReadableRank(string $readableRank): int |
| 18 | { |
| 19 | $suffix = substr($readableRank, -1); |
| 20 | $number = substr($readableRank, 0, -1); |
| 21 | if (!is_numeric($number)) |
| 22 | throw new RatingParseException($readableRank . " can't be parsed as go rank."); |
| 23 | if ($suffix == 'k') |
| 24 | return 31 - $number; |
| 25 | elseif ($suffix == 'd') |
| 26 | return 30 + $number; |
| 27 | else |
| 28 | throw new RatingParseException($readableRank . " can't be parsed as go rank."); |
| 29 | } |
| 30 | |
| 31 | public static function getRankFromRating(float $rating): int |
| 32 | { |
| 33 | // Internal number for rank representation better than the textual "18k" etc, so it is just going to be integer like this |
| 34 | // 30k = rating [-950, -850) = rank 1 |
| 35 | // "20k" = rating [ 50, 150) = rank 11 |
| 36 | // "10k" = rating [1050, 1150) = rank 21 |
| 37 | // 1k = rating [1950, 2050) = rank 30 |
| 38 | // 1d = rating [2050, 2150) = rank 31 |
| 39 | // 7d = rating [2650, 2750) = rank 37 7d is equivalent of pro rank, and then it is custom to have 30 points per rank |
| 40 | // 8d = rating [2750, 2780) = rank 38 |
| 41 | // 9d = rating [2780, 2810) = rank 39 |
| 42 | // 10d = rating [2780, 2810) = rank 40 |
| 43 | // 11d = rating [2810, 2840) = rank 41 |
| 44 | // ..... |
| 45 | if ($rating < 2750) |
| 46 | return (int) floor(max(($rating + 1050) / 100, 1)); |
| 47 | |
| 48 | return (int) floor(($rating - 2750) / 30) + 38; |
| 49 | } |
| 50 | |
| 51 | public static function getReadableRankFromRating(float $rating): string |
| 52 | { |
| 53 | return static::getReadableRank(static::getRankFromRating($rating)); |
| 54 | } |
| 55 | |
| 56 | public static function getRankMinimalRating(int $rank): float |
| 57 | { |
| 58 | if ($rank <= 38) |
| 59 | return 100 * $rank - 1050.0; |
| 60 | return ($rank - 38) * 30 + 2750.0; |
| 61 | } |
| 62 | |
| 63 | public static function getRankMinimalRatingFromReadableRank(string $readableRank): float |
| 64 | { |
| 65 | return Rating::getRankMinimalRating(Rating::getRankFromReadableRank($readableRank)); |
| 66 | } |
| 67 | |
| 68 | public static function getRankMiddleRatingFromRank(int $rank): float |
| 69 | { |
| 70 | return (Rating::getRankMinimalRating($rank) + Rating::getRankMinimalRating($rank + 1)) / 2; |
| 71 | } |
| 72 | |
| 73 | public static function getRankMiddleRatingFromReadableRank(string $readableRank): float |
| 74 | { |
| 75 | return Rating::getRankMiddleRatingFromRank(Rating::getRankFromReadableRank($readableRank)); |
| 76 | } |
| 77 | |
| 78 | private static function beta($rating) |
| 79 | { |
| 80 | return -7 * log(3300 - $rating); |
| 81 | } |
| 82 | |
| 83 | public static function calculateRatingChange($rating, $opponentRating, $result, $modifier) |
| 84 | { |
| 85 | $Se = 1.0 / (1.0 + exp(self::beta($opponentRating) - self::beta($rating))); |
| 86 | $con = pow(((3300 - $rating) / 200), 1.6); |
| 87 | $bonus = log(1 + exp((2300 - $rating) / 80)) / 5; |
| 88 | return $modifier * ($con * ($result - $Se) + $bonus); |
| 89 | } |
| 90 | |
| 91 | // changes should be reflected in util.js |
| 92 | public static function ratingToXP(float $rating, float $multiplier): int |
| 93 | { |
| 94 | $bla = intval(ceil(Rating::ratingToXPFloat($rating) * $multiplier)); |
| 95 | return intval(ceil(Rating::ratingToXPFloat($rating) * $multiplier)); |
| 96 | } |
| 97 | |
| 98 | public static function ratingToXPFloat(float $rating): float |
| 99 | { |
| 100 | if ($rating < 0) |
| 101 | return 1 + max(0, ($rating / 1000 + 0.9) * 3); |
| 102 | |
| 103 | // until 1200 rating, the old formula but with half of the values |
| 104 | if ($rating < 1200) |
| 105 | return max(10, pow($rating / 100, 1.55) - 6) / 2; |
| 106 | |
| 107 | // with higher ratings, it is important to have more aggressive exponential growth, |
| 108 | return (pow(($rating - 500) / 100, 2) - 10) / 2; |
| 109 | } |
| 110 | |
| 111 | public static function parseRatingOrReadableRank(string $input): float |
| 112 | { |
| 113 | if (is_numeric($input)) |
| 114 | { |
| 115 | if (!self::isReasonableRating((float) $input)) |
| 116 | throw new RatingParseException("Rating of " . $input . "isn't reasonable"); |
| 117 | return (float) $input; |
| 118 | } |
| 119 | return self::getRankMiddleRatingFromReadableRank($input); |
| 120 | } |
| 121 | |
| 122 | public static function isReasonableRating(float $rating) |
| 123 | { |
| 124 | return $rating >= -950 // 30k |
| 125 | && $rating < 3200; // the formula stops working at 3300 |
| 126 | } |
| 127 | |
| 128 | public static function getReadableRankFromRatingWhenPossible(?float $rating): string |
| 129 | { |
| 130 | if (is_null($rating)) |
| 131 | return ''; |
| 132 | $rank = self::getRankFromRating($rating); |
| 133 | if (self::getRankMiddleRatingFromRank($rank) == $rating) |
| 134 | return Rating::getReadableRank($rank); |
| 135 | return strval($rating); |
| 136 | } |
| 137 | } |