Completed
Push — master ( 834eaf...74c218 )
by Julien
05:32
created

TreeGen::getFightersByArea()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 1
eloc 6
nc 1
nop 0
1
<?php
2
3
namespace Xoco70\LaravelTournaments\TreeGen;
4
5
use Illuminate\Support\Collection;
6
use Xoco70\LaravelTournaments\Contracts\TreeGenerable;
7
use Xoco70\LaravelTournaments\Exceptions\TreeGenerationException;
8
use Xoco70\LaravelTournaments\Models\Championship;
9
use Xoco70\LaravelTournaments\Models\Fight;
10
use Xoco70\LaravelTournaments\Models\FightersGroup;
11
12
abstract class TreeGen implements TreeGenerable
13
{
14
    protected $groupBy;
15
    protected $tree;
16
    public $championship;
17
    public $settings;
18
    protected $numFighters;
19
20
    abstract protected function pushEmptyGroupsToTree($numFighters);
21
22
    abstract protected function generateFights();
23
24
    abstract protected function createByeFighter();
25
26
    abstract protected function chunkAndShuffle(Collection $fightersByEntity);
27
28
    abstract protected function addFighterToGroup(FightersGroup $group, $fighter);
29
30
    abstract protected function syncGroup(FightersGroup $group, $fighters);
31
32
    abstract protected function getByeGroup($fighters);
33
34
    abstract protected function getFighter($fighterId);
35
36
    abstract protected function getFighters();
37
38
    abstract protected function getNumRounds($fightersCount);
39
40
    abstract protected function generateGroupsForRound(Collection $fightersByArea, $round);
41
42
    abstract protected function generateAllTrees();
43
44
    /**
45
     * @param Championship $championship
46
     * @param $groupBy
47
     */
48
    public function __construct(Championship $championship, $groupBy)
49
    {
50
        $this->championship = $championship;
51
        $this->groupBy = $groupBy;
52
        $this->settings = $championship->getSettings();
53
        $this->tree = new Collection();
54
    }
55
56
    /**
57
     * Generate tree groups for a championship.
58
     *
59
     * @throws TreeGenerationException
60
     */
61
    public function run()
62
    {
63
        $this->championship->fightersGroups()->delete();
64
        $this->generateAllTrees();
65
        $this->generateAllFights();
66
    }
67
68
    /**
69
     * Get the biggest entity group.
70
     *
71
     * @param $userGroups
72
     *
73
     * @return int
74
     */
75
    private function getMaxFightersByEntity($userGroups): int
76
    {
77
        return $userGroups
78
            ->sortByDesc(function ($group) {
79
                return $group->count();
80
            })
81
            ->first()
82
            ->count();
83
    }
84
85
    /**
86
     * Get Competitor's list ordered by entities
87
     * Countries for Internation Tournament, State for a National Tournament, etc.
88
     *
89
     * @param $fighters
90
     *
91
     * @return Collection
92
     */
93
    private function getFightersByEntity($fighters): Collection
94
    {
95
        // Right now, we are treating users and teams as equals.
96
        // It doesn't matter right now, because we only need name attribute which is common to both models
97
98
        // $this->groupBy contains federation_id, association_id, club_id, etc.
99
        if (($this->groupBy) != null) {
100
            return $fighters->groupBy($this->groupBy); // Collection of Collection
101
        }
102
        return $fighters->chunk(1); // Collection of Collection
103
    }
104
105
    /**
106
     * Get the size the first round will have.
107
     *
108
     * @param $fighterCount
109
     * @param $groupSize
110
     *
111
     * @return int
112
     */
113
    protected function getTreeSize($fighterCount, $groupSize)
114
    {
115
        $squareMultiplied = collect([1, 2, 4, 8, 16, 32, 64])
116
            ->map(function ($item) use ($groupSize) {
117
                return $item * $groupSize;
118
            }); // [4, 8, 16, 32, 64,...]
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
119
120
        foreach ($squareMultiplied as $limit) {
121
            if ($fighterCount <= $limit) {
122
                $treeSize = $limit;
123
                $numAreas = $this->championship->getSettings()->fightingAreas;
124
                $fighterCountPerArea = $treeSize / $numAreas;
125
                if ($fighterCountPerArea < $groupSize) {
126
                    $treeSize = $treeSize * $numAreas;
127
                }
128
129
                return $treeSize;
130
            }
131
        }
132
133
        return 64 * $groupSize;
134
    }
135
136
    /**
137
     * Repart BYE in the tree,.
138
     *
139
     * @param $fighterGroups
140
     * @param int $max
141
     *
142
     * @return Collection
143
     */
144
    private function repart($fighterGroups, $max)
145
    {
146
        $fighters = new Collection();
147
        for ($i = 0; $i < $max; $i++) {
148
            foreach ($fighterGroups as $fighterGroup) {
149
                $fighter = $fighterGroup->values()->get($i);
150
                if ($fighter != null) {
151
                    $fighters->push($fighter);
152
                }
153
            }
154
        }
155
156
        return $fighters;
157
    }
158
159
    /**
160
     * Insert byes in an homogen way.
161
     *
162
     * @param Collection $fighters
163
     * @param Collection $byeGroup
164
     *
165
     * @return Collection
166
     */
167
    private function insertByes(Collection $fighters, Collection $byeGroup)
168
    {
169
        $bye = count($byeGroup) > 0 ? $byeGroup[0] : [];
170
        $sizeFighters = count($fighters);
171
        $sizeGroupBy = count($byeGroup);
172
173
        $frequency = $sizeGroupBy != 0
174
            ? (int)floor($sizeFighters / $sizeGroupBy)
175
            : -1;
176
177
        // Create Copy of $competitors
178
        return $this->getFullFighterList($fighters, $frequency, $sizeGroupBy, $bye);
179
    }
180
181
182
    /**
183
     * @param $order
184
     * @param $round
185
     * @param $parent
186
     *
187
     * @return FightersGroup
188
     */
189
    protected function saveGroup($order, $round, $parent): FightersGroup
190
    {
191
        $group = new FightersGroup();
192
        $this->championship->isDirectEliminationType()
193
            ? $group->area = $this->getNumArea($round, $order)
194
            : $group->area = 1; // Area limited to 1 in playoff
195
196
197
        $group->order = $order;
198
        $group->round = $round;
199
        $group->championship_id = $this->championship->id;
200
        if ($parent != null) {
201
            $group->parent_id = $parent->id;
202
        }
203
        $group->save();
204
205
        return $group;
206
    }
207
208
    /**
209
     * @param int $groupSize
210
     *
211
     * @return Collection
212
     */
213
    public function createByeGroup($groupSize): Collection
214
    {
215
        $byeFighter = $this->createByeFighter();
216
        $group = new Collection();
217
        for ($i = 0; $i < $groupSize; $i++) {
218
            $group->push($byeFighter);
219
        }
220
221
        return $group;
222
    }
223
224
    /**
225
     * @param $fighters
226
     * @param Collection $fighterGroups
227
     *
228
     * @return Collection
229
     */
230
    public function adjustFightersGroupWithByes($fighters, $fighterGroups): Collection
231
    {
232
        $tmpFighterGroups = clone $fighterGroups;
233
        $byeGroup = $this->getByeGroup($fighters);
234
235
        // Get biggest competitor's group
236
        $max = $this->getMaxFightersByEntity($tmpFighterGroups);
237
238
        // We reacommodate them so that we can mix them up and they don't fight with another competitor of his entity.
239
240
        $fighters = $this->repart($fighterGroups, $max);
241
        $fighters = $this->insertByes($fighters, $byeGroup);
242
243
        return $fighters;
244
    }
245
246
    /**
247
     * Get All Groups on previous round.
248
     *
249
     * @param $currentRound
250
     *
251
     * @return Collection
252
     */
253
    private function getPreviousRound($currentRound)
254
    {
255
        $previousRound = $this->championship->groupsByRound($currentRound + 1)->get();
256
257
        return $previousRound;
258
    }
259
260
    /**
261
     * Get the next group on the right ( parent ), final round being the ancestor.
262
     *
263
     * @param $matchNumber
264
     * @param Collection $previousRound
265
     *
266
     * @return mixed
267
     */
268
    private function getParentGroup($matchNumber, $previousRound)
269
    {
270
        $parentIndex = intval(($matchNumber + 1) / 2);
271
        $parent = $previousRound->get($parentIndex - 1);
272
273
        return $parent;
274
    }
275
276
    /**
277
     * Group Fighters by area.
278
     *
279
     * @throws TreeGenerationException
280
     *
281
     * @return Collection
282
     */
283
    protected function getFightersByArea()
284
    {
285
        // If previous trees already exists, delete all
286
        $areas = $this->settings->fightingAreas;
287
        $fighters = $this->getFighters();
288
289
        // Get Competitor's / Team list ordered by entities ( Federation, Assoc, Club, etc...)
290
        $fighterByEntity = $this->getFightersByEntity($fighters); // Chunk(1)
291
        $fightersWithBye = $this->adjustFightersGroupWithByes($fighters, $fighterByEntity);
292
        // Chunk user by areas
293
        return $fightersWithBye->chunk(count($fightersWithBye) / $areas);
294
    }
295
296
    /**
297
     * Attach a parent to every child for nestedSet Navigation.
298
     *
299
     * @param $numFightersElim
300
     */
301
    protected function addParentToChildren($numFightersElim)
302
    {
303
        $numRounds = $this->getNumRounds($numFightersElim);
304
        $groupsDesc = $this->championship
305
            ->fightersGroups()
306
            ->where('round', '<', $numRounds)
307
            ->orderByDesc('id')->get();
308
309
        $groupsDescByRound = $groupsDesc->groupBy('round');
310
311
        foreach ($groupsDescByRound as $round => $groups) {
312
            $previousRound = $this->getPreviousRound($round);
313
            foreach ($groups->reverse()->values() as $matchNumber => $group) {
314
                $parent = $this->getParentGroup($matchNumber + 1, $previousRound);
315
                $group->parent_id = $parent->id;
316
                $group->save();
317
            }
318
        }
319
    }
320
321
    /**
322
     * @param Collection $fighters
323
     * @param $frequency
324
     * @param $sizeGroupBy
325
     * @param $bye
326
     *
327
     * @return Collection
328
     */
329
    private function getFullFighterList(Collection $fighters, $frequency, $sizeGroupBy, $bye): Collection
330
    {
331
        $newFighters = new Collection();
332
        $count = 0;
333
        $byeCount = 0;
334
        foreach ($fighters as $fighter) {
335
            if ($this->shouldInsertBye($frequency, $sizeGroupBy, $count, $byeCount)) {
336
                $newFighters->push($bye);
337
                $byeCount++;
338
            }
339
            $newFighters->push($fighter);
340
            $count++;
341
        }
342
343
        return $newFighters;
344
    }
345
346
    /**
347
     * @param $frequency
348
     * @param $sizeGroupBy
349
     * @param $count
350
     * @param $byeCount
351
     *
352
     * @return bool
353
     */
354
    private function shouldInsertBye($frequency, $sizeGroupBy, $count, $byeCount): bool
355
    {
356
        return $frequency != -1 && $count % $frequency == 0 && $byeCount < $sizeGroupBy;
357
    }
358
359
    /**
360
     * Destroy Previous Fights for demo.
361
     */
362
    protected function destroyPreviousFights()
363
    {
364
        // Delete previous fight for this championship
365
        $arrGroupsId = $this->championship->fightersGroups()->get()->pluck('id');
366
        Fight::destroy($arrGroupsId);
367
    }
368
369
    /**
370
     * Generate Fights for next rounds.
371
     */
372
    public function generateNextRoundsFights()
373
    {
374
        $fightersCount = $this->championship->competitors->count() + $this->championship->teams->count();
375
        $maxRounds = $this->getNumRounds($fightersCount);
376
        for ($numRound = 1; $numRound < $maxRounds; $numRound++) {
377
            $groupsByRound = $this->championship->fightersGroups()->where('round', $numRound)->with('parent', 'children')->get();
378
            $this->updateParentFight($groupsByRound); // should be groupsByRound
379
        }
380
    }
381
382
    /**
383
     * @param $groupsByRound
384
     */
385
    protected function updateParentFight($groupsByRound)
386
    {
387
        foreach ($groupsByRound as $keyGroup => $group) {
388
            $parentGroup = $group->parent;
389
            if ($parentGroup == null) {
390
                break;
391
            }
392
            $parentFight = $parentGroup->fights->get(0);
393
394
            // determine whether c1 or c2 must be updated
395
            $this->chooseAndUpdateParentFight($keyGroup, $group, $parentFight);
396
        }
397
    }
398
399
    /**
400
     * @param $group
401
     * @param $parentFight
402
     */
403
    protected function chooseAndUpdateParentFight($keyGroup, FightersGroup $group, Fight $parentFight)
404
    {
405
        // we need to know if the child has empty fighters, is this BYE or undetermined
406
        if ($group->hasDeterminedParent()) {
407
            $valueToUpdate = $group->getValueToUpdate(); // This should be OK
408
            if ($valueToUpdate != null) {
409
                $fighterToUpdate = $group->getParentFighterToUpdate($keyGroup);
410
                $parentFight->$fighterToUpdate = $valueToUpdate;
411
                $parentFight->save();
412
                // Add fighter to pivot table
413
                $parentGroup = $parentFight->group;
414
415
                $fighter = $this->getFighter($valueToUpdate);
416
                $this->addFighterToGroup($parentGroup, $fighter);
417
            }
418
        }
419
    }
420
421
    /**
422
     * Calculate the area of the group ( group is still not created ).
423
     *
424
     * @param $round
425
     * @param $order
426
     *
427
     * @return int
428
     */
429
    protected function getNumArea($round, $order)
430
    {
431
        $totalAreas = $this->settings->fightingAreas;
432
        $numFighters = $this->championship->fighters->count(); // 4
433
        $numGroups = $this->getTreeSize($numFighters, $this->championship->getGroupSize()) / $this->championship->getGroupSize(); // 1 -> 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
434
435
        $areaSize = $numGroups / ($totalAreas * pow(2, $round - 1));
436
437
        $numArea = intval(ceil($order / $areaSize)); // if round == 4, and second match 2/2 = 1 BAD
438
//        dump($numArea);
439
        return $numArea;
440
    }
441
442
443
    protected function generateAllFights()
444
    {
445
        $this->generateFights(); // Abstract
446
447
        //TODO In direct elimination without Prelim, short_id are not generating well
448
        $this->generateNextRoundsFights();
449
        Fight::generateFightsId($this->championship);
450
    }
451
}
452