Completed
Push — fm-matches ( 7fc64f...67f3c2 )
by Vladimir
05:40
created

Match::getMatchType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * This file contains functionality relating to the official matches played in the league
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
use BZIon\Model\Column\Timestamp;
9
10
/**
11
 * A match played between two teams
12
 * @package    BZiON\Models
13
 */
14
class Match extends UrlModel implements NamedModel
15
{
16
    const OFFICIAL = "official";
17
    const SPECIAL  = "special";
18
    const FUN      = "fm";
19
20
    use Timestamp;
21
22
    /**
23
     * The ID of the first team of the match
24
     * @var int
25
     */
26
    protected $team_a;
27
28
    /**
29
     * The ID of the second team of the match
30
     * @var int
31
     */
32
    protected $team_b;
33
34
    /**
35
     * The match points (usually the number of flag captures) Team A scored
36
     * @var int
37
     */
38
    protected $team_a_points;
39
40
    /**
41
     * The match points Team B scored
42
     * @var int
43
     */
44
    protected $team_b_points;
45
46
    /**
47
     * The BZIDs of players part of Team A who participated in the match, separated by commas
48
     * @var string
49
     */
50
    protected $team_a_players;
51
52
    /**
53
     * The BZIDs of players part of Team B who participated in the match, separated by commas
54
     * @var string
55
     */
56
    protected $team_b_players;
57
58
    /**
59
     * The ELO score of Team A after the match
60
     * @var int
61
     */
62
    protected $team_a_elo_new;
63
64
    /**
65
     * The ELO score of Team B after the match
66
     * @var int
67
     */
68
    protected $team_b_elo_new;
69
70
    /**
71
     * The map ID used in the match if the league supports more than one map
72
     * @var int
73
     */
74
    protected $map;
75
76
    /**
77
     * The type of match that occurred. Valid options: official, fm, special
78
     *
79
     * @var string
80
     */
81
    protected $match_type;
82
83
    /**
84
     * A JSON string of events that happened during a match, such as captures and substitutions
85
     * @var string
86
     */
87
    protected $match_details;
88
89
    /**
90
     * The port of the server where the match took place
91
     * @var int
92
     */
93
    protected $port;
94
95
    /**
96
     * The server location of there the match took place
97
     * @var string
98
     */
99
    protected $server;
100
101
    /**
102
     * The file name of the replay file of the match
103
     * @var string
104
     */
105
    protected $replay_file;
106
107
    /**
108
     * The absolute value of the ELO score difference
109
     * @var int
110
     */
111
    protected $elo_diff;
112
113
    /**
114
     * The timestamp representing when the match information was last updated
115
     * @var TimeDate
116
     */
117
    protected $updated;
118
119
    /**
120
     * The duration of the match in minutes
121
     * @var int
122
     */
123
    protected $duration;
124
125
    /**
126
     * The ID of the person (i.e. referee) who last updated the match information
127
     * @var string
128
     */
129
    protected $entered_by;
130
131
    /**
132
     * The status of the match. Can be 'entered', 'disabled', 'deleted' or 'reported'
133
     * @var string
134
     */
135
    protected $status;
136
137
    /**
138
     * The name of the database table used for queries
139
     */
140
    const TABLE = "matches";
141
142
    const CREATE_PERMISSION = Permission::ENTER_MATCH;
143
    const EDIT_PERMISSION = Permission::EDIT_MATCH;
144
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_MATCH;
145
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_MATCH;
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 9
    protected function assignResult($match)
151
    {
152 9
        $this->team_a = $match['team_a'];
153 9
        $this->team_b = $match['team_b'];
154 9
        $this->team_a_points = $match['team_a_points'];
155 9
        $this->team_b_points = $match['team_b_points'];
156 9
        $this->team_a_players = $match['team_a_players'];
157 9
        $this->team_b_players = $match['team_b_players'];
158 9
        $this->team_a_elo_new = $match['team_a_elo_new'];
159 9
        $this->team_b_elo_new = $match['team_b_elo_new'];
160 9
        $this->map = $match['map'];
161 9
        $this->match_type = $match['match_type'];
162 9
        $this->match_details = $match['match_details'];
163 9
        $this->port = $match['port'];
164 9
        $this->server = $match['server'];
165 9
        $this->replay_file = $match['replay_file'];
166 9
        $this->elo_diff = $match['elo_diff'];
167 9
        $this->timestamp = TimeDate::fromMysql($match['timestamp']);
168 9
        $this->updated = TimeDate::fromMysql($match['updated']);
169 9
        $this->duration = $match['duration'];
170 9
        $this->entered_by = $match['entered_by'];
171 9
        $this->status = $match['status'];
172 9
    }
173
174
    /**
175
     * Get the name of the route that shows the object
176
     * @param  string $action The route's suffix
177
     * @return string
178
     */
179 1
    public static function getRouteName($action = 'show')
180
    {
181 1
        return "match_$action";
182
    }
183
184
    /**
185
     * Get a one word description of a match relative to a team (i.e. win, loss, or draw)
186
     *
187
     * @param int $teamID The team ID we want the noun for
188
     *
189
     * @return string Either "win", "loss", or "draw" relative to the team
190
     */
191 1
    public function getMatchDescription($teamID)
192
    {
193 1
        if ($this->getScore($teamID) > $this->getOpponentScore($teamID)) {
194 1
            return "win";
195 1
        } elseif ($this->getScore($teamID) < $this->getOpponentScore($teamID)) {
196 1
            return "loss";
197
        }
198
199 1
        return "tie";
200
    }
201
202
    /**
203
     * Get a one letter description of a match relative to a team (i.e. W, L, or T)
204
     *
205
     * @param int $teamID The team ID we want the noun for
206
     *
207
     * @return string Either "W", "L", or "T" relative to the team
208
     */
209 1
    public function getMatchLetter($teamID)
210
    {
211 1
        return strtoupper(substr($this->getMatchDescription($teamID), 0, 1));
212
    }
213
214
    /**
215
     * Get the score of a specific team
216
     *
217
     * @param int|Team $teamID The team we want the score for
218
     *
219
     * @return int The score that team received
220
     */
221 2 View Code Duplication
    public function getScore($teamID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
222
    {
223 2
        if ($teamID instanceof Team) {
224
            // Oh no! The caller gave us a Team model instead of an ID!
225 1
            $teamID = $teamID->getId();
226
        }
227
228 2
        if ($this->getTeamA()->getId() == $teamID) {
229 2
            return $this->getTeamAPoints();
230
        }
231
232 1
        return $this->getTeamBPoints();
233
    }
234
235
    /**
236
     * Get the score of the opponent relative to a team
237
     *
238
     * @param int $teamID The opponent of the team we want the score for
239
     *
240
     * @return int The score of the opponent
241
     */
242 2 View Code Duplication
    public function getOpponentScore($teamID)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
243
    {
244 2
        if ($teamID instanceof Team) {
245
            $teamID = $teamID->getId();
246
        }
247
248 2
        if ($this->getTeamA()->getId() != $teamID) {
249 1
            return $this->getTeamAPoints();
250
        }
251
252 2
        return $this->getTeamBPoints();
253
    }
254
255
    /**
256
     * Get the opponent of a match relative to a team ID
257
     *
258
     * @param int $teamID The team who is known in a match
259
     *
260
     * @return Team The opponent team
261
     */
262 8
    public function getOpponent($teamID)
263
    {
264 8
        if ($this->getTeamA()->getId() == $teamID) {
265 6
            return $this->getTeamB();
266
        }
267
268 4
        return $this->getTeamA();
269
    }
270
271
    /**
272
     * Get the timestamp of the last update of the match
273
     *
274
     * @return TimeDate The match's update timestamp
275
     */
276 1
    public function getUpdated()
277
    {
278 1
        return $this->updated->copy();
279
    }
280
281
    /**
282
     * Set the timestamp of the match
283
     *
284
     * @param  mixed The match's new timestamp
285
     * @return $this
286
     */
287
    public function setTimestamp($timestamp)
288
    {
289
        $this->timestamp = TimeDate::from($timestamp);
290
        $this->update("timestamp", $this->timestamp->toMysql(), "s");
291
292
        return $this;
293
    }
294
295
    /**
296
     * Get the first team involved in the match
297
     * @return Team Team A's id
298
     */
299 9
    public function getTeamA()
300
    {
301 9
        return Team::get($this->team_a);
302
    }
303
304
    /**
305
     * Get the second team involved in the match
306
     * @return Team Team B's id
307
     */
308 9
    public function getTeamB()
309
    {
310 9
        return Team::get($this->team_b);
311
    }
312
313
    /**
314
     * Get the list of players on Team A who participated in this match
315
     * @return Player[]|null Returns null if there were no players recorded for this match
316
     */
317 1
    public function getTeamAPlayers()
318
    {
319 1
        return $this->parsePlayers($this->team_a_players);
320
    }
321
322
    /**
323
     * Get the list of players on Team B who participated in this match
324
     * @return Player[]|null Returns null if there were no players recorded for this match
325
     */
326 1
    public function getTeamBPlayers()
327
    {
328 1
        return $this->parsePlayers($this->team_b_players);
329
    }
330
331
    /**
332
     * Get the list of players for a team in a match
333
     * @param  Team|int The team or team ID
334
     * @return Player[]|null Returns null if there were no players recorded for this match
335
     */
336 1
    public function getPlayers($team)
337
    {
338 1
        if ($team instanceof Team) {
339 1
            $team = $team->getId();
340
        }
341
342 1
        if ($team == $this->team_a) {
343 1
            return $this->getTeamAPlayers();
344 1
        } elseif ($team == $this->team_b) {
345 1
            return $this->getTeamBPlayers();
346
        }
347
348
        return null;
349
    }
350
351
    /**
352
     * Set the players of the match's teams
353
     *
354
     * @param int[] $teamAPlayers An array of player IDs
355
     * @param int[] $teamBPlayers An array of player IDs
356
     * @return self
357
     */
358
    public function setTeamPlayers($teamAPlayers = array(), $teamBPlayers = array())
359
    {
360
        $this->updateProperty($this->team_a_players, "team_a_players", implode(',', $teamAPlayers), "s");
361
        $this->updateProperty($this->team_b_players, "team_b_players", implode(',', $teamBPlayers), "s");
362
363
        return $this;
364
    }
365
366
    /**
367
     * Get an array of players based on a string representation
368
     * @param string $playerString
369
     * @return Player[]|null Returns null if there were no players recorded for this match
370
     */
371 1
    private static function parsePlayers($playerString)
372
    {
373 1
        if ($playerString == null) {
374 1
            return null;
375
        }
376
377
        return Player::arrayIdToModel(explode(",", $playerString));
378
    }
379
380
    /**
381
     * Get the first team's points
382
     * @return int Team A's points
383
     */
384 4
    public function getTeamAPoints()
385
    {
386 4
        return $this->team_a_points;
387
    }
388
389
    /**
390
     * Get the second team's points
391
     * @return int Team B's points
392
     */
393 4
    public function getTeamBPoints()
394
    {
395 4
        return $this->team_b_points;
396
    }
397
398
    /**
399
     * Set the match team points
400
     *
401
     * @param  int $teamAPoints Team A's points
402
     * @param  int $teamBPoints Team B's points
403
     * @return self
404
     */
405
    public function setTeamPoints($teamAPoints, $teamBPoints)
406
    {
407
        $this->updateProperty($this->team_a_points, "team_a_points", $teamAPoints, "i");
408
        $this->updateProperty($this->team_b_points, "team_b_points", $teamBPoints, "i");
409
410
        return $this;
411
    }
412
413
    /**
414
     * Get the ELO difference applied to each team's old ELO
415
     * @return int The ELO difference
416
     */
417 7
    public function getEloDiff()
418
    {
419 7
        return abs($this->elo_diff);
420
    }
421
422
    /**
423
     * Get the first team's new ELO
424
     * @return int Team A's new ELO
425
     */
426 7
    public function getTeamAEloNew()
427
    {
428 7
        return $this->team_a_elo_new;
429
    }
430
431
    /**
432
     * Get the second team's new ELO
433
     * @return int Team B's new ELO
434
     */
435 7
    public function getTeamBEloNew()
436
    {
437 7
        return $this->team_b_elo_new;
438
    }
439
440
    /**
441
     * Get the first team's old ELO
442
     * @return int
443
     */
444 6
    public function getTeamAEloOld()
445
    {
446 6
        return $this->team_a_elo_new - $this->elo_diff;
447
    }
448
449
    /**
450
     * Get the second team's old ELO
451
     * @return int
452
     */
453 6
    public function getTeamBEloOld()
454
    {
455 6
        return $this->team_b_elo_new + $this->elo_diff;
456
    }
457
458
    /**
459
     * Get the team's new ELO
460
     * @param  Team $team The team whose new ELO to return
461
     * @return int|null   The new ELO, or null if the team provided has not
462
     *                    participated in the match
463
     */
464 1
    public function getTeamEloNew(Team $team)
465
    {
466 1
        if ($team->getId() === $this->team_a) {
467 1
            return $this->getTeamAEloNew();
468 1
        } elseif ($team->getId() === $this->team_b) {
469 1
            return $this->getTeamBEloNew();
470
        }
471
472
        return null;
473
    }
474
475
    /**
476
     * Get the team's old ELO
477
     * @param  Team $team The team whose old ELO to return
478
     * @return int|null   The old ELO, or null if the team provided has not
479
     *                    participated in the match
480
     */
481 1
    public function getTeamEloOld(Team $team)
482
    {
483 1
        if ($team->getId() === $this->team_a) {
484 1
            return $this->getTeamAEloOld();
485 1
        } elseif ($team->getId() === $this->team_b) {
486 1
            return $this->getTeamBEloOld();
487
        }
488
489
        return null;
490
    }
491
492
    /**
493
     * Get the map where the match was played on
494
     * @return Map Returns an invalid map if no map was found
495
     */
496 1
    public function getMap()
497
    {
498 1
        return Map::get($this->map);
499
    }
500
501
    /**
502
     * Set the map where the match was played
503
     * @param  int $map The ID of the map
504
     * @return self
505
     */
506
    public function setMap($map)
507
    {
508
        $this->updateProperty($this->map, "map", $map, "s");
509
    }
510
511
    /**
512
     * Get the match type
513
     *
514
     * @return string 'official', 'fm', or 'special'
515
     */
516
    public function getMatchType()
517
    {
518
        return $this->match_type;
519
    }
520
521
    /**
522
     * Set the match type
523
     *
524
     * @param  string $matchType A valid match type; official, fm, special
525
     *
526
     * @return static
527
     */
528
    public function setMatchType($matchType)
529
    {
530
        return $this->updateProperty($this->match_type, "match_type", $matchType, 's');
531
    }
532
533
    /**
534
     * Get a JSON decoded array of events that occurred during the match
535
     * @return mixed|null Returns null if there were no events recorded for the match
536
     */
537
    public function getMatchDetails()
538
    {
539
        return json_decode($this->match_details);
540
    }
541
542
    /**
543
     * Get the server address of the server where this match took place
544
     * @return string|null Returns null if there was no server address recorded
545
     */
546
    public function getServerAddress()
547
    {
548
        if ($this->port == null || $this->server == null) {
549
            return null;
550
        }
551
552
        return $this->server . ":" . $this->port;
553
    }
554
555
    /**
556
     * Set the server address of the server where this match took place
557
     *
558
     * @param  string|null $server The server hostname
559
     * @param  int|null    $port   The server port
560
     * @return self
561
     */
562
    public function setServerAddress($server = null, $port = 5154)
563
    {
564
        $this->updateProperty($this->server, "server", $server, "s");
565
        $this->updateProperty($this->port, "port", $port, "i");
566
567
        return $this;
568
    }
569
570
    /**
571
     * Get the name of the replay file for this specific map
572
     * @param  int    $length The length of the replay file name; it will be truncated
573
     * @return string Returns null if there was no replay file name recorded
574
     */
575
    public function getReplayFileName($length = 0)
576
    {
577
        if ($length > 0) {
578
            return substr($this->replay_file, 0, $length);
579
        }
580
581
        return $this->replay_file;
582
    }
583
584
    /**
585
     * Get the match duration
586
     * @return int The duration of the match in minutes
587
     */
588 3
    public function getDuration()
589
    {
590 3
        return $this->duration;
591
    }
592
593
    /**
594
     * Set the match duration
595
     *
596
     * @param  int  $duration The new duration of the match in minutes
597
     * @return self
598
     */
599
    public function setDuration($duration)
600
    {
601
        return $this->updateProperty($this->duration, "duration", $duration, "i");
602
    }
603
604
    /**
605
     * Get the user who entered the match
606
     * @return Player
607
     */
608 2
    public function getEnteredBy()
609
    {
610 2
        return Player::get($this->entered_by);
611
    }
612
613
    /**
614
     * Get the loser of the match
615
     *
616
     * @return Team The team that was the loser or the team with the lower elo if the match was a draw
617
     */
618 8
    public function getLoser()
619
    {
620
        // Get the winner of the match
621 8
        $winner = $this->getWinner();
622
623
        // Get the team that wasn't the winner... Duh
624 8
        return $this->getOpponent($winner->getId());
625
    }
626
627
    /**
628
     * Get the winner of a match
629
     *
630
     * @return Team The team that was the victor or the team with the lower elo if the match was a draw
631
     */
632 8
    public function getWinner()
633
    {
634
        // Get the team that had its ELO increased
635 8
        if ($this->elo_diff > 0) {
636 6
            return $this->getTeamA();
637 2
        } elseif ($this->elo_diff < 0) {
638 2
            return $this->getTeamB();
639
        }
640
641
        // If the ELOs are the same, return Team A because well, fuck you that's why
642
        return $this->getTeamA();
643
    }
644
645
    /**
646
     * Determine whether the match was a draw
647
     * @return bool True if the match ended without any winning teams
648
     */
649 9
    public function isDraw()
650
    {
651 9
        return $this->team_a_points == $this->team_b_points;
652
    }
653
654
    /**
655
     * Find out whether the match involves a team
656
     *
657
     * @param  Team $team The team to check
658
     * @return bool
659
     */
660 1
    public function involvesTeam($team)
661
    {
662 1
        return $team->getId() == $this->team_a || $team->getId() == $this->team_b;
663
    }
664
665
    /**
666
     * Reset the ELOs of the teams participating in the match
667
     *
668
     * @return self
669
     */
670
    public function resetELOs()
671
    {
672
        $this->getTeamA()->changeELO(-$this->elo_diff);
673
        $this->getTeamB()->changeELO(+$this->elo_diff);
674
    }
675
676
    /**
677
     * Calculate the match's contribution to the team activity
678
     *
679
     * @return float
680
     */
681 1
    public function getActivity()
682
    {
683 1
        $daysPassed = $this->getTimestamp()->diffInSeconds();
684 1
        $daysPassed = $daysPassed / TimeDate::SECONDS_PER_MINUTE / TimeDate::MINUTES_PER_HOUR / TimeDate::HOURS_PER_DAY;
685
686 1
        $activity = 0.0116687059537612 * (pow(45 - $daysPassed, (1 / 6)) + atan(31.0 - $daysPassed) / 2.0);
687
688 1
        if (is_nan($activity) || $activity < 0.0) {
689
            return 0.0;
690
        }
691
692 1
        return $activity;
693
    }
694
695
    /**
696
     * Enter a new match to the database
697
     * @param  int             $a          Team A's ID
698
     * @param  int             $b          Team B's ID
699
     * @param  int             $a_points   Team A's match points
700
     * @param  int             $b_points   Team B's match points
701
     * @param  int             $duration   The match duration in minutes
702
     * @param  int|null        $entered_by The ID of the player reporting the match
703
     * @param  string|DateTime $timestamp  When the match was played
704
     * @param  int[]           $a_players  The IDs of the first team's players
705
     * @param  int[]           $b_players  The IDs of the second team's players
706
     * @param  string|null     $server     The address of the server where the match was played
707
     * @param  int|null        $port       The port of the server where the match was played
708
     * @param  string          $replayFile The name of the replay file of the match
709
     * @param  int             $map        The ID of the map where the match was played, only for rotational leagues
710
     * @param  string          $matchType  The type of match (e.g. official, fm, special)
711
     * @return Match           An object representing the match that was just entered
712
     */
713 9
    public static function enterMatch(
714
        $a, $b, $a_points, $b_points, $duration, $entered_by, $timestamp = "now",
715
        $a_players = array(), $b_players = array(), $server = null, $port = null,
716
        $replayFile = null, $map = null, $matchType = "official"
717
    ) {
718
        $matchData = array(
719 9
            'team_a_points'  => $a_points,
720 9
            'team_b_points'  => $b_points,
721 9
            'team_a_players' => implode(',', $a_players),
722 9
            'team_b_players' => implode(',', $b_players),
723 9
            'timestamp'      => TimeDate::from($timestamp)->toMysql(),
724 9
            'duration'       => $duration,
725 9
            'entered_by'     => $entered_by,
726 9
            'server'         => $server,
727 9
            'port'           => $port,
728 9
            'replay_file'    => $replayFile,
729 9
            'map'            => $map,
730 9
            'status'         => 'entered',
731 9
            'match_type'     => $matchType
732
        );
733 9
        $matchDataTypes = 'iisssiisisiss';
734
735 9
        if ($matchType === Match::OFFICIAL) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
736 9
            $team_a = Team::get($a);
737 9
            $team_b = Team::get($b);
738 9
            $a_elo = $team_a->getElo();
739 9
            $b_elo = $team_b->getElo();
740
741 9
            $diff = self::calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration);
742
743
            // Update team ELOs
744 9
            $team_a->changeElo($diff);
745 9
            $team_b->changeElo(-$diff);
746
747 9
            $matchData = array_merge($matchData, array(
748 9
                'team_a'         => $a,
749 9
                'team_b'         => $b,
750 9
                'team_a_elo_new' => $team_a->getElo(),
751 9
                'team_b_elo_new' => $team_b->getElo(),
752 9
                'elo_diff'       => $diff
753
            ));
754 9
            $matchDataTypes .= 'iiiii';
755
        }
756
757 9
        $match = self::create($matchData, $matchDataTypes, 'updated');
758
759 9
        if ($matchType === Match::OFFICIAL) {
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
760 9
            $match->updateMatchCount();
761
        }
762
763 9
        return $match;
764
    }
