Test Failed
Branch scoresheet (d5c514)
by Julien
12:40
created

TreeGen::getMaxCompetitorByEntity()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
3
namespace Xoco70\KendoTournaments\TreeGen;
4
5
use App\User;
6
use Illuminate\Support\Collection;
7
use Xoco70\KendoTournaments\Contracts\TreeGenerable;
8
use Xoco70\KendoTournaments\Exceptions\TreeGenerationException;
9
use Xoco70\KendoTournaments\Models\Championship;
10
use Xoco70\KendoTournaments\Models\ChampionshipSettings;
11
use Xoco70\KendoTournaments\Models\Competitor;
12
use Xoco70\KendoTournaments\Models\FightersGroup;
13
use Xoco70\KendoTournaments\Models\Team;
14
15
class TreeGen implements TreeGenerable
16
{
17
    protected $groupBy, $tree;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

Loading history...
18
    public $championship;
19
    public $settings;
20
21
    /**
22
     * @param \Xoco70\KendoTournaments\Models\ChampionshipSettings $settings
23
     */
24
    public function __construct(Championship $championship, $groupBy, $settings)
25
    {
26
        $this->championship = $championship;
27
        $this->groupBy = $groupBy;
28
        $this->settings = $settings;
29
    }
30
31
    /**
32
     * Generate tree groups for a championship.
33
     *
34
     * @throws TreeGenerationException
35
     *
36
     * @return Collection
37
     */
38
    public function run()
39
    {
40
        $this->tree = new Collection();
41
        // If previous trees already exist, delete all
42
        $this->championship->fightersGroups()->delete();
43
        $areas = $this->settings->fightingAreas;
44
        $fighters = $this->getFighters();
45
46
        if ($fighters->count() / $areas < ChampionshipSettings::MIN_COMPETITORS_BY_AREA) {
47
            throw new TreeGenerationException();
48
        }
49
        // Get Competitor's / Team list ordered by entities ( Federation, Assoc, Club, etc...)
50
        $fightersByEntity = $this->getFightersByEntity($fighters);
51
52
        // Chunk user by areas
53
54
        $usersByArea = $fightersByEntity->chunk(count($fightersByEntity) / $areas);
55
56
        $area = 1;
57
        $round = 1;
58
        $numFighters = sizeof($usersByArea->collapse());
59
60
        $this->generateGroupsForRound($usersByArea, $area, $round);
61
62
        $this->pushEmptyGroupsToTree($numFighters);
63
64
        return $this->tree;
65
    }
66
67
    /**
68
     * @param $userGroups
69
     *
70
     * @return int
71
     */
72
    private function getMaxCompetitorByEntity($userGroups): int
73
    {
74
        // Surely there is a Laravel function that does it ;)
75
        $max = 0;
76
        foreach ($userGroups as $userGroup) {
77
            if (count($userGroup) > $max) {
78
                $max = count($userGroup);
79
            }
80
        }
81
82
        return $max;
83
    }
84
85
    /**
86
     * Get Competitor's list ordered by entities
87
     * Countries for Internation Tournament, State for a National Tournament, etc.
88
     *
89
     * @return Collection
90
     */
91
    private function getFightersByEntity($fighters): Collection
92
    {
93
        // Right now, we are treating users and teams as equals.
94
        // It doesn't matter right now, because we only need name attribute which is common to both models
95
96
        // $this->groupBy contains federation_id, association_id, club_id, etc.
97
        if (($this->groupBy) != null) {
98
            $fighterGroups = $fighters->groupBy($this->groupBy); // Collection of Collection
99
        } else {
100
            $fighterGroups = $fighters->chunk(1); // Collection of Collection
101
        }
102
103
        $tmpFighterGroups = clone $fighterGroups;
104
105
        $byeGroup = $this->getByeGroup($this->championship, $fighters);
106
107
        // Get biggest competitor's group
108
        $max = $this->getMaxCompetitorByEntity($tmpFighterGroups);
109
110
        // We reacommodate them so that we can mix them up and they don't fight with another competitor of his entity.
111
112
        $competitors = $this->repart($fighterGroups, $max);
113
114
        $competitors = $this->insertByes($competitors, $byeGroup);
115
116
        return $competitors;
117
    }
118
119
    /**
120
     * Calculate the Byes need to fill the Championship Tree.
121
     *
122
     * @param Championship $championship
123
     *
124
     * @return Collection
125
     */
126
    private function getByeGroup(Championship $championship, $fighters)
127
    {
128
        $groupSizeDefault = 3;
129
        $preliminaryGroupSize = 2;
130
131
        $fighterCount = $fighters->count();
132
133
        if ($championship->hasPreliminary()) {
134
            $preliminaryGroupSize = $championship->settings != null
135
                ? $championship->settings->preliminaryGroupSize
136
                : $groupSizeDefault;
137
        } elseif ($championship->isDirectEliminationType()) {
138
            $preliminaryGroupSize = 2;
139
        } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
140
            // No Preliminary and No Direct Elimination --> Round Robin
141
            // Should Have no tree
142
        }
143
        $treeSize = $this->getTreeSize($fighterCount, $preliminaryGroupSize);
144
145
        $byeCount = $treeSize - $fighterCount;
146
147
        return $this->createNullsGroup($byeCount, $championship->category->isTeam);
148
    }
149
150
    /**
151
     * @param $fighterCount
152
     *
153
     * @return int
154
     */
155
    private function getTreeSize($fighterCount, $groupSize)
