Completed
Pull Request — master (#167)
by
unknown
03:17
created

RecordHandler::loadForPlayers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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