1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
// let API = "http://localhost:8080/example.json";
let API = "example.json";
var tabelle = [] /* Will contain all pairings */
var leagues = [] /* List of league ids */
var max_spieltage = 0;
var next_pairing_id = 0;
function weekday_to_string(weekday) {
return new Date(Date.UTC(1970, 0, 6+weekday)).toLocaleDateString(undefined, { weekday: 'long' });
}
var wildcard = JSON.parse('{"id":"-1", "name":"*"}');
var nowhere = JSON.parse('{"id":"-1", "name":"*"}');
class paarung {
static _next_id = 0;
constructor(team_a, team_b, league, ort) {
this.id = next_pairing_id++;
this.team_a = team_a;
this.team_b = team_b;
this.league = league;
this.ort = ort;
}
get weekday() {
if (this.team_a.id != -1 && this.team_b.id != -1)
return weekday_to_string(this.team_a.tag);
return "*";
}
get spieltag() {
if (!this.spieltage.size)
return -1;
return [...this.spieltage][0];
}
get name() {
return this.team_a.name + " :: " + this.team_b.name + ", Liga " + this.league;
}
};
function is_same(team_a, team_b) {
if (team_a.id == -1 || team_b.id == -1 || team_a.id != team_b.id)
return false;
return true;
}
function is_same_ort(ort_a, ort_b) {
if (ort_a.id == -1 || ort_b.id == -1 || ort_a.id != ort_b.id)
return false;
return true;
}
function createTextElement(name, text) {
let elem = document.createElement(name);
elem.appendChild(document.createTextNode(text));
return elem;
}
function appendChildList(elem, name, ...texts) {
for (const text of texts)
elem.appendChild(createTextElement(name, text));
}
function draw_table() {
/* Draw tables for each league */
let anchor = document.getElementById("anchor");
for (league of leagues) {
anchor.appendChild(createTextElement("h2", "Liga " + league.toString()));
let table = document.createElement("table");
let thead = document.createElement("thead");
let tr = document.createElement("tr");
appendChildList(tr, "th", "Heim", "Gäste", "Wochentag", "Tisch", "Spieltag");
thead.appendChild(tr);
table.appendChild(thead);
for (paar of tabelle.filter(paar => paar.league == league).sort((paar_a, paar_b) => { if (!paar_a.spieltage.size || !paar_b.spieltage.size) return -1; return paar_a.spieltag - paar_b.spieltag } )) {
let tr = document.createElement("tr");
appendChildList(tr, "td", paar.team_a.name, paar.team_b.name, paar.weekday, paar.ort.name, 1 + paar.spieltag);
table.appendChild(tr);
}
anchor.appendChild(table);
}
}
function fill_table(data) {
leagues = [...new Set(data.teams.map(team => team.liga))].sort();
/* Init some objects */
for (ort of data.orte)
ort.pairings = new Array();
/* Count necessary spieltage */
for (league of leagues) {
let pairings_count = data.teams.filter(team => team.liga == league).sort((a,b) => a.id > b.id).length;
if (pairings_count % 2)
pairings_count++;
max_spieltage = Math.max(2 * (pairings_count - 1), max_spieltage);
}
/* Fill out complete table for all leagues */
for (league of leagues) {
let league_teams = data.teams.filter(team => team.liga == league).sort((a,b) => a.id > b.id);
console.log( "liga " + league_teams.length );
for (team_a of league_teams) {
var game_count = 0;
for (team_b of league_teams) {
if (team_a == team_b)
continue;
/* If Heimspiel-Team is smokers and the Gastteam is not, the alternative
location needs to be chosen */
var ort = data.orte.find(ort => ort.id == team_a.heimort);
if (team_b.nichtraucher && ort.raucher)
ort = data.orte.find(ort => ort.id == team_a.ersatzort);
pair = new paarung(team_a, team_b, league, ort);
tabelle.push(pair);
ort.pairings.push(pair);
game_count++;
}
/* Fill rest of this team's games with wildcard games */
while (game_count < max_spieltage / 2) {
tabelle.push(new paarung(team_a, wildcard, league, nowhere));
tabelle.push(new paarung(wildcard, team_a, league, nowhere));
game_count++;
}
}
}
/* Fill all leagues with uneven or fewer spieltage with wildcard games */
for (league of leagues) {
}
/* Check if an ort is over-provisioned
for (ort of data.orte) {
for (weekday of [...new Set(ort.pairings.map(pair => pair.weekday))].sort()) {
var games_on_day = ort.pairings.filter(pair => pair.weekday = weekday);
if (games_on_day > max_spieltage)
alert("Ort " + ort.name + "over provisioned on weekday " + weekday_to_string(weekday));
}
} */
}
function shuffle_array(array) {
return array.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}
function not_equal_function(s1, s2) { return s1 != s2; }
function find_csp_solution() {
var all_spieltage = new Array();
for (let i=0; i < max_spieltage; i++)
all_spieltage.push(i);
for (league of leagues) {
var sub = tabelle.filter(paar => paar.league == league);
var candidate = {}, variables = {}, constraints = [];
for (outer of sub) {
variables[outer.id] = [...all_spieltage];
for (inner of tabelle.filter(inner =>
inner.id > outer.id && (
is_same(outer.team_a, inner.team_a) ||
is_same(outer.team_b, inner.team_a) ||
is_same(outer.team_a, inner.team_b) ||
is_same(outer.team_b, inner.team_b))))
{
constraints.push([outer.id, inner.id, not_equal_function]);
}
}
candidate.variables = variables;
candidate.constraints = constraints;
var result = csp.solve(candidate);
console.log(result);
}
}
function find_table_configuration() {
/* Check if there is one possible configuration that fulfills the following criteria:
1) each team must have 0 or 1 games on any given week
2) each location+weekday combo must have 0 or 1 games on any given week
3) each pairing needs a week and a location
*/
var candidates = tabelle; /* Take a copy of the array */
/* Add all possible spieltage to the list for
them to be later reduced */
for (paar of candidates) {
paar.spieltage = new Set();
for (let i=0; i < max_spieltage; i++)
paar.spieltage.add(i);
}
/* TODO: Add more initial constraints */
while (candidates.length) {
/* First pick the pairings that have the least amount of spieltage to fit in */
candidates = shuffle_array(candidates).sort((a, b) => a.spieltage.size <= b.spieltage.size);
var looking_at = candidates.pop();
/* Pick random spieltag out of the possible ones */
let spieltag = Array.from(looking_at.spieltage)[Math.floor(Math.random() * looking_at.spieltage.size)];
/* Now we have to remove all new conflicting dates from other unset pairings: */
/* Filter out all pairings on the same spieltag with the current team */
for (paar of candidates.filter(paar =>
is_same(paar.team_a, looking_at.team_a) ||
is_same(paar.team_b, looking_at.team_a) ||
is_same(paar.team_a, looking_at.team_b) ||
is_same(paar.team_b, looking_at.team_b)))
{
paar.spieltage.delete(spieltag);
}
/* Filter out all pairing on the same spieltag with the same ort and weekday
for (paar of candidates.filter(paar =>
is_same_ort(paar.ort, looking_at.ort) &&
paar.weekday == looking_at.weekday))
{
if (paar.ort.id != -1 && looking_at.ort.id != -1)
paar.spieltage.delete(spieltag);
}
*/
/* Filter out the reverse pairing until the second half of the season (Rueckrunde).
If it doesn't exist, we're probably already in Rueckrunde
var runden_start = Math.floor(max_spieltage * Math.floor(spieltag * 2 / max_spieltage) / 2);
for (paar of candidates.filter(paar =>
is_same(paar.team_a, looking_at.team_b) ||
is_same(paar.team_b, looking_at.team_a))) {
for (var exclude = 0; exclude < max_spieltage / 2; exclude++)
paar.spieltage.delete(runden_start + exclude);
}
*/
/* Filter out home rounds for the home team on next spieltag and guest rounds for
guest team on next spieltag, except for end of Hinrunde
if (spieltag != max_spieltage / 2 - 1)
for (paar of candidates.filter(paar =>
is_same(paar.team_a, looking_at.team_a) ||
is_same(paar.team_b, looking_at.team_b)))
paar.spieltage.delete(spieltag + 1);
*/
for (paar of candidates)
if (paar.spieltage.size == 0) {
console.log(candidates.length);
return false;
}
/* Now fix the date */
looking_at.spieltage = new Set();
looking_at.spieltage.add(spieltag);
}
return true;
}
function init() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
fill_table(xhttp.response);
//find_table_configuration();
find_csp_solution();
draw_table();
}
};
xhttp.responseType = "json";
xhttp.open("GET", API, true);
xhttp.send();
}
init();
|