156
    {
157
        $square = collect([1, 2, 4, 8, 16, 32, 64]);
158
        $squareMultiplied = $square->map(function ($item, $key) use ($groupSize) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
159
            return $item * $groupSize;
160
        });
161
162
        foreach ($squareMultiplied as $limit) {
163
            if ($fighterCount <= $limit) {
164
                return $limit;
165
            }
166
        }
167
168
        return 64 * $groupSize;
169
    }
170
171
    /**
172
     * @param $byeCount
173
     *
174
     * @return Collection
175
     */
176
    private function createNullsGroup($byeCount, $isTeam): Collection
177
    {
178
        $isTeam
179
            ? $null = new Team()
180
            : $null = new Competitor();
181
182
        $byeGroup = new Collection();
183
        for ($i = 0; $i < $byeCount; $i++) {
184
            $byeGroup->push($null);
185
        }
186
187
        return $byeGroup;
188
    }
189
190
    /**
191
     * @param $fighterGroups
192
     * @param int $max
193
     *
194
     * @return Collection
195
     */
196
    private function repart($fighterGroups, $max)
197
    {
198
        $fighters = new Collection();
199
        for ($i = 0; $i < $max; $i++) {
200
            foreach ($fighterGroups as $fighterGroup) {
201
                $fighter = $fighterGroup->values()->get($i);
202
                if ($fighter != null) {
203
                    $fighters->push($fighter);
204
                }
205
            }
206
        }
207
208
        return $fighters;
209
    }
210
211
    /**
212
     * Insert byes in an homogen way.
213
     *
214
     * @param Collection $fighters
215
     * @param Collection $byeGroup
216
     *
217
     * @return Collection
218
     */
219
    private function insertByes(Collection $fighters, Collection $byeGroup)
220
    {
221
        $bye = count($byeGroup) > 0 ? $byeGroup[0] : [];
222
        $sizeFighters = count($fighters);
223
        $sizeGroupBy = count($byeGroup);
224
225
        $frequency = $sizeGroupBy != 0
226
            ? (int)floor($sizeFighters / $sizeGroupBy)
227
            : -1;
228
229
        // Create Copy of $competitors
230
        $newFighters = new Collection();
231
        $i = 0;
232
        $byeCount = 0;
233
        foreach ($fighters as $fighter) {
234
            if ($frequency != -1 && $i % $frequency == 0 && $byeCount < $sizeGroupBy) {
235
                $newFighters->push($bye);
236
                $byeCount++;
237
            }
238
            $newFighters->push($fighter);
239
            $i++;
240
        }
241
242
        return $newFighters;
243
    }
244
245
    private function getFighters()
246
    {
247
        $this->championship->category->isTeam()
248
            ? $fighters = $this->championship->teams
249
            : $fighters = $this->championship->competitors;
250
251
        return $fighters;
252
    }
253
254
    /**
255
     * @param $usersByArea
256
     * @param $area
257
     *
258
     * @return Collection
259
     */
260
    public function generateGroupsForRound($usersByArea, $area, $round)
261
    {
262
        $groups = new Collection();
0 ignored issues
show
Unused Code introduced by
$groups is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
263
        foreach ($usersByArea as $fightersByEntity) {
264
            // Chunking to make small round robin groups
265
            if ($this->championship->hasPreliminary()) {
266
                $fightersGroup = $fightersByEntity->chunk($this->settings->preliminaryGroupSize)->shuffle();
267
            } elseif ($this->championship->isDirectEliminationType() || $round>1) {
268
                $fightersGroup = $fightersByEntity->chunk(2)->shuffle();
269
            } else { // Round Robin
270
                $fightersGroup = $fightersByEntity->chunk($fightersByEntity->count());
271
            }
272
            $order = 1;
273
274
            // Before doing anything, check last group if numUser = 1
275
            foreach ($fightersGroup as $fighters) {
276
                $group = $this->saveGroup($area, $fighters, $order, $round);
277
                $this->tree->push($group);
278
                $order++;
279
            }
280
            $area++;
281
        }
282
    }
283
284
    /**
285
     * @param $area
286
     * @param $fighters
287
     * @param $order
288
     * @param $round
289
     * @return FightersGroup
290
     */
291
    public function saveGroup($area, $fighters, $order, $round)
292
    {
293
        $fighters = $fighters->pluck('id')->shuffle();
294
295
        $group = new FightersGroup();
296
        $group->area = $area;
297
        $group->order = $order;
298
        $group->round = $round;
299
        $group->championship_id = $this->championship->id;
300
        $group->save();
301
302
        // Add all competitors to Pivot Table
303
        if ($this->championship->category->isTeam()) {
304
            $group->syncTeams($fighters);
305
        } else {
306
            $group->syncCompetitors($fighters);
307
        }
308
309
        return $group;
310
    }
311
312
    /**
313
     * @param $numFighters
314
     */
315
    private function pushEmptyGroupsToTree($numFighters)
316
    {
317
        $numRounds = log($numFighters, 2);
318
319
        $roundNumber = 1;
320
        for ($roundNumber += 1; $roundNumber <= $numRounds; $roundNumber++) {
321
            for ($matchNumber = 1; $matchNumber <= ($numFighters / pow(2, $roundNumber)); $matchNumber++) {
322
                $group = new FightersGroup();
323
                $group->championship_id = $this->championship->id;
324
                $group->round = $roundNumber;
325
                $group->area = 1; // Change this
326
                $group->order = 1; // Change this
327
                $this->tree->push($group);
328
                $group->save();
329
            }
330
        }
331
    }
332
}
333