Test Failed
Push — master ( 2d5b3a...83ab01 )
by Julien
03:19
created

TreeGen::repart()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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