Test Failed
Push — master ( 028749...4233d7 )
by Julien
03:35
created

TreeGen::generateAllRounds()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 7
nop 2
1
<?php
2
3
4
namespace Xoco70\KendoTournaments\TreeGen;
5
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\Config;
8
use Xoco70\KendoTournaments\Contracts\TreeGenerable;
9
use Xoco70\KendoTournaments\Exceptions\TreeGenerationException;
10
use Xoco70\KendoTournaments\Models\Championship;
11
use Xoco70\KendoTournaments\Models\Competitor;
12
use Xoco70\KendoTournaments\Models\Round;
13
use Xoco70\KendoTournaments\Models\Team;
14
15
class TreeGen implements TreeGenerable
16
{
17
18
    protected $groupBy;
19
    public $championship, $settings;
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...
20
21
22
    public function __construct(Championship $championship, $groupBy, $settings)
23
    {
24
        $this->championship = $championship;
25
        $this->groupBy = $groupBy;
26
        $this->settings = $settings;
27
    }
28
29
    /**
30
     * Generate tree groups for a championship
31
     * @return Collection
32
     * @throws TreeGenerationException
33
     */
34
    public function run()
35
    {
36
        // If previous trees already exist, delete all
37
        $this->championship->rounds()->delete();
38
        $areas = $this->settings->fightingAreas;
39
        $fighters = $this->getFighters();
40
41
        if ($fighters->count() / $areas < config('kendo-tournaments.MIN_COMPETITORS_X_AREA')) {
42
            throw new TreeGenerationException();
43
        }
44
        // Get Competitor's / Team list ordered by entities ( Federation, Assoc, Club, etc...)
45
        $fightersByEntity = $this->getFightersByEntity($fighters);
46
47
        // Chunk user by areas
48
49
        $usersByArea = $fightersByEntity->chunk(sizeof($fightersByEntity) / $areas);
50
51
        $area = 1;
52
53
        // loop on areas
54
        $tree = $this->generateAllRounds($usersByArea, $area);
55
56
        return $tree;
57
    }
58
59
    /**
60
     * @param $userGroups
61
     * @return int
62
     */
63
    private function getMaxCompetitorByEntity($userGroups): int
64
    {
65
        // Surely there is a Laravel function that does it ;)
66
        $max = 0;
67
        foreach ($userGroups as $userGroup) {
68
            if (sizeof($userGroup) > $max) {
69
                $max = sizeof($userGroup);
70
            }
71
72
        }
73
        return $max;
74
    }
75
76
    /**
77
     * Get Competitor's list ordered by entities
78
     * Countries for Internation Tournament, State for a National Tournament, etc
79
     * @return Collection
80
     */
81
    private function getFightersByEntity($fighters): Collection
82
    {
83
        // Right now, we are treating users and teams as equals.
84
        // It doesn't matter right now, because we only need name attribute which is common to both models
85
86
        // $this->groupBy contains federation_id, association_id, club_id, etc.
87
        if (($this->groupBy) != null) {
88
            $fighterGroups = $fighters->groupBy($this->groupBy); // Collection of Collection
89
        } else {
90
            $fighterGroups = $fighters->chunk(1); // Collection of Collection
91
        }
92
93
94
        $tmpFighterGroups = clone $fighterGroups;
95
96
        $byeGroup = $this->getByeGroup($this->championship, $fighters);
97
98
99
        // Get biggest competitor's group
100
        $max = $this->getMaxCompetitorByEntity($tmpFighterGroups);
101
102
        // We reacommodate them so that we can mix them up and they don't fight with another competitor of his entity.
103
104
        $competitors = $this->repart($fighterGroups, $max);
105
106
        $competitors = $this->insertByes($competitors, $byeGroup);
107
108
        return $competitors;
109
110
    }
111
112
    /**
113
     * Calculate the Byes need to fill the Championship Tree
114
     * @param Championship $championship
115
     * @return Collection
116
     */
117
    private function getByeGroup(Championship $championship, $fighters)
118
    {
119
        $groupSizeDefault = 3;
120
        $preliminaryGroupSize = 2;
121
122
        $fighterCount = $fighters->count();
123
124
        if ($championship->hasPreliminary()) {
125
            $preliminaryGroupSize = $championship->settings != null
126
                ? $championship->settings->preliminaryGroupSize
127
                : $groupSizeDefault;
128
        } else if ($championship->isDirectEliminationType()) {
129
            $preliminaryGroupSize = 2;
130
131
        } 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...
132
            // No Preliminary and No Direct Elimination --> Round Robin
133
            // Should Have no tree
134
        }
135
        $treeSize = $this->getTreeSize($fighterCount, $preliminaryGroupSize);
136
137
        $byeCount = $treeSize - $fighterCount;
138
        return $this->createNullsGroup($byeCount, $championship->category->isTeam);
139
    }
