Completed
Push — master ( d3b38e...70fe6c )
by Vladimir
05:42
created

PlayerEloCalculations::calculateEloDiff()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 27
Code Lines 16

Duplication

Lines 11
Ratio 40.74 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 11
loc 27
ccs 0
cts 0
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 16
nc 18
nop 5
crap 56
1
<?php
2
3
use Phinx\Migration\AbstractMigration;
4
5
class PlayerEloCalculations extends AbstractMigration
6
{
7
    /**
8
     * {@inheritdoc}
9
     */
10 1
    public function up()
11
    {
12 1
        $pageCountQuery = "
13 1
            SELECT
14 1
                COUNT(*)
15 1
            FROM
16
                matches
17
            WHERE
18
                team_a_players IS NOT NULL AND team_a_players != '' AND
19
                team_b_players IS NOT NULL AND team_b_players != '' AND 
20
                match_type = 'official' AND status = 'entered'
21
        ";
22
        $matchesQuery = "
23
            SELECT
24
                *
25 1
            FROM
26 1
                matches
27 1
            WHERE
28 1
                team_a_players IS NOT NULL AND team_a_players != '' AND
29 1
                team_b_players IS NOT NULL AND team_b_players != '' AND 
30 1
                match_type = 'official' AND status = 'entered' AND
31 1
                id > {id}
32 1
            ORDER BY
33
                `timestamp`
34
            LIMIT 1000
35 1
        ";
36 1
37
        $pageCount = ceil($this->fetchRow($pageCountQuery)[0] / 1000);
38 1
        $lastSeason = [];
39
        $lastID = 0;
40
41
        for ($i = 1; $i <= $pageCount; $i++) {
42
            $matches = $this->fetchAll(strtr($matchesQuery, [
43
                '{id}' => $lastID,
44
            ]));
45
46
            foreach ($matches as $match) {
47
                $timestamp = new \DateTime($match['timestamp']);
48
                $seasonInfo = [
49
                    'season' => $this->literalSeasonFromMonth($timestamp->format('n')),
50
                    'year' => (int)$timestamp->format('Y'),
51
                ];
52
53
                // Clear the model cache every season since Elos are cached internally
54
                if ($seasonInfo != $lastSeason) {
55
                    $lastSeason = $seasonInfo;
56
                }
57
58
                $teamA = explode(',', $match['team_a_players']);
59
                $teamB = explode(',', $match['team_b_players']);
60
61
                if (empty($teamA) || empty($teamB)) {
62
                    continue;
63
                }
64
65
                $getElo = function($n) use ($seasonInfo) {
66
                    $query = "
67
                        SELECT
68
                            elo_new
69
                        FROM
70
                            player_elo
71
                        WHERE
72
                            user_id = $n AND 
73
                            season_period = '{$seasonInfo['season']}' AND
74
                            season_year = {$seasonInfo['year']}
75
                        ORDER BY
76
                            match_id DESC
77
                        LIMIT 1
78
                    ";
79
80
                    $result = $this->fetchRow($query);
81
82
                    if (empty($result)) {
83
                        return 1200;
84
                    }
85
86
                    return $result[0];
87
                };
88
89
                $teamA_Avg = array_sum(array_map($getElo, $teamA)) / count($teamA);
90
                $teamB_Avg = array_sum(array_map($getElo, $teamB)) / count($teamB);
91
92
                $diff = self::calculateEloDiff(
93
                    $teamA_Avg, $teamB_Avg, $match['team_a_points'], $match['team_b_points'], $match['duration']
94 1
                );
95
96
                // We need to disable transactions so our Player::adjustElo() fxn won't hold up execution
97
                $this->getAdapter()->commitTransaction();
98
99
                $this->query("UPDATE matches SET player_elo_diff = {$diff} WHERE id = {$match['id']} LIMIT 1;");
100
101
                $walkFxn = function ($v, $k, $positive) use ($diff, $match, $seasonInfo, $getElo) {
102
                    $eloDiff = ($positive) ? $diff : -$diff;
103
                    $prevElo = $getElo($v);
104
                    $newElo = $prevElo + $eloDiff;
105
                    $query = "
106
                        INSERT INTO player_elo VALUES ($v, {$match['id']}, '{$seasonInfo['season']}', {$seasonInfo['year']}, $prevElo, $newElo)
107
                    ";
108
109
                    $this->execute($query);
110
                };
111
112
                array_walk($teamA, $walkFxn, true);
113
                array_walk($teamB, $walkFxn, false);
114
115
                $this->getAdapter()->beginTransaction();
116
117
                $lastSeason = $match['id'];
118
            }
119
        }
120
    }
121
122
    public function down()
123
    {
124
        $seasonElos = $this->table('player_elo');
125
        $seasonElos->truncate();
126
    }
127
128
    private function literalSeasonFromMonth($monthNumber)
129
    {
130
        $num = (int)$monthNumber;
131
132
        if (1 <= $num && $num <= 3) {
133
            return 'winter';
134
        } elseif (4 <= $num && $num <= 6) {
135
            return 'spring';
136
        } elseif (7 <= $num && $num <= 9) {
137
            return 'summer';
138
        }
139
140
        return 'fall';
141
    }
142
143
    private static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
144
    {
145
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
146 View Code Duplication
        if ($a_points > $b_points) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
            $diff = 50 * (1 - $prob);
148
        } elseif ($a_points == $b_points) {
149
            $diff = 50 * (0.5 - $prob);
150
        } else {
151
            $diff = 50 * (0 - $prob);
152
        }
153
154
        // Apply ELO modifiers from `config.yml`
155
        $durations = [
156
            '30' => 3/3,
157
            '20' => 2/3,
158
            '15' => 1/3,
159
        ];
160
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
161
162 View Code Duplication
        if (abs($diff) < 1 && $diff != 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
            // ELOs such as 0.75 should round up to 1...
164
            return ($diff > 0) ? 1 : -1;
165
        }
166
167
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
168
        return intval($diff);
169
    }
170
}
171