Test Failed
Push — master ( 0463aa...316b3e )
by Julien
11:52
created

TreeGen::run()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
rs 8.8571
cc 2
eloc 16
nc 2
nop 0
1
<?php
2
3
namespace Xoco70\KendoTournaments\TreeGen;
4
5
use Illuminate\Support\Collection;
6
use Xoco70\KendoTournaments\Contracts\TreeGenerable;
7
use Xoco70\KendoTournaments\Exceptions\TreeGenerationException;
8
use Xoco70\KendoTournaments\Models\Championship;
9
use Xoco70\KendoTournaments\Models\ChampionshipSettings;
10
use Xoco70\KendoTournaments\Models\Competitor;
11
use Xoco70\KendoTournaments\Models\FightersGroup;
12
use Xoco70\KendoTournaments\Models\Team;
13
14
class TreeGen implements TreeGenerable
15
{
16
    protected $groupBy;
17
    public $championship;
18
    public $settings;
19
20
    /**
21
     * @param \Xoco70\KendoTournaments\Models\ChampionshipSettings $settings
22
     */
23
    public function __construct(Championship $championship, $groupBy, $settings)
24
    {
25
        $this->championship = $championship;
26
        $this->groupBy = $groupBy;
27
        $this->settings = $settings;
28
    }
29
30
    /**
31
     * Generate tree groups for a championship.
32
     *
33
     * @throws TreeGenerationException
34
     *
35
     * @return Collection
36
     */
37
    public function run()
38
    {
39
        // If previous trees already exist, delete all
40
        $this->championship->fightersGroups()->delete();
41
        $areas = $this->settings->fightingAreas;
42
        $fighters = $this->getFighters();
43
44
        if ($fighters->count() / $areas < ChampionshipSettings::MIN_COMPETITORS_BY_AREA) {
45
            throw new TreeGenerationException();
46
        }
47
        // Get Competitor's / Team list ordered by entities ( Federation, Assoc, Club, etc...)
48
        $fightersByEntity = $this->getFightersByEntity($fighters);
49
50
        // Chunk user by areas
51
52
        $usersByArea = $fightersByEntity->chunk(count($fightersByEntity) / $areas);
53
54
        $area = 1;
55
        $round = 1;
56
        $winnersPerGroup = $this->settings->preliminaryWinner;
57
        // loop on areas
58
        $tree = $this->generateGroupsForFirstRound($usersByArea, $area, $round );
59
        $round++;
60
61
        $numGroups = sizeof($tree);
62
        $numActiveFighters = $numGroups * $winnersPerGroup;
0 ignored issues
show
Unused Code introduced by
$numActiveFighters 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...
63
64
65
66
        return $tree;
67
    }
68
69
    /**
70
     * @param $userGroups
71
     *
72
     * @return int
73
     */
74
    private function getMaxCompetitorByEntity($userGroups): int
75
    {
76
        // Surely there is a Laravel function that does it ;)
77
        $max = 0;
78
        foreach ($userGroups as $userGroup) {
79
            if (count($userGroup) > $max) {
80
                $max = count($userGroup);
81
            }
82
        }
83
84
        return $max;
85
    }
86
87
    /**
88
     * Get Competitor's list ordered by entities
89
     * Countries for Internation Tournament, State for a National Tournament, etc.
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
            $fighterGroups = $fighters->groupBy($this->groupBy); // Collection of Collection
101
        } else {
102
            $fighterGroups = $fighters->chunk(1); // Collection of Collection
103
        }
104
105
        $tmpFighterGroups = clone $fighterGroups;
106
107
        $byeGroup = $this->getByeGroup($this->championship, $fighters);
108
109
        // Get biggest competitor's group
110
        $max = $this->getMaxCompetitorByEntity($tmpFighterGroups);
111
112
        // We reacommodate them so that we can mix them up and they don't fight with another competitor of his entity.
113
114
        $competitors = $this->repart($fighterGroups, $max);
115
116
        $competitors = $this->insertByes($competitors, $byeGroup);
117
118
        return $competitors;
119
    }
120
121
    /**
122
     * Calculate the Byes need to fill the Championship Tree.
123
     *
124
     * @param Championship $championship
125
     *
126
     * @return Collection
127
     */
128
    private function getByeGroup(Championship $championship, $fighters)
129
    {
130
        $groupSizeDefault = 3;
131
        $preliminaryGroupSize = 2;
132
133
        $fighterCount = $fighters->count();
134
135
        if ($championship->hasPreliminary()) {
136
            $preliminaryGroupSize = $championship->settings != null
137
                ? $championship->settings->preliminaryGroupSize
138
                : $groupSizeDefault;
139
        } elseif ($championship->isDirectEliminationType()) {
140
            $preliminaryGroupSize = 2;
141
        } 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...
142
            // No Preliminary and No Direct Elimination --> Round Robin
143
            // Should Have no tree
144
        }
145
        $treeSize = $this->getTreeSize($fighterCount, $preliminaryGroupSize);
146
147
        $byeCount = $treeSize - $fighterCount;
148
149
        return $this->createNullsGroup($byeCount, $championship->category->isTeam);
150
    }