140
141
    /**
142
     * @param $fighterCount
143
     * @return integer
144
     */
145
    private function getTreeSize($fighterCount, $groupSize)
146
    {
147
        $square = collect([1, 2, 4, 8, 16, 32, 64]);
148
        $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...
149
            return $item * $groupSize;
150
        });
151
152
153
        foreach ($squareMultiplied as $limit) {
154
            if ($fighterCount <= $limit) {
155
156
                return $limit;
157
            }
158
        }
159
        return 64 * $groupSize;
160
161
    }
162
163
    /**
164
     * @param $byeCount
165
     * @return Collection
166
     */
167
    private function createNullsGroup($byeCount, $isTeam): Collection
168
    {
169
        $isTeam
170
            ? $null = new Team()
171
            : $null = new Competitor();
172
173
        $byeGroup = new Collection();
174
        for ($i = 0; $i < $byeCount; $i++) {
175
            $byeGroup->push($null);
176
        }
177
        return $byeGroup;
178
    }
179
180
    /**
181
     * @param $fighterGroups
182
     * @param $max
183
     * @return Collection
184
     */
185
    private function repart($fighterGroups, $max)
186
    {
187
        $fighters = new Collection;
188
        for ($i = 0; $i < $max; $i++) {
189
            foreach ($fighterGroups as $fighterGroup) {
190
                $fighter = $fighterGroup->values()->get($i);
191
                if ($fighter != null) {
192
                    $fighters->push($fighter);
193
                }
194
            }
195
        }
196
        return $fighters;
197
    }
198
199
    /**
200
     * Insert byes in an homogen way
201
     * @param Collection $fighters
202
     * @param Collection $byeGroup
203
     * @return Collection
204
     */
205
    private function insertByes(Collection $fighters, Collection $byeGroup)
206
    {
207
        $bye = sizeof($byeGroup) > 0 ? $byeGroup[0] : [];
208
        $sizeFighters = sizeof($fighters);
209
        $sizeGroupBy = sizeof($byeGroup);
210
211
        $frequency = $sizeGroupBy != 0
212
            ? (int)floor($sizeFighters / $sizeGroupBy)
213
            : -1;
214
215
        // Create Copy of $competitors
216
        $newFighters = new Collection;
217
        $i = 0;
218
        $byeCount = 0;
219
        foreach ($fighters as $fighter) {
220
221
            if ($frequency != -1 && $i % $frequency == 0 && $byeCount < $sizeGroupBy) {
222
                $newFighters->push($bye);
223
                $byeCount++;
224
            }
225
            $newFighters->push($fighter);
226
            $i++;
227
228
        }
229
230
        return $newFighters;
231
    }
232
233
    private function getFighters()
234
    {
235
        $this->championship->category->isTeam()
236
            ? $fighters = $this->championship->teams
237
            : $fighters = $this->championship->competitors;
238
        return $fighters;
239
    }
240
241
    /**
242
     * @param $usersByArea
243
     * @param $area
244
     * @return Collection
245
     */
246
    public function generateAllRounds($usersByArea, $area)
247
    {
248
        $rounds = new Collection();
249
        foreach ($usersByArea as $fightersByEntity) {
250
251
            // Chunking to make small round robin groups
252
            if ($this->championship->hasPreliminary()) {
253
                $fightersGroup = $fightersByEntity->chunk($this->settings->preliminaryGroupSize)->shuffle();
254
255
            } else if ($this->championship->isDirectEliminationType()) {
256
                $fightersGroup = $fightersByEntity->chunk(2)->shuffle();
257
            } else {
258
                $fightersGroup = $fightersByEntity->chunk($fightersByEntity->count());
259
            }
260
261
            $order = 1;
262
263
            // Before doing anything, check last group if numUser = 1
264
            foreach ($fightersGroup as $fighters) {
265
                $round = $this->saveRound($area, $fighters, $order, $rounds);
0 ignored issues
show
Unused Code introduced by
The call to TreeGen::saveRound() has too many arguments starting with $rounds.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
266
                $rounds->push($round);
267
                $order++;
268
            }
269
            $area++;
270
        }
271
        return $rounds;
272
    }
273
274
    /**
275
     * @param $area
276
     * @param $fighters
277
     * @param $order
278
     * @return Round
279
     */
280
    public function saveRound($area, $fighters, $order)
281
    {
282
        $fighters = $fighters->pluck('id')->shuffle();
283
284
        $round = new Round;
285
        $round->area = $area;
286
        $round->order = $order;
287
        $round->championship_id = $this->championship->id;
288
289
        $round->save();
290
291
        // Add all competitors to Pivot Table
292
        if ($this->championship->category->isTeam()) {
293
            $round->syncTeams($fighters);
294
        } else {
295
            $round->syncCompetitors($fighters);
296
        }
297
        return $round;
298
    }
299
300
301
}