Completed
Pull Request — master (#68)
by De Cramer
05:45
created

RecordHandler::loadForMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 14
cts 14
cp 1
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 13
nc 2
nop 2
crap 2
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 11
    public function __construct(RecordRepository $recordRepository, $nbRecords, $ordering = self::ORDER_ASC)
69
    {
70 11
        $this->recordRepository = $recordRepository;
71 11
        $this->nbRecords = $nbRecords;
72 11
        $this->ordering = $ordering;
73 11
    }
74
75
    /**
76
     * @return Record[]
77
     */
78 5
    public function getRecords()
79
    {
80 5
        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 8
    public function loadForMap($mapUid, $nbLaps)
114
    {
115 8
        $this->recordsPerPlayer = [];
116 8
        $this->positionPerPlayer = [];
117
118 8
        $this->currentMapUid = $mapUid;
119 8
        $this->currentNbLaps = $nbLaps;
120
121 8
        $this->records = $this->recordRepository->findBy(
122 8
            ['mapUid' => $mapUid, 'nbLaps' => $nbLaps],
123 8
            ['score' => $this->getScoreOrdering()],
124 8
            $this->nbRecords
125
        );
126
127 8
        $position = 1;
128 8
        foreach ($this->records as $record)
129
        {
130 8
            $this->recordsPerPlayer[$record->getPlayerLogin()] = $record;
131 8
            $this->positionPerPlayer[$record->getPlayerLogin()] = $position++;
132
        }
133 8
    }
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 8
    public function addRecord($login, $score, $checkpoints) {
177 8
        $oldPosition = isset($this->positionPerPlayer[$login]) ? $this->positionPerPlayer[$login] : count($this->records) + 1;
178 8
        $record = isset($this->recordsPerPlayer[$login]) ? $this->recordsPerPlayer[$login] : $this->getNewRecord($login);
179
180 8
        $oldRecord = clone $record;
181 8
        $this->updateRecordStats($record, $score);
182
183 8
        if (empty($this->records)) {
184 1
            $record->setScore($score);
185 1
            $record->setDate(new \DateTime());
186 1
            $record->setCheckpointTimes($checkpoints);
187
188 1
            $this->records[0] = $record;
189 1
            $this->positionPerPlayer[$record->getPlayerLogin()] = 1;
190 1
            $this->recordsPerPlayer[$record->getPlayerLogin()] = $record;
191
192
            return [
193 1
                self::COL_EVENT => self::EVENT_TYPE_FIRST_TIME,
194 1
                self::COL_RECORD => $record,
195 1
                self::COL_RECORDS => $this->records,
196 1
                self::COL_POS => 1,
197
            ];
198
        }
199
200
        // Check if first time of this player.
201 8
        $firstTime = is_null($record->getScore());
202
203 8
        if ($score == $record->getScore()) {
204
            return [
205 1
                self::COL_EVENT => self::EVENT_TYPE_SAME_SCORE,
206 1
                self::COL_RECORD => $record,
207 1
                self::COL_OLD_RECORD => $oldRecord,
208 1
                self::COL_RECORDS => $this->records
209
            ];
210
        }
211
212 7
        if ($firstTime || $this->compareNewScore($record, $score)) {
213
214 6
            $betterRecordIndex = $oldPosition - 2;
215 6
            $newPosition = $oldPosition;
216
217 6
            while ($betterRecordIndex >= 0 && $this->compareNewScore($this->records[$betterRecordIndex], $score)) {
218 4
                $previousRecord = $this->records[$betterRecordIndex];
219
220 4
                $this->records[$betterRecordIndex] = $record;
221 4
                $this->records[$betterRecordIndex+1] = $previousRecord;
222
223 4
                $newPosition = $betterRecordIndex + 1;
224 4
                $this->positionPerPlayer[$record->getPlayerLogin()] = $betterRecordIndex + 1;
225 4
                $this->positionPerPlayer[$previousRecord->getPlayerLogin()] = $betterRecordIndex + 2;
226
227 4
                $betterRecordIndex--;
228
            }
229
230 6
            $record->setScore($score);
231 6
            $record->setDate(new \DateTime());
232 6
            $record->setCheckpointTimes($checkpoints);
233
234
            // Remove entries whose position is superior to the limit.
235 6
            $this->records = array_slice($this->records, 0, $this->nbRecords);
236
237 6
            if ($newPosition <= $this->nbRecords) {
238 5
                if ($newPosition != $oldPosition) {
239
                    return [
240 4
                        self::COL_EVENT => self::EVENT_TYPE_BETTER_POS,
241 4
                        self::COL_RECORD => $record,
242 4
                        self::COL_OLD_RECORD => $oldRecord,
243 4
                        self::COL_RECORDS => $this->records,
244 4
                        self::COL_POS => $newPosition,
245 4
                        self::COL_OLD_POS => $firstTime ? null : $oldPosition,
246
                    ];
247
                }
248
249
                return [
250 1
                    self::COL_EVENT => self::EVENT_TYPE_SAME_POS,
251 1
                    self::COL_RECORD => $record,
252 1
                    self::COL_OLD_RECORD => $oldRecord,
253 1
                    self::COL_RECORDS => $this->records,
254 1
                    self::COL_POS => $newPosition,
255
                ];
256
            }
257
        }
258
259 2
        return null;
260
    }
261
262
    /**
263
     * Get a new record instance.
264
     *
265
     * @param $login
266
     *
267
     * @return Record
268
     */
269 3
    protected function getNewRecord($login)
270
    {
271 3
        $record = new Record();
272 3
        $record->setPlayerLogin($login);
273 3
        $record->setNbLaps($this->currentNbLaps);
274 3
        $record->setNbFinish(0);
275 3
        $record->setMapUid($this->currentMapUid);
276
277 3
        return $record;
278
    }
279
280
    /**
281
     * Update Records statistics.
282
     *
283
     * @param Record $record
284
     * @param        $score
285
     */
286 8
    protected function updateRecordStats(Record $record, $score)
287
    {
288 8
        $record->setAvgScore(
289 8
            (($record->getAvgScore() * $record->getNbFinish()) + $score) / ($record->getNbFinish() + 1)
290
        );
291 8
        $record->setNbFinish($record->getNbFinish() + 1);
292 8
    }
293
294
295
    /**
296
     * Get ordering use for sorting.
297
     *
298
     * @return string
299
     */
300 9
    protected function getScoreOrdering()
301
    {
302 9
        return $this->ordering;
303
    }
304
305
    /**
306
     * @param int $newScore
307
     * @param Record $record
308
     *
309
     * @return bool
310
     */
311 7
    protected function compareNewScore($record, $newScore)
312
    {
313 7
        if ($this->getScoreOrdering() == self::ORDER_ASC) {
314 6
            return $newScore <= $record->getScore();
315
        } else {
316 1
            return $newScore >= $record->getScore();
317
        }
318
    }
319
}