Issues (24)

src/TreeGen/TreeGen.php (3 issues)

1
<?php
2
3
namespace Xoco70\LaravelTournaments\TreeGen;
4
5
use Illuminate\Support\Collection;
6
use Xoco70\LaravelTournaments\Contracts\TreeGenerable;
7
use Xoco70\LaravelTournaments\Exceptions\TreeGenerationException;
8
use Xoco70\LaravelTournaments\Models\Championship;
9
use Xoco70\LaravelTournaments\Models\Fight;
10
use Xoco70\LaravelTournaments\Models\FightersGroup;
11
12
abstract class TreeGen implements TreeGenerable
13
{
14
    protected $groupBy;
15
    protected $tree;
16
    public $championship;
17
    public $settings;
18
    protected $numFighters;
19
20
    abstract protected function generateAllTrees();
21
22
    abstract protected function generateFights();
23
24
    abstract protected function addFighterToGroup(FightersGroup $group, $fighter, $fighterToUpdate);
25
26
    abstract protected function getByeGroup($fighters);
27
28
    abstract protected function getNumRounds($fightersCount);
29
30
    abstract protected function chunk(Collection $fightersByEntity);
31
32
    abstract protected function syncGroup(FightersGroup $group, $fighters);
33
34
    abstract protected function generateGroupsForRound(Collection $fightersByArea, $round);
35
36
    /**
37
     * @param Championship $championship
38
     * @param $groupBy
39
     */
40
    public function __construct(Championship $championship, $groupBy)
41
    {
42
        $this->championship = $championship;
43
        $this->groupBy = $groupBy;
44
        $this->settings = $championship->getSettings();
45
        $this->tree = new Collection();
46
    }
47
48
    /**
49
     * Generate tree groups for a championship.
50
     *
51
     * @throws TreeGenerationException
52
     */
53
    public function run()
54
    {
55
        $this->championship->fightersGroups()->delete();
56
        $this->generateAllTrees();
57
        $this->generateAllFights();
58
    }
59
60
    /**
61
     * Get Competitor's list ordered by entities
62
     * Countries for Internation Tournament, State for a National Tournament, etc.
63
     *
64
     * @param $fighters
65
     *
66
     * @return Collection
67
     */
68
    private function getFightersByEntity($fighters): Collection
69
    {
70
        // Right now, we are treating users and teams as equals.
71
        // It doesn't matter right now, because we only need name attribute which is common to both models
72
73
        // $this->groupBy contains federation_id, association_id, club_id, etc.
74
        if (($this->groupBy) != null) {
75
            return $fighters->groupBy($this->groupBy); // Collection of Collection
76
        }
77
78
        return $fighters->chunk(1); // Collection of Collection
79
    }
80
81
    /**
82
     * Get the size the first round will have.
83
     *
84
     * @param $fighterCount
85
     * @param $groupSize
86
     *
87
     * @return int
88
     */
89
    protected function getTreeSize($fighterCount, $groupSize)
90
    {
91
        $squareMultiplied = collect([1, 2, 4, 8, 16, 32, 64])
92
            ->map(function ($item) use ($groupSize) {
93
                return $item * $groupSize;
94
            }); // [4, 8, 16, 32, 64,...]
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
95
96
        foreach ($squareMultiplied as $limit) {
97
            if ($fighterCount <= $limit) {
98
                $treeSize = $limit;
99
                $numAreas = $this->settings->fightingAreas;
100
                $fighterCountPerArea = $treeSize / $numAreas;
101
                if ($fighterCountPerArea < $groupSize) {
102
                    $treeSize = $treeSize * $numAreas;
103
                }
104
105
                return $treeSize;
106
            }
107
        }
108
109
        return 64 * $groupSize;
110
    }
111
112
    /**
113
     * @param $order
114
     * @param $round
115
     * @param $parent
116
     *
117
     * @return FightersGroup
118
     */
119
    protected function saveGroup($order, $round, $parent): FightersGroup
120
    {
121
        $group = new FightersGroup();
122
        $this->championship->isSingleEliminationType()
123
            ? $group->area = $this->getNumArea($round, $order)
124
            : $group->area = 1; // Area limited to 1 in playoff
125
126
        $group->order = $order;
127
        $group->round = $round;
128
        $group->championship_id = $this->championship->id;
129
        if ($parent != null) {
130
            $group->parent_id = $parent->id;
131
        }
132
        $group->save();
133
134
        return $group;
135
    }
136
137
    /**
138
     * @param int $groupSize
139
     *
140
     * @return Collection
141
     */
142
    public function createByeGroup($groupSize): Collection
143
    {
144
        $byeFighter = $this->createByeFighter();
145
        $group = new Collection();
146
        for ($i = 0; $i < $groupSize; $i++) {
147
            $group->push($byeFighter);
148
        }
149
150
        return $group;
151
    }
152
153
    /**
154
     * Get All Groups on previous round.
155
     *
156
     * @param $currentRound
157
     *
158
     * @return Collection
159
     */
160
    private function getPreviousRound($currentRound)
161
    {
162
        $previousRound = $this->championship->groupsByRound($currentRound + 1)->get();
163
164
        return $previousRound;
165
    }
166
167
    /**
168
     * Get the next group on the right ( parent ), final round being the ancestor.
169
     *
170
     * @param $matchNumber
171
     * @param Collection $previousRound
172
     *
173
     * @return mixed
174
     */
175
    private function getParentGroup($matchNumber, $previousRound)
176
    {
177
        $parentIndex = intval(($matchNumber + 1) / 2);
178
        $parent = $previousRound->get($parentIndex - 1);
179
180
        return $parent;
181
    }
182
183
    /**
184
     * Group Fighters by area.
185
     * Here is where we fill with empty fighters.
186
     *
187
     * @throws TreeGenerationException
188
     *
189
     * @return Collection
190
     */
191
    protected function getFightersByArea()
192
    {
193
        $areas = $this->settings->fightingAreas;
194
        $fighters = $this->getFighters();   // Get Competitor or Team Objects
195
        $fighterByEntity = $this->getFightersByEntity($fighters);   // Chunk it by entities (Fede, Assoc, Club,...)
196
        $fightersWithBye = $this->adjustFightersGroupWithByes($fighters, $fighterByEntity);     // Fill with Byes
197
        return $fightersWithBye->chunk(count($fightersWithBye) / $areas);   // Chunk user by areas
198
    }
199
200
    /**
201
     * Logically build the tree ( attach a parent to every child for nestedSet Navigation ).
202
     *
203
     * @param $numFighters
204
     */
205
    protected function addParentToChildren($numFighters)
206
    {
207
        $numRounds = $this->getNumRounds($numFighters);
208
        $groupsDesc = $this->championship
209
            ->fightersGroups()
210
            ->where('round', '<', $numRounds)
211
            ->orderByDesc('id')->get();
212
213
        $groupsDescByRound = $groupsDesc->groupBy('round');
214
215
        foreach ($groupsDescByRound as $round => $groups) {
216
            $previousRound = $this->getPreviousRound($round);
217
            foreach ($groups->reverse()->values() as $matchNumber => $group) {
218
                $parent = $this->getParentGroup($matchNumber + 1, $previousRound);
219
                $group->parent_id = $parent->id;
220
                $group->save();
221
            }
222
        }
223
    }
224
225
    /**
226
     * Destroy Previous Fights for demo.
227
     */
228
    protected function destroyPreviousFights()
229
    {
230
        // Delete previous fight for this championship
231
        $arrGroupsId = $this->championship->fightersGroups()->get()->pluck('id');
232
        if (count($arrGroupsId) > 0) {
233
            Fight::destroy($arrGroupsId);
234
        }
235
    }
236
237
    /**
238
     * Generate Fights for next rounds.
239
     */
240
    public function generateNextRoundsFights()
241
    {
242
        $fightersCount = $this->championship->competitors->count() + $this->championship->teams->count();
243
        $maxRounds = $this->getNumRounds($fightersCount);
244
        for ($numRound = 1; $numRound < $maxRounds; $numRound++) {
245
            $groupsByRound = $this->championship->fightersGroups()->where('round', $numRound)->with('parent', 'children')->get();
246
            $this->updateParentFight($groupsByRound); // should be groupsByRound
247
        }
248
    }
249
250
    /**
251
     * @param $groupsByRound
252
     */
253
    protected function updateParentFight($groupsByRound)
254
    {
255
        foreach ($groupsByRound as $keyGroup => $group) {
256
            $parentGroup = $group->parent;
257
            if ($parentGroup == null) {
258
                break;
259
            }
260
            $parentFight = $parentGroup->fights->get(0);
261
262
            // determine whether c1 or c2 must be updated
263
            $this->chooseAndUpdateParentFight($keyGroup, $group, $parentFight);
264
        }
265
    }
266
267
    /**
268
     * @param $group
269
     * @param $parentFight
270
     */
271
    protected function chooseAndUpdateParentFight($keyGroup, FightersGroup $group, Fight $parentFight)
272
    {
273
        // we need to know if the child has empty fighters, is this BYE or undetermined
274
        if ($group->hasDeterminedParent()) {
275
            $valueToUpdate = $group->getValueToUpdate(); // This should be OK
276
            if ($valueToUpdate != null) {
277
                $fighterToUpdate = $group->getParentFighterToUpdate($keyGroup);
278
                $parentFight->$fighterToUpdate = $valueToUpdate;
279
                $parentFight->save();
280
                // Add fighter to pivot table
281
                $parentGroup = $parentFight->group;
282
283
                $fighter = $this->getFighter($valueToUpdate);
284
                $this->addFighterToGroup($parentGroup, $fighter, $fighterToUpdate);
285
            }
286
        }
287
    }
288
289
    /**
290
     * Calculate the area of the group ( group is still not created ).
291
     *
292
     * @param $round
293
     * @param $order
294
     *
295
     * @return int
296
     */
297
    protected function getNumArea($round, $order)
298
    {
299
        $totalAreas = $this->settings->fightingAreas;
300
        $numFighters = $this->championship->fighters->count(); // 4
301
        $numGroups = $this->getTreeSize($numFighters, $this->championship->getGroupSize()) / $this->championship->getGroupSize(); // 1 -> 1
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
302
303
        $areaSize = $numGroups / ($totalAreas * pow(2, $round - 1));
304
305
        $numArea = intval(ceil($order / $areaSize)); // if round == 4, and second match 2/2 = 1 BAD
306
        return $numArea;
307
    }
308
309
    protected function generateAllFights()
310
    {
311
        $this->generateFights(); // Abstract
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
312
        $this->generateNextRoundsFights();
313
        Fight::generateFightsId($this->championship);
314
    }
315
}
316