Completed
Pull Request — master (#87)
by De Cramer
02:48
created

RecordHandler::updateRecordStats()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php
2
3
namespace eXpansion\Bundle\LocalRecords\Services;
4
use eXpansion\Bundle\LocalRecords\Entity\Record;
5
use eXpansion\Bundle\LocalRecords\Repository\RecordRepository;
6
7
/**
8
 * Class RecordHandler
9
 *
10
 * @package eXpansion\Bundle\LocalRecords\Model;
11
 * @author  oliver de Cramer <[email protected]>
12
 */
13
class RecordHandler
14
{
15
    /**
16
     * Available order logic for score.
17
     */
18
    const ORDER_ASC = "ASC";
19
    const ORDER_DESC = "DESC";
20
21
    /**
22
     * List of event types
23
     */
24
    const EVENT_TYPE_FIRST_TIME = 'first_time';
25
    const EVENT_TYPE_SAME_SCORE = 'same_score';
26
    const EVENT_TYPE_SAME_POS =   'same_position';
27
    const EVENT_TYPE_BETTER_POS = 'better_position';
28
29
    /**
30
     * List of data in the associative array returned.
31
     */
32
    const COL_EVENT = 'event';
33
    const COL_RECORD = 'record';
34
    const COL_OLD_RECORD = 'old_record';
35
    const COL_POS = 'position';
36
    const COL_OLD_POS = 'old_position';
37
    const COL_RECORDS = 'records';
38
39
    /** @var int */
40
    protected $nbRecords;
41
42
    /** @var string */
43
    protected $ordering;
44
45
    /** @var  RecordRepository */
46
    protected $recordRepository;
47
48
    /** @var Record[] */
49
    protected $records;
50
51
    /** @var Record[] */
52
    protected $recordsPerPlayer;
53
54
    /** @var int[] */
55
    protected $positionPerPlayer;
56
57
    /** @var int */
58
    protected $currentNbLaps;
59
60
    protected $currentMapUid;
61
62
    /**
63
     * RaceRecordHandler constructor.
64
     *
65
     * @param RecordRepository $recordRepository
66
     * @param int $nbRecords
67
     */
68 15
    public function __construct(RecordRepository $recordRepository, $nbRecords, $ordering = self::ORDER_ASC)
69
    {
70 15
        $this->recordRepository = $recordRepository;
71 15
        $this->nbRecords = $nbRecords;
72 15
        $this->ordering = $ordering;
73 15
    }
74
75
    /**
76
     * @return Record[]
77
     */
78 7
    public function getRecords()
79
    {
80 7
        return $this->records;
81
    }
82
83
    /**
84
     * Get the position of a player
85
     *
86
     * @param string $login
87
     *
88
     * @return bool
89
     */
90 5
    public function getPlayerPosition($login)
91
    {
92 5
        return isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : null;
93
    }
94
95
    /**
96
     * Get a players record information.
97
     *
98
     * @param $login
99
     *
100
     * @return Record|null
101
     */
102 2
    public function getPlayerRecord($login)
103
    {
104 2
        return isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : null;
105
    }
106
107
    /**
108
     * Load records for a certain map.
109
     *
110
     * @param $mapUid
111
     * @param $nbLaps
112
     */
113 12
    public function loadForMap($mapUid, $nbLaps)
114
    {
115 12
        $this->recordsPerPlayer = [];
116 12
        $this->positionPerPlayer = [];
117
118 12
        $this->currentMapUid = $mapUid;
119 12
        $this->currentNbLaps = $nbLaps;
120
121 12
        $this->records = $this->recordRepository->findBy(
122 12
            ['mapUid' => $mapUid, 'nbLaps' => $nbLaps],
123 12
            ['score' => $this->getScoreOrdering()],
124 12
            $this->nbRecords
125
        );
126
127 12
        $position = 1;
128 12
        foreach ($this->records as $record)
129
        {
130 11
            $this->recordsPerPlayer[$record->getPlayerLogin()] = $record;
131 11
            $this->positionPerPlayer[$record->getPlayerLogin()] = $position++;
132
        }
133 12
    }
134
135
    /**
136
     * Load records for certain players only.
137
     *
138
     * @param $mapUid
139
     * @param $nbLaps
140
     * @param $logins
141
     */
142 1
    public function loadForPlayers($mapUid, $nbLaps, $logins)
143
    {
144 1
        $logins = array_diff(array_keys($this->recordsPerPlayer), $logins);
145
146 1
        if (!empty($logins)) {
147 1
            $records = $this->recordRepository->findBy(
148 1
                ['mapUid' => $mapUid, 'nbLaps' => $nbLaps, 'playerLogin' => $logins],
149 1
                ['score' => $this->getScoreOrdering()],
150 1
                $this->nbRecords
151
            );
152
153 1
            foreach ($records as $record) {
154 1
                $this->recordsPerPlayer[$record->getPlayerLogin()] = $record;
155
            }
156
        }
157 1
    }
158
159
    /**
160
     * Save all new records.
161
     */
162 1
    public function save()
163
    {
164 1
        $this->recordRepository->massSave($this->recordsPerPlayer);
165 1
    }
166
167
    /**
168
     * Add a new record
169
     *
170
     * @param string $login
171
     * @param int $score
172
     * @param int[] $checkpoints
173
     *
174
     * @return array|null Data for the new records.
175
     */
176 12
    public function addRecord($login, $score, $checkpoints) {
177 12
        $oldPosition = isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : count($this->records) + 1;
178 12
        $record = isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : $this->getNewRecord($login);
179 12
        $this->recordsPerPlayer[$login] = $record;
180
181 12
        $oldRecord = clone $record;
182 12
        $this->updateRecordStats($record, $score);
183
184 12
        if (empty($this->records)) {
185 2
            $record->setScore($score);
186 2
            $record->setDate(new \DateTime());
187 2
            $record->setCheckpointTimes($checkpoints);
188
189 2
            $this->records[0] = $record;
190 2
            $this->positionPerPlayer[$record->getPlayerLogin()] = 1;
191 2
            $this->recordsPerPlayer[$record->getPlayerLogin()] = $record;
192
193
            return [
194 2
                self::COL_EVENT => self::EVENT_TYPE_FIRST_TIME,
195 2
                self::COL_RECORD => $record,
196 2
                self::COL_RECORDS => $this->records,
197 2
                self::COL_POS => 1,
198
            ];
199
        }
200
201
        // Check if first time of this player.
202 12
        $firstTime = is_null($record->getScore());
203
204 12
        if ($score == $record->getScore()) {
205
            return [
206 1
                self::COL_EVENT => self::EVENT_TYPE_SAME_SCORE,
207 1
                self::COL_RECORD => $record,
208 1
                self::COL_OLD_RECORD => $oldRecord,
209 1
                self::COL_RECORDS => $this->records
210
            ];
211
        }
212
213 11
        if ($firstTime || $this->compareNewScore($record, $score)) {
214 9
            $recordIndex = $oldPosition - 1;
215 9
            $newPosition = $oldPosition;
216
217 9
            $this->records[$recordIndex] = $record;
218 9
            $this->positionPerPlayer[$record->getPlayerLogin()] = $oldPosition;
219
220 9
            while ($recordIndex > 0 && $this->compareNewScore($this->records[$recordIndex - 1], $score)) {
221 5
                $previousRecord = $this->records[$recordIndex - 1];
222
223 5
                $this->records[$recordIndex - 1] = $record;
224 5
                $this->records[$recordIndex] = $previousRecord;
225
226 5
                $newPosition = $recordIndex;
227 5
                $this->positionPerPlayer[$record->getPlayerLogin()] = $recordIndex;
228 5
                $this->positionPerPlayer[$previousRecord->getPlayerLogin()] = $recordIndex + 1;
229
230 5
                $recordIndex--;
231
            }
232
233 9
            $record->setScore($score);
234 9
            $record->setDate(new \DateTime());
235 9
            $record->setCheckpointTimes($checkpoints);
236
237
            // Remove entries whose position is superior to the limit.
238 9
            $this->records = array_slice($this->records, 0, $this->nbRecords);
239
240 9
            if ($newPosition <= $this->nbRecords) {
241 8
                if ($newPosition != $oldPosition || $firstTime) {
242
                    return [
243 6
                        self::COL_EVENT => self::EVENT_TYPE_BETTER_POS,
244 6
                        self::COL_RECORD => $record,
245 6
                        self::COL_OLD_RECORD => $oldRecord,
246 6
                        self::COL_RECORDS => $this->records,
247 6
                        self::COL_POS => $newPosition,
248 6
                        self::COL_OLD_POS => $firstTime ? null : $oldPosition,
249
                    ];
250
                }
251
252
                return [
253 2
                    self::COL_EVENT => self::EVENT_TYPE_SAME_POS,
254 2
                    self::COL_RECORD => $record,
255 2
                    self::COL_OLD_RECORD => $oldRecord,
256 2
                    self::COL_RECORDS => $this->records,
257 2
                    self::COL_POS => $newPosition,
258
                ];
259
            }
260
        }
261
262 5
        return null;
263
    }
264
265
    /**
266
     * Get a new record instance.
267
     *
268
     * @param $login
269
     *
270
     * @return Record
271
     */
272 6
    protected function getNewRecord($login)
273
    {
274 6
        $record = new Record();
275 6
        $record->setPlayerLogin($login);
276 6
        $record->setNbLaps($this->currentNbLaps);
277 6
        $record->setNbFinish(0);
278 6
        $record->setMapUid($this->currentMapUid);
279
280 6
        return $record;
281
    }
282
283
    /**
284
     * Update Records statistics.
285
     *
286
     * @param Record $record
287
     * @param        $score
288
     */
289 12
    protected function updateRecordStats(Record $record, $score)
290
    {
291 12
        $record->setAvgScore(
292 12
            (($record->getAvgScore() * $record->getNbFinish()) + $score) / ($record->getNbFinish() + 1)
293
        );
294 12
        $record->setNbFinish($record->getNbFinish() + 1);
295 12
    }
296
297
298
    /**
299
     * Get ordering use for sorting.
300
     *
301
     * @return string
302
     */
303 13
    protected function getScoreOrdering()
304
    {
305 13
        return $this->ordering;
306
    }
307
308
    /**
309
     * @param int $newScore
310
     * @param Record $record
311
     *
312
     * @return bool
313
     */
314 11
    protected function compareNewScore($record, $newScore)
315
    {
316 11
        if ($this->getScoreOrdering() == self::ORDER_ASC) {
317 10
            return $newScore <= $record->getScore();
318
        } else {
319 1
            return $newScore >= $record->getScore();
320
        }
321
    }
322
}