765
766
    /**
767
     * Calculate the ELO score difference
768
     *
769
     * Computes the ELO score difference on each team after a match, based on
770
     * GU League's rules.
771
     *
772
     * @param  int $a_elo    Team A's current ELO score
773
     * @param  int $b_elo    Team B's current ELO score
774
     * @param  int $a_points Team A's match points
775
     * @param  int $b_points Team B's match points
776
     * @param  int $duration The match duration in minutes
777
     * @return int The ELO score difference
778
     */
779 9
    public static function calculateEloDiff($a_elo, $b_elo, $a_points, $b_points, $duration)
780
    {
781 9
        $prob = 1.0 / (1 + pow(10, (($b_elo - $a_elo) / 400.0)));
782 9
        if ($a_points > $b_points) {
783 5
            $diff = 50 * (1 - $prob);
784 5
        } elseif ($a_points == $b_points) {
785 4
            $diff = 50 * (0.5 - $prob);
786
        } else {
787 1
            $diff = 50 * (0 - $prob);
788
        }
789
790
        // Apply ELO modifiers from `config.yml`
791 9
        $durations = Service::getParameter('bzion.league.duration');
792 9
        $diff *= (isset($durations[$duration])) ? $durations[$duration] : 1;
793
794 9
        if (abs($diff) < 1 && $diff != 0) {
795
            // ELOs such as 0.75 should round up to 1...
796 2
            return ($diff > 0) ? 1 : -1;
797
        }
798
799
        // ...everything else is rounded down (-3.7 becomes -3 and 48.1 becomes 48)
800 7
        return intval($diff);
801
    }
