1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Xoco70\KendoTournaments\TreeGen; |
4
|
|
|
|
5
|
|
|
use Xoco70\KendoTournaments\Contracts\TreeGenerable; |
6
|
|
|
|
7
|
|
|
class DirectEliminationTreeGen implements TreeGenerable |
8
|
|
|
{ |
9
|
|
|
public $names; |
10
|
|
|
public $brackets = array(); |
11
|
|
|
public $noTeams; |
12
|
|
|
public $noRounds; |
13
|
|
|
public $playerWrapperHeight = 30; |
14
|
|
|
public $matchWrapperWidth = 150; |
15
|
|
|
public $roundSpacing = 40; |
16
|
|
|
public $matchSpacing = 42; |
17
|
|
|
public $borderWidth = 3; |
18
|
|
|
|
19
|
|
|
public function __construct($names) |
20
|
|
|
{ |
21
|
|
|
|
22
|
|
|
$this->names = $names; |
23
|
|
|
|
24
|
|
|
$this->run(); |
25
|
|
|
|
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
public function run() |
29
|
|
|
{ |
30
|
|
|
$teams = []; |
31
|
|
|
//If no names have been entered, then use numbers |
32
|
|
|
|
33
|
|
|
if ($this->names != '') { |
34
|
|
|
$teams = $this->names; |
35
|
|
|
$this->noTeams = count($teams); |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
|
39
|
|
|
//Calculate the size of the first full round - for example if you have 5 teams, then the first full round will consist of 4 teams |
40
|
|
|
$minimumFirstRoundSize = pow(2, ceil(log($this->noTeams) / log(2))); |
41
|
|
|
$this->noRounds = log($minimumFirstRoundSize, 2); |
42
|
|
|
$noByesToAdd = $minimumFirstRoundSize - $this->noTeams; |
43
|
|
|
|
44
|
|
|
//Add the byes to the teams array |
45
|
|
|
for ($i = 0; $i < $noByesToAdd; $i++) { |
46
|
|
|
$teams[] = null; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
|
50
|
|
|
//Order the teams in a seeded order - this is required regardless of whether it is a seeded tournament or not, as it prevents BYEs playing eachother |
51
|
|
|
// $teams = $this->orderTeamsInSeededOrder($teams); |
52
|
|
|
|
53
|
|
|
$roundNumber = 1; |
54
|
|
|
|
55
|
|
|
//Group 2 teams into a match |
56
|
|
|
$matches = array_chunk($teams, 2); |
57
|
|
|
|
58
|
|
|
// if ($this->noTeams > 2) { |
|
|
|
|
59
|
|
|
// |
60
|
|
|
// foreach ($matches as $key => &$match) { |
61
|
|
|
// |
62
|
|
|
// $matchNumber = $key + 1; |
63
|
|
|
// |
64
|
|
|
// |
65
|
|
|
// //Add the match to the first round |
66
|
|
|
// $this->brackets[$roundNumber][$matchNumber] = $match; |
67
|
|
|
// |
68
|
|
|
// //Set the match to null as the result of the above match hasn't yet been determined |
69
|
|
|
// $match = null; |
70
|
|
|
// |
71
|
|
|
// |
72
|
|
|
// } |
73
|
|
|
// |
74
|
|
|
// //Now all of the blank spaces except the ones awaiting first round results have gone, group the single dimension array into a multiple dimensional array, so opponents share the same parent array |
75
|
|
|
// $matches = array_chunk($matches, 2); |
76
|
|
|
// |
77
|
|
|
// } |
78
|
|
|
|
79
|
|
|
|
80
|
|
|
//If there's already a match in the match array, then that means the next round is round 2, so increase the round number |
81
|
|
|
if (count($this->brackets)) $roundNumber++; |
82
|
|
|
|
83
|
|
|
$countMatches = count($matches); |
84
|
|
|
//Create the first full round of teams, some may be blank if waiting on the results of a previous round |
85
|
|
|
for ($i = 0; $i < $countMatches; $i++) { |
86
|
|
|
$this->brackets[$roundNumber][$i + 1] = $matches[$i]; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
//Create the result of the empty rows for this tournament |
90
|
|
|
|
91
|
|
|
for ($roundNumber += 1; $roundNumber <= $this->noRounds; $roundNumber++) { |
92
|
|
|
for ($matchNumber = 1; $matchNumber <= ($minimumFirstRoundSize / pow(2, $roundNumber)); $matchNumber++) { |
93
|
|
|
$this->brackets[$roundNumber][$matchNumber] = array(null, null); |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
$this->assignPositions(); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
private function assignPositions() |
100
|
|
|
{ |
101
|
|
|
|
102
|
|
|
//Variables required for figuring outing the height of the vertical connectors |
103
|
|
|
|
104
|
|
|
$matchSpacingMultiplier = 0.5; |
105
|
|
|
$playerWrapperHeightMultiplier = 1; |
106
|
|
|
|
107
|
|
|
foreach ($this->brackets as $roundNumber => &$round) { |
108
|
|
|
|
109
|
|
|
foreach ($round as $matchNumber => &$match) { |
110
|
|
|
|
111
|
|
|
//Give teams a nicer index |
112
|
|
|
|
113
|
|
|
$match['playerA'] = $match[0]; |
114
|
|
|
$match['playerB'] = $match[1]; |
115
|
|
|
|
116
|
|
|
unset($match[0]); |
117
|
|
|
unset($match[1]); |
118
|
|
|
|
119
|
|
|
//Figure out the bracket positions |
120
|
|
|
|
121
|
|
|
$match['matchWrapperTop'] = (((2 * $matchNumber) - 1) * (pow(2, ($roundNumber) - 1)) - 1) * (($this->matchSpacing / 2) + $this->playerWrapperHeight); |
122
|
|
|
$match['matchWrapperLeft'] = ($roundNumber - 1) * ($this->matchWrapperWidth + $this->roundSpacing - 1); |
123
|
|
|
$match['vConnectorLeft'] = floor($match['matchWrapperLeft'] + $this->matchWrapperWidth + ($this->roundSpacing / 2) - ($this->borderWidth / 2)); |
124
|
|
|
$match['vConnectorHeight'] = ($matchSpacingMultiplier * $this->matchSpacing) + ($playerWrapperHeightMultiplier * $this->playerWrapperHeight) + $this->borderWidth; |
125
|
|
|
$match['vConnectorTop'] = $match['hConnectorTop'] = $match['matchWrapperTop'] + $this->playerWrapperHeight; |
126
|
|
|
$match['hConnectorLeft'] = ($match['vConnectorLeft'] - ($this->roundSpacing / 2)) + 2; |
127
|
|
|
$match['hConnector2Left'] = $match['matchWrapperLeft'] + $this->matchWrapperWidth + ($this->roundSpacing / 2); |
128
|
|
|
|
129
|
|
|
//Adjust the positions depending on the match number |
130
|
|
|
|
131
|
|
|
if (!($matchNumber % 2)) { |
132
|
|
|
$match['hConnector2Top'] = $match['vConnectorTop'] -= ($match['vConnectorHeight'] - $this->borderWidth); |
133
|
|
|
} else { |
134
|
|
|
$match['hConnector2Top'] = $match['vConnectorTop'] + ($match['vConnectorHeight'] - $this->borderWidth); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
//Update the spacing variables |
140
|
|
|
|
141
|
|
|
$matchSpacingMultiplier *= 2; |
142
|
|
|
$playerWrapperHeightMultiplier *= 2; |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
public function printBrackets() |
149
|
|
|
{ |
150
|
|
|
|
151
|
|
|
// $this->printRoundTitles(); |
|
|
|
|
152
|
|
|
// |
153
|
|
|
// echo '<div id="brackets-wrapper">'; |
154
|
|
|
// |
155
|
|
|
// foreach ($this->brackets as $roundNumber => $round) { |
156
|
|
|
// |
157
|
|
|
// foreach ($round as $matchNumber => $match) { |
158
|
|
|
// |
159
|
|
|
// echo '<div class="match-wrapper" style="top: ' . $match['matchWrapperTop'] . 'px; left: ' . $match['matchWrapperLeft'] . 'px; width: ' . $this->matchWrapperWidth . 'px;"> |
160
|
|
|
// <input type="text" class="score">' |
161
|
|
|
// . $this->getPlayerList($match['playerA']) . |
162
|
|
|
// '<div class="match-divider"> |
163
|
|
|
// </div> |
164
|
|
|
// <input type="text" class="score">' |
165
|
|
|
// . $this->getPlayerList($match['playerB']) . |
166
|
|
|
// '</div>'; |
167
|
|
|
// |
168
|
|
|
// if ($roundNumber != $this->noRounds) { |
169
|
|
|
// |
170
|
|
|
// echo '<div class="vertical-connector" style="top: ' . $match['vConnectorTop'] . 'px; left: ' . $match['vConnectorLeft'] . 'px; height: ' . $match['vConnectorHeight'] . 'px;"></div> |
171
|
|
|
// <div class="horizontal-connector" style="top: ' . $match['hConnectorTop'] . 'px; left: ' . $match['hConnectorLeft'] . 'px;"></div> |
172
|
|
|
// <div class="horizontal-connector" style="top: ' . $match['hConnector2Top'] . 'px; left: ' . $match['hConnector2Left'] . 'px;"></div>'; |
173
|
|
|
// |
174
|
|
|
// } |
175
|
|
|
// |
176
|
|
|
// } |
177
|
|
|
// |
178
|
|
|
// } |
179
|
|
|
// |
180
|
|
|
// echo '</div>'; |
181
|
|
|
|
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Print Round Titles |
186
|
|
|
*/ |
187
|
|
|
public function printRoundTitles() |
188
|
|
|
{ |
189
|
|
|
|
190
|
|
|
if ($this->noTeams == 2) { |
191
|
|
|
|
192
|
|
|
$roundTitles = array('Final'); |
193
|
|
|
|
194
|
|
|
} elseif ($this->noTeams <= 4) { |
195
|
|
|
|
196
|
|
|
$roundTitles = array('Semi-Finals', 'Final'); |
197
|
|
|
|
198
|
|
|
} elseif ($this->noTeams <= 8) { |
199
|
|
|
|
200
|
|
|
$roundTitles = array('Quarter-Finals', 'Semi-Finals', 'Final'); |
201
|
|
|
|
202
|
|
|
} else { |
203
|
|
|
|
204
|
|
|
$roundTitles = array('Quarter-Finals', 'Semi-Finals', 'Final'); |
205
|
|
|
$noRounds = ceil(log($this->noTeams, 2)); |
206
|
|
|
$noTeamsInFirstRound = pow(2, ceil(log($this->noTeams) / log(2))); |
207
|
|
|
$tempRounds = array(); |
208
|
|
|
|
209
|
|
|
//The minus 3 is to ignore the final, semi final and quarter final rounds |
210
|
|
|
|
211
|
|
|
for ($i = 0; $i < $noRounds - 3; $i++) { |
212
|
|
|
$tempRounds[] = 'Last ' . $noTeamsInFirstRound; |
213
|
|
|
$noTeamsInFirstRound /= 2; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
$roundTitles = array_merge($tempRounds, $roundTitles); |
217
|
|
|
|
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
echo '<div id="round-titles-wrapper">'; |
221
|
|
|
|
222
|
|
|
foreach ($roundTitles as $key => $roundTitle) { |
223
|
|
|
|
224
|
|
|
$left = $key * ($this->matchWrapperWidth + $this->roundSpacing - 1); |
225
|
|
|
|
226
|
|
|
echo '<div class="round-title" style="left: ' . $left . 'px;">' . $roundTitle . '</div>'; |
227
|
|
|
|
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
echo '</div>'; |
231
|
|
|
|
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param $selected |
236
|
|
|
* @return string |
237
|
|
|
*/ |
238
|
|
|
public function getPlayerList($selected) |
239
|
|
|
{ |
240
|
|
|
|
241
|
|
|
$html = '<select> |
242
|
|
|
<option' . ($selected == '' ? ' selected' : '') . '></option>'; |
243
|
|
|
foreach (array_merge($this->brackets[1]) as $bracket) { // Bug Fix 24-02-2017 , $this->brackets[2] |
|
|
|
|
244
|
|
View Code Duplication |
if ($bracket['playerA'] != '') { |
|
|
|
|
245
|
|
|
$html .= '<option' . ($selected == $bracket['playerA'] ? ' selected' : '') . '>' . $bracket['playerA'] . '</option>'; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
View Code Duplication |
if ($bracket['playerB'] != '') { |
|
|
|
|
249
|
|
|
$html .= '<option' . ($selected == $bracket['playerB'] ? ' selected' : '') . '>' . $bracket['playerB'] . '</option>'; |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$html .= '</select>'; |
254
|
|
|
|
255
|
|
|
return $html; |
256
|
|
|
|
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @param $teams |
261
|
|
|
* @return array |
262
|
|
|
*/ |
263
|
|
|
private function orderTeamsInSeededOrder($teams): array |
|
|
|
|
264
|
|
|
{ |
265
|
|
|
$logNoTeam = log($this->noTeams / 2, 2); |
266
|
|
|
|
267
|
|
|
for ($i = 0; $i < $logNoTeam; $i++) { |
268
|
|
|
|
269
|
|
|
$out = []; |
270
|
|
|
|
271
|
|
|
foreach ($teams as $player) { |
272
|
|
|
$splice = pow(2, $i); |
273
|
|
|
$out = array_merge($out, array_splice($teams, 0, $splice)); |
274
|
|
|
$out = array_merge($out, array_splice($teams, -$splice)); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
$teams = $out; |
278
|
|
|
|
279
|
|
|
} |
280
|
|
|
return $teams; |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
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.