Test Failed
Push — master ( 2841c5...928d31 )
by Julien
04:15
created

TreeGen::getByeGroup()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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...
272
                $groups->push($group);
273
                $order++;
274
            }
275
            $area++;
276
        }
277
278
        return $groups;
279
    }
280
281
    /**
282
     * @param $area
283
     * @param $fighters
284
     * @param $order
285
     *
286
     * @return FightersGroup
287
     */
288
    public function saveGroup($area, $fighters, $order)
289
    {
290
        $fighters = $fighters->pluck('id')->shuffle();
291
292
        $group = new FightersGroup();
293
        $group->area = $area;
294
        $group->order = $order;
295
        $group->championship_id = $this->championship->id;
296
297
        $group->save();
298
299
        // Add all competitors to Pivot Table
300
        if ($this->championship->category->isTeam()) {
301
            $group->syncTeams($fighters);
302
        } else {
303
            $group->syncCompetitors($fighters);
304
        }
305
306
        return $group;
307
    }
308
}
309