802
803
    /**
804
     * Find if a match's stored ELO is correct
805
     */
806
    public function isEloCorrect()
807
    {
808
        return $this->elo_diff === $this->calculateEloDiff(
809
            $this->getTeamAEloOld(),
810
            $this->getTeamBEloOld(),
811
            $this->getTeamAPoints(),
812
            $this->getTeamBPoints(),
813
            $this->getDuration()
814
        );
815
    }
816
817
    /**
818
     * Recalculate the match's elo and adjust the team ELO values
819
     */
820
    public function recalculateElo()
821
    {
822
        $a = $this->getTeamA();
823
        $b = $this->getTeamB();
824
825
        $elo = $this->calculateEloDiff(
826
            $a->getElo(),
827
            $b->getElo(),
828
            $this->getTeamAPoints(),
829
            $this->getTeamBPoints(),
830
            $this->getDuration()
831
        );
832
833
        $this->updateProperty($this->elo_diff, "elo_diff", $elo, "i");
834
835
        $a->changeElo($elo);
836
        $b->changeElo(-$elo);
837
838
        $this->updateProperty($this->team_a_elo_new, "team_a_elo_new", $a->getElo(), "i");
839
        $this->updateProperty($this->team_b_elo_new, "team_b_elo_new", $b->getElo(), "i");
840
    }
