1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Xoco70\KendoTournaments\TreeGen; |
4
|
|
|
|
5
|
|
|
use Xoco70\KendoTournaments\Contracts\TreeGenerable; |
6
|
|
|
|
7
|
|
|
class DirectEliminationTreeGen implements TreeGenerable |
8
|
|
|
{ |
9
|
|
|
private $names; |
10
|
|
|
private $brackets = array(); |
11
|
|
|
private $noTeams; |
12
|
|
|
private $noRounds; |
13
|
|
|
private $playerWrapperHeight = 30; |
14
|
|
|
private $matchWrapperWidth = 150; |
15
|
|
|
private $roundSpacing = 40; |
16
|
|
|
private $matchSpacing = 42; |
17
|
|
|
private $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
|
|
|
|
43
|
|
|
|
44
|
|
|
//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 |
45
|
|
|
$teams = $this->orderTeamsInSeededOrder($teams); |
46
|
|
|
|
47
|
|
|
$roundNumber = 1; |
48
|
|
|
|
49
|
|
|
//Group 2 teams into a match |
50
|
|
|
$matches = array_chunk($teams, 2); |
51
|
|
|
|
52
|
|
|
|
53
|
|
|
//If there's already a match in the match array, then that means the next round is round 2, so increase the round number |
54
|
|
|
if (count($this->brackets)) $roundNumber++; |
55
|
|
|
$countMatches = count($matches); |
56
|
|
|
//Create the first full round of teams, some may be blank if waiting on the results of a previous round |
57
|
|
|
for ($i = 0; $i < $countMatches; $i++) { |
58
|
|
|
$this->brackets[$roundNumber][$i + 1] = $matches[$i]; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
$this->assignPositions(); |
62
|
|
|
|
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
private function assignPositions() |
66
|
|
|
{ |
67
|
|
|
|
68
|
|
|
//Variables required for figuring outing the height of the vertical connectors |
69
|
|
|
|
70
|
|
|
$matchSpacingMultiplier = 0.5; |
71
|
|
|
$playerWrapperHeightMultiplier = 1; |
72
|
|
|
|
73
|
|
|
foreach ($this->brackets as $roundNumber => &$round) { |
74
|
|
|
|
75
|
|
|
foreach ($round as $matchNumber => &$match) { |
76
|
|
|
|
77
|
|
|
//Give teams a nicer index |
78
|
|
|
|
79
|
|
|
$match['playerA'] = $match[0]; |
80
|
|
|
$match['playerB'] = $match[1]; |
81
|
|
|
|
82
|
|
|
unset($match[0]); |
83
|
|
|
unset($match[1]); |
84
|
|
|
|
85
|
|
|
//Figure out the bracket positions |
86
|
|
|
|
87
|
|
|
$match['matchWrapperTop'] = (((2 * $matchNumber) - 1) * (pow(2, ($roundNumber) - 1)) - 1) * (($this->matchSpacing / 2) + $this->playerWrapperHeight); |
88
|
|
|
$match['matchWrapperLeft'] = ($roundNumber - 1) * ($this->matchWrapperWidth + $this->roundSpacing - 1); |
89
|
|
|
$match['vConnectorLeft'] = floor($match['matchWrapperLeft'] + $this->matchWrapperWidth + ($this->roundSpacing / 2) - ($this->borderWidth / 2)); |
90
|
|
|
$match['vConnectorHeight'] = ($matchSpacingMultiplier * $this->matchSpacing) + ($playerWrapperHeightMultiplier * $this->playerWrapperHeight) + $this->borderWidth; |
91
|
|
|
$match['vConnectorTop'] = $match['hConnectorTop'] = $match['matchWrapperTop'] + $this->playerWrapperHeight; |
92
|
|
|
$match['hConnectorLeft'] = ($match['vConnectorLeft'] - ($this->roundSpacing / 2)) + 2; |
93
|
|
|
$match['hConnector2Left'] = $match['matchWrapperLeft'] + $this->matchWrapperWidth + ($this->roundSpacing / 2); |
94
|
|
|
|
95
|
|
|
//Adjust the positions depending on the match number |
96
|
|
|
|
97
|
|
|
if (!($matchNumber % 2)) { |
98
|
|
|
$match['hConnector2Top'] = $match['vConnectorTop'] -= ($match['vConnectorHeight'] - $this->borderWidth); |
99
|
|
|
} else { |
100
|
|
|
$match['hConnector2Top'] = $match['vConnectorTop'] + ($match['vConnectorHeight'] - $this->borderWidth); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
//Update the spacing variables |
106
|
|
|
|
107
|
|
|
$matchSpacingMultiplier *= 2; |
108
|
|
|
$playerWrapperHeightMultiplier *= 2; |
109
|
|
|
|
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
public function printBrackets() |
115
|
|
|
{ |
116
|
|
|
|
117
|
|
|
$this->printRoundTitles(); |
118
|
|
|
|
119
|
|
|
echo '<div id="brackets-wrapper">'; |
120
|
|
|
|
121
|
|
|
foreach ($this->brackets as $roundNumber => $round) { |
122
|
|
|
|
123
|
|
|
foreach ($round as $matchNumber => $match) { |
124
|
|
|
|
125
|
|
|
echo '<div class="match-wrapper" style="top: ' . $match['matchWrapperTop'] . 'px; left: ' . $match['matchWrapperLeft'] . 'px; width: ' . $this->matchWrapperWidth . 'px;"> |
126
|
|
|
<input type="text" class="score">' |
127
|
|
|
. $this->getPlayerList($match['playerA']) . |
128
|
|
|
'<div class="match-divider"> |
129
|
|
|
</div> |
130
|
|
|
<input type="text" class="score">' |
131
|
|
|
. $this->getPlayerList($match['playerB']) . |
132
|
|
|
'</div>'; |
133
|
|
|
|
134
|
|
|
if ($roundNumber != $this->noRounds) { |
135
|
|
|
|
136
|
|
|
echo '<div class="vertical-connector" style="top: ' . $match['vConnectorTop'] . 'px; left: ' . $match['vConnectorLeft'] . 'px; height: ' . $match['vConnectorHeight'] . 'px;"></div> |
137
|
|
|
<div class="horizontal-connector" style="top: ' . $match['hConnectorTop'] . 'px; left: ' . $match['hConnectorLeft'] . 'px;"></div> |
138
|
|
|
<div class="horizontal-connector" style="top: ' . $match['hConnector2Top'] . 'px; left: ' . $match['hConnector2Left'] . 'px;"></div>'; |
139
|
|
|
|
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
echo '</div>'; |
147
|
|
|
|
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Print Round Titles |
152
|
|
|
*/ |
153
|
|
|
private function printRoundTitles() |
154
|
|
|
{ |
155
|
|
|
|
156
|
|
|
if ($this->noTeams == 2) { |
157
|
|
|
|
158
|
|
|
$roundTitles = array('Final'); |
159
|
|
|
|
160
|
|
|
} elseif ($this->noTeams <= 4) { |
161
|
|
|
|
162
|
|
|
$roundTitles = array('Semi-Finals', 'Final'); |
163
|
|
|
|
164
|
|
|
} elseif ($this->noTeams <= 8) { |
165
|
|
|
|
166
|
|
|
$roundTitles = array('Quarter-Finals', 'Semi-Finals', 'Final'); |
167
|
|
|
|
168
|
|
|
} else { |
169
|
|
|
|
170
|
|
|
$roundTitles = array('Quarter-Finals', 'Semi-Finals', 'Final'); |
171
|
|
|
$noRounds = ceil(log($this->noTeams, 2)); |
172
|
|
|
$noTeamsInFirstRound = pow(2, ceil(log($this->noTeams) / log(2))); |
173
|
|
|
$tempRounds = array(); |
174
|
|
|
|
175
|
|
|
//The minus 3 is to ignore the final, semi final and quarter final rounds |
176
|
|
|
|
177
|
|
|
for ($i = 0; $i < $noRounds - 3; $i++) { |
178
|
|
|
$tempRounds[] = 'Last ' . $noTeamsInFirstRound; |
179
|
|
|
$noTeamsInFirstRound /= 2; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$roundTitles = array_merge($tempRounds, $roundTitles); |
183
|
|
|
|
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
echo '<div id="round-titles-wrapper">'; |
187
|
|
|
|
188
|
|
|
foreach ($roundTitles as $key => $roundTitle) { |
189
|
|
|
|
190
|
|
|
$left = $key * ($this->matchWrapperWidth + $this->roundSpacing - 1); |
191
|
|
|
|
192
|
|
|
echo '<div class="round-title" style="left: ' . $left . 'px;">' . $roundTitle . '</div>'; |
193
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
echo '</div>'; |
197
|
|
|
|
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @param $selected |
202
|
|
|
* @return string |
203
|
|
|
*/ |
204
|
|
|
private function getPlayerList($selected) |
205
|
|
|
{ |
206
|
|
|
|
207
|
|
|
$html = '<select> |
208
|
|
|
<option' . ($selected == '' ? ' selected' : '') . '></option>'; |
209
|
|
|
foreach (array_merge($this->brackets[1]) as $bracket) { // Bug Fix 24-02-2017 , $this->brackets[2] |
|
|
|
|
210
|
|
View Code Duplication |
if ($bracket['playerA'] != '') { |
|
|
|
|
211
|
|
|
$html .= '<option' . ($selected == $bracket['playerA'] ? ' selected' : '') . '>' . $bracket['playerA'] . '</option>'; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
View Code Duplication |
if ($bracket['playerB'] != '') { |
|
|
|
|
215
|
|
|
$html .= '<option' . ($selected == $bracket['playerB'] ? ' selected' : '') . '>' . $bracket['playerB'] . '</option>'; |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$html .= '</select>'; |
220
|
|
|
|
221
|
|
|
return $html; |
222
|
|
|
|
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* @param $teams |
227
|
|
|
* @return array |
228
|
|
|
*/ |
229
|
|
|
private function orderTeamsInSeededOrder($teams): array |
230
|
|
|
{ |
231
|
|
|
$logNoTeam = log($this->noTeams / 2, 2); |
232
|
|
|
|
233
|
|
|
for ($i = 0; $i < $logNoTeam; $i++) { |
234
|
|
|
|
235
|
|
|
$out = []; |
236
|
|
|
|
237
|
|
|
foreach ($teams as $player) { |
238
|
|
|
$splice = pow(2, $i); |
239
|
|
|
$out = array_merge($out, array_splice($teams, 0, $splice)); |
240
|
|
|
$out = array_merge($out, array_splice($teams, -$splice)); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$teams = $out; |
244
|
|
|
|
245
|
|
|
} |
246
|
|
|
return $teams; |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
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.