151
152
    /**
153
     * @param $fighterCount
154
     *
155
     * @return int
156
     */
157
    private function getTreeSize($fighterCount, $groupSize)
158
    {
159
        $square = collect([1, 2, 4, 8, 16, 32, 64]);
160
        $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...
161
            return $item * $groupSize;
162
        });
163
164
        foreach ($squareMultiplied as $limit) {
165
            if ($fighterCount <= $limit) {
166
                return $limit;
167
            }
168
        }
169
170
        return 64 * $groupSize;
171
    }
172
173
    /**
174
     * @param $byeCount
175
     *
176
     * @return Collection
177
     */
178
    private function createNullsGroup($byeCount, $isTeam): Collection
179
    {
180
        $isTeam
181
            ? $null = new Team()
182
            : $null = new Competitor();
183
184
        $byeGroup = new Collection();
185
        for ($i = 0; $i < $byeCount; $i++) {
186
            $byeGroup->push($null);
187
        }
188
189
        return $byeGroup;
190
    }
191
192
    /**
193
     * @param $fighterGroups
194
     * @param int $max
195
     *
196
     * @return Collection
197
     */
198
    private function repart($fighterGroups, $max)
199
    {
200
        $fighters = new Collection();
201
        for ($i = 0; $i < $max; $i++) {
202
            foreach ($fighterGroups as $fighterGroup) {
203
                $fighter = $fighterGroup->values()->get($i);
204
                if ($fighter != null) {
205
                    $fighters->push($fighter);
206
                }
207
            }
208
        }
209
210
        return $fighters;
211
    }
212
213
    /**
214
     * Insert byes in an homogen way.
215
     *
216
     * @param Collection $fighters
217
     * @param Collection $byeGroup
218
     *
219
     * @return Collection
220
     */
221
    private function insertByes(Collection $fighters, Collection $byeGroup)
222
    {
223
        $bye = count($byeGroup) > 0 ? $byeGroup[0] : [];
224
        $sizeFighters = count($fighters);
225
        $sizeGroupBy = count($byeGroup);
226
227
        $frequency = $sizeGroupBy != 0
228
            ? (int)floor($sizeFighters / $sizeGroupBy)
229
            : -1;
230
231
        // Create Copy of $competitors
232
        $newFighters = new Collection();
233
        $i = 0;
234
        $byeCount = 0;
235
        foreach ($fighters as $fighter) {
236
            if ($frequency != -1 && $i % $frequency == 0 && $byeCount < $sizeGroupBy) {
237
                $newFighters->push($bye);
238
                $byeCount++;
239
            }
240
            $newFighters->push($fighter);
241
            $i++;
242
        }
243
244
        return $newFighters;
245
    }
246
247
    private function getFighters()
248
    {
249
        $this->championship->category->isTeam()
250
            ? $fighters = $this->championship->teams
251
            : $fighters = $this->championship->competitors;
252
253
        return $fighters;
254
    }
255
256
    /**
257
     * @param $usersByArea
258
     * @param $area
259
     *
260
     * @return Collection
261
     */
262
    public function generateGroupsForFirstRound($usersByArea, $area, $round)
263
    {
264
        $groups = new Collection();
265
        foreach ($usersByArea as $fightersByEntity) {
266
            // Chunking to make small round robin groups
267
            if ($this->championship->hasPreliminary()) {
268
                $fightersGroup = $fightersByEntity->chunk($this->settings->preliminaryGroupSize)->shuffle();
269
            } elseif ($this->championship->isDirectEliminationType()) {
270
                $fightersGroup = $fightersByEntity->chunk(2)->shuffle();
271
            } else { // Round Robin
272
                $fightersGroup = $fightersByEntity->chunk($fightersByEntity->count());
273
            }
274
275
            $order = 1;
276
277
            // Before doing anything, check last group if numUser = 1
278
            foreach ($fightersGroup as $fighters) {
279
                $group = $this->saveGroup($area, $fighters, $order, $round);
280
                $groups->push($group);
281
                $order++;
282
            }
283
            $area++;
284
        }
285
286
        return $groups;
287
    }
288
289
    /**
290
     * @param $area
291
     * @param $fighters
292
     * @param $order
293
     * @param $round
294
     * @return FightersGroup
295
     */
296
    public function saveGroup($area, $fighters, $order,$round)
297
    {
298
        $fighters = $fighters->pluck('id')->shuffle();
299
300
        $group = new FightersGroup();
301
        $group->area = $area;
302
        $group->order = $order;
303
        $group->round = $round;
304
        $group->championship_id = $this->championship->id;
305
        $group->save();
306
307
        // Add all competitors to Pivot Table
308
        if ($this->championship->category->isTeam()) {
309
            $group->syncTeams($fighters);
310
        } else {
311
            $group->syncCompetitors($fighters);
312
        }
313
314
        return $group;
315
    }
316
}
317