841
842
    /**
843
     * Get all the matches in the database
844
     */
845 1
    public static function getMatches()
846
    {
847 1
        return self::getQueryBuilder()->active()->getModels();
848
    }
849
850
    /**
851
     * Get a query builder for matches
852
     * @return MatchQueryBuilder
853
     */
854 3
    public static function getQueryBuilder()
855
    {
856 3
        return new MatchQueryBuilder('Match', array(
857
            'columns' => array(
858
                'firstTeam'        => 'team_a',
859
                'secondTeam'       => 'team_b',
860
                'firstTeamPoints'  => 'team_a_points',
861
                'secondTeamPoints' => 'team_b_points',
862
                'time'             => 'timestamp',
863
                'status'           => 'status'
864 3
            ),
865
        ));
866
    }
867
868
    /**
869
     * {@inheritdoc}
870
     */
871
    public function delete()
872
    {
873
        $this->updateMatchCount(true);
874
875
        return parent::delete();
876
    }
877
878
    /**
879
     * {@inheritdoc}
880
     */
881 3
    public static function getActiveStatuses()
882
    {
883 3
        return array('entered');
884
    }
885
886
    /**
887
     * {@inheritdoc}
888
     */
889 1
    public function getName()
890
    {
891 1
        return sprintf("(+/- %d) %s [%d] vs [%d] %s",
892 1
            $this->getEloDiff(),
893 1
            $this->getWinner()->getName(),
894 1
            $this->getScore($this->getWinner()),
0 ignored issues
show
Bug introduced by
It seems like $this->getWinner() can be null; however, getScore() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
895 1
            $this->getScore($this->getLoser()),
0 ignored issues
show
Bug introduced by
It seems like $this->getLoser() can be null; however, getScore() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
896 1
            $this->getLoser()->getName()
897
        );
898
    }
899
900
    /**
901
     * Update the match count of the teams participating in the match
902
     *
903
     * @param bool $decrement Whether to decrement instead of incrementing the match count
904
     */
905 9
    private function updateMatchCount($decrement = false)
906
    {
907 9
        $diff = ($decrement) ? -1 : 1;
908
909 9
        if ($this->isDraw()) {
910 4
            $this->getTeamA()->changeMatchCount($diff, 'draw');
911 4
            $this->getTeamB()->changeMatchCount($diff, 'draw');
912
        } else {
913 6
            $this->getWinner()->changeMatchCount($diff, 'win');
914 6
            $this->getLoser()->changeMatchCount($diff, 'loss');
915
        }
916 9
    }
917
}
918