Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
46 / 48
86.67% covered (warning)
86.67%
13 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
Rating
95.74% covered (success)
95.74%
45 / 47
86.67% covered (warning)
86.67%
13 / 15
28
0.00% covered (danger)
0.00%
0 / 1
 getReadableRank
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getRankFromReadableRank
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 getRankFromRating
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getReadableRankFromRating
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRankMinimalRating
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getRankMinimalRatingFromReadableRank
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRankMiddleRatingFromRank
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRankMiddleRatingFromReadableRank
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 beta
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 calculateRatingChange
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 ratingToXP
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 ratingToXPFloat
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 parseRatingOrReadableRank
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 isReasonableRating
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getReadableRankFromRatingWhenPossible
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3use function PHPUnit\Framework\isNull;
4
5App::uses('RatingParseException', 'Utility');
6
7class 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}