From 47cb23ce1f991c21ceb9273cf4bed717a09abd9a Mon Sep 17 00:00:00 2001 From: erdgeist Date: Mon, 26 May 2025 15:55:29 +0200 Subject: Kickoff commit --- Makefile | 6 + augment.py | 194 ++++++++++++ csp.js | 181 +++++++++++ data.json | 86 +++++ example-output.txt | 912 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 13 + requirements.txt | 1 + style.css | 21 ++ tabularasa.js | 284 +++++++++++++++++ 9 files changed, 1698 insertions(+) create mode 100644 Makefile create mode 100644 augment.py create mode 100644 csp.js create mode 100644 data.json create mode 100644 example-output.txt create mode 100644 index.html create mode 100644 requirements.txt create mode 100644 style.css create mode 100644 tabularasa.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d50316 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +install: venv + venv/bin/pip install --upgrade pip + venv/bin/pip install -r requirements.txt + +venv: + python3 -m venv ./venv diff --git a/augment.py b/augment.py new file mode 100644 index 0000000..d96d511 --- /dev/null +++ b/augment.py @@ -0,0 +1,194 @@ +import json +from pprint import pprint +from random import randrange, randint + +league_count = 4 +tabelle = [] + +with open('data.json') as f: + data = json.load(f) + +ident = 0 +for o in data["orte"]: + o["id"] = ident + o["raucher"] = not randrange(2) + o["teams"] = [] + ident += 1 + +ident = 0 +for t in data["teams"]: + + t["id"] = ident + t["tag"] = randrange(7) + t["liga"] = randrange(league_count) + t["nichtraucher"] = not randrange(4) + + team_nr = t["nichtraucher"] + + found = False + while not found: + heimort = randrange(len(data["orte"])) + ersatzort = randrange(len(data["orte"])) + + if data["orte"][ersatzort]["raucher"]: + continue + + ort_nr = not data["orte"][heimort]["raucher"] + + if team_nr != ort_nr: + continue + + if team_nr: + ersatzort = -1 + + found = True + + t["ersatzort"] = ersatzort + t["heimort"] = heimort + data["orte"][heimort]["teams"].append(ident) + + ident += 1 + +# Count necessary spieltage +max_spieltage = 0 +for league in range(league_count): + pairings_count = len([t for t in data["teams"] if t["liga"] == league]) + if pairings_count % 2: + pairings_count += 1 + max_spieltage = max(2 * (pairings_count - 1), max_spieltage); + +print( "Max Spieltage: ", max_spieltage) + +# Fill Tabelle with our demo data +ident = 0 +for league in range(league_count): + league_teams = [t for t in data["teams"] if t["liga"] == league] + + for team_a in league_teams: + game_count = 0 + + for team_b in league_teams: + if team_a["id"] == team_b["id"]: continue + + # If Heimspiel-Team is smokers and the Gastteam is not, the alternative + # location needs to be chosen + ort = next((ort for ort in data["orte"] if ort["id"] == team_a["heimort"]), None) + if team_b["nichtraucher"] and ort["raucher"]: + ort = next((ort for ort in data["orte"] if ort["id"] == team_a["ersatzort"]), None) + + tabelle.append({"team_a": team_a["id"], "team_b": team_b["id"], "liga": league, "ort": ort["id"], "tag": team_a["tag"], "id": ident}) + ident += 1 + game_count += 1 + + while game_count < max_spieltage / 2: + tabelle.append({"team_a": team_a["id"], "team_b": -1, "liga": league, "ort": -1, "tag": -1, "id": ident}) + ident += 1 + tabelle.append({"team_a": -1, "team_b": team_a["id"], "liga": league, "ort": -1, "tag": -1, "id": ident}) + ident += 1 + game_count += 1 + +rueckrundenstart = max_spieltage // 2 + +from ortools.sat.python import cp_model +model = cp_model.CpModel() + +variables = {game["id"]: model.new_int_var(0, max_spieltage - 1, f"game{game['id']}") for game in tabelle} + +# Make sure each team only plays once each spieltag +for team in data["teams"]: + # team_games = [game["id"] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]] + # print (len(team_games), team_games, team) + model.add_all_different([variables[game["id"]] for game in tabelle if game["team_a"] == team["id"] or game["team_b"] == team["id"]]) + +# Make sure each ort is only used once per spieltag+week +for ort in data["orte"]: + ort_games = [game for game in tabelle if game["ort"] == ort["id"]] + for tag in list({game["tag"] for game in ort_games}): + model.add_all_different([variables[game["id"]] for game in ort_games if game["tag"] == tag ]) + +# Make sure that Rueckrundenspiele only happen at Rueckrunde +for game in tabelle: + # Don't force this constraint on wildcard games + if game["ort"] == -1: continue + + # Find Rueckspiel + rueckspiel = next((candidate for candidate in tabelle if candidate["team_a"] == game["team_b"] and candidate["team_b"] == game["team_a"]), None) + + va = variables[game["id"]] + vb = variables[rueckspiel["id"]] + + runde_a = model.new_int_var(0, 1, "tmpa") + runde_b = model.new_int_var(0, 1, "tmpb") + + model.add_division_equality(runde_a, va, rueckrundenstart) + model.add_division_equality(runde_b, vb, rueckrundenstart) + model.add(runde_a != runde_b) + +# Make sure that Heimspiele and Gastspiele alternate +violations = [] +for team in data["teams"]: + heimspiele = [candidate for candidate in tabelle if candidate["team_a"] == team["id"] ] + + for heimspiel in heimspiele: + for conflict in heimspiele: + if heimspiel["id"] == conflict["id"]: continue + + va = variables[heimspiel["id"]] + vb = variables[conflict["id"]] + + tmpdiff_a = model.new_int_var(-max_spieltage, max_spieltage, "tmpa") + tmpdiff_b = model.new_int_var(-max_spieltage, max_spieltage, "tmpb") + + model.add(tmpdiff_a == va - vb) + model.add(tmpdiff_b == vb - va) + + tmpconsequtive_a = model.new_bool_var("tmpa") + tmpconsequtive_b = model.new_bool_var("tmpb") + + model.add(tmpdiff_a == 1).only_enforce_if(tmpconsequtive_a) + model.add(tmpdiff_a != 1).only_enforce_if(tmpconsequtive_a.Not()) + + model.add(tmpdiff_b == 1).only_enforce_if(tmpconsequtive_b) + model.add(tmpdiff_b != 1).only_enforce_if(tmpconsequtive_b.Not()) + + is_consecutive = model.new_bool_var('is_consecutive') + + model.add_bool_or([tmpconsequtive_a, tmpconsequtive_b]).OnlyEnforceIf(is_consecutive) + model.add_bool_and([tmpconsequtive_a.Not(), tmpconsequtive_b.Not()]).OnlyEnforceIf(is_consecutive.Not()) + + violations.append(is_consecutive) + +model.minimize(sum(violations)) + +print ("All set, solving") + +solver = cp_model.CpSolver() +solver.parameters.max_time_in_seconds = 30.0 +solver.parameters.random_seed = randint(0, 1_000_000) +status = solver.solve(model) +print(status, solver.status_name()) + +from sys import exit +if status == cp_model.INFEASIBLE: + exit(-1) + +for game in tabelle: + game["spieltag"] = solver.value(variables[game["id"]]) + +# tabelle.sort(key=lambda game: game["spieltag"]) + +# PRINT OUT RESULTS AS TABLE +all_teams = {team["id"]: team for team in data["teams"]} +all_teams[-1] = {"name": "*"} +all_orte = {ort["id"]: ort for ort in data["orte"]} +all_orte[-1] = {"name": "*"} + +for league in range(league_count): + league_tabelle = [game for game in tabelle if game["liga"] == league] + league_tabelle.sort(key=lambda game: game["spieltag"]) + + for game in league_tabelle: + print(game["spieltag"], all_teams[game["team_a"]]["name"], "::", all_teams[game["team_b"]]["name"], "::", all_orte[game["ort"]]["name"]) + +#print( json.dumps(data)) +#print( json.dumps(tabelle)) diff --git a/csp.js b/csp.js new file mode 100644 index 0000000..139b70d --- /dev/null +++ b/csp.js @@ -0,0 +1,181 @@ +!function() { + +var CSP = {}, + FAILURE = 'FAILURE', + stepCounter = 0; + +CSP.solve = function solve(csp) { + // Solves a constraint satisfaction problem. + // `csp` is an object that should have the properties: + // `variables` : object that holds variable names and their domain. + // `constraints`: list of constraints where each element is an + // array of [head node, tail node, constraint function] + // `cb`: callback function for visualizing assignments. It is passed in + // an "assigned" object, an "unassigned" object, and `csp`. + // `timeStep`: milliseconds between invocations of `cb`. + + csp.timeStep = csp.timeStep || 1; + var result = backtrack({}, csp.variables, csp); + if (result == FAILURE) { return result; } + // Unwrap values from array containers. + for (var key in result) { + result[key] = result[key][0]; + } + return result; +} + +function backtrack(_assigned, unassigned, csp) { + // Backtracking search. + + // Copying assigned in necessary because we modify it. Without copying + // the object over, modifying assigned would also change values for old + // assigned objects (which are used in callbacks). + var assigned = {}; + for (var key in _assigned) { assigned[key] = _assigned[key]; } + + if (finished(unassigned)) { return assigned; } // Base case. + var nextKey = selectUnassignedVariable(unassigned), + values = orderValues(nextKey, assigned, unassigned, csp); + delete unassigned[nextKey]; + + for (var i = 0; i < values.length; i++) { + stepCounter++; + assigned[nextKey] = [values[i]]; // Assign a value to a variable. + var consistent = enforceConsistency(assigned, unassigned, csp); + var newUnassigned = {}, newAssigned = {}; + for (var key in consistent) { + if (assigned[key]) { newAssigned[key] = assigned[key].slice(); } + else { newUnassigned[key] = consistent[key].slice(); } + } + if (csp.cb) { + setTimeout( + // Need a closure to fix values of newAssigned and newUnassigned. + // Otherwise, _every_ call of the callback takes the on values of the last iteration. + (function (newAssigned, newUnassigned) { + return function () { csp.cb(newAssigned, newUnassigned, csp); }; + })(newAssigned, newUnassigned), + stepCounter * csp.timeStep + ); + } + if (anyEmpty(consistent)) { continue; } // Empty domains means failure. + var result = backtrack(newAssigned, newUnassigned, csp); + if (result != FAILURE) { return result; } + } + + return FAILURE; +} + +function finished(unassigned) { + // Checks if there are no more variables to assign. + return Object.keys(unassigned).length == 0; +} + +function anyEmpty(consistent) { + // Checks if any variable's domain is empty. + for (var key in consistent) { + if (consistent[key].length == 0) { return true; } + } + return false; +} + +function partialAssignment(assigned, unassigned) { + // Combine unassigned and assigned for use in enforceConsistency. + var partial = {}; + for (var key in unassigned) { partial[key] = unassigned[key].slice(); } + for (var key in assigned) { partial[key] = assigned[key].slice(); } + return partial; +} + +function enforceConsistency(assigned, unassigned, csp) { + // Enforces arc consistency by removing inconsistent values from + // every constraint's tail node. + + function removeInconsistentValues(head, tail, constraint, variables) { + // Removes inconsistent values from the tail node. A value is + // inconsistent when if the `tail` is assigned that value, there are + // no values in `head`'s domain that satisfies the constraint. + var hv = variables[head], tv = variables[tail]; + var validTailValues = tv.filter(function (t) { + return hv.some(function (h) { + return constraint(h, t); + }); + }); + var removed = tv.length != validTailValues.length; + variables[tail] = validTailValues; + return removed; + } + + function incomingConstraints(node) { + // Returns all the constraints where `node` is the head node. + return csp.constraints.filter(function (c) { + return c[0] == node; + }); + } + + var queue = csp.constraints.slice(), + variables = partialAssignment(assigned, unassigned); + while (queue.length) { // While there are more constraints to test. + var c = queue.shift(), head = c[0], tail = c[1], constraint = c[2]; + if (removeInconsistentValues(head, tail, constraint, variables)) { + // If values from the tail have been removed, incoming constraints + // to the tail must be rechecked. + queue = queue.concat(incomingConstraints(tail)); + } + } + return variables; +} + +function selectUnassignedVariable(unassigned) { + // Picks the next variable to assign according to the Minimum + // Remaining Values heuristic. Pick the variable with the fewest + // values remaining in its domain. This helps identify domain + // failures earlier. + var minKey = null, minLen = Number.POSITIVE_INFINITY; + for (var key in unassigned) { + var len = unassigned[key].length; + if (len < minLen) { minKey = key, minLen = len; } + } + return minKey; +} + +function orderValues(nextKey, assigned, unassigned, csp) { + // Orders the values of an unassigned variable according to the + // Least Constraining Values heuristic. Perform arc consistency + // on each possible value, and order variables according to the + // how many values were eliminated from all the domains (fewest + // eliminated in the front). This helps makes success more likely + // by keeping future options open. + + function countValues(vars) { + var sum = 0; + for (var key in vars) { sum += vars[key].length; } + return sum; + } + + function valuesEliminated(val) { + assigned[nextKey] = [val]; + var newLength = countValues(enforceConsistency(assigned, unassigned, csp)); + delete assigned[nextKey]; + return newLength; + } + + // Cache valuesEliminated to be used in sort. + var cache = {}, values = unassigned[nextKey]; + values.forEach(function(val) { + cache[val] = valuesEliminated(val); + }); + // Descending order based on the number of domain values remaining. + values.sort(function (a, b) { return cache[b] - cache[a]; }); + return values; +} + +// Taken from d3 source. Makes `csp` usable in other scripts. +if (typeof define === 'function' && define.amd) { + define(CSP); +} else if (typeof module === 'object' && module.exports) { + module.exports = CSP; +} else { + this.csp = CSP; +} + +}(); diff --git a/data.json b/data.json new file mode 100644 index 0000000..8e6c1fa --- /dev/null +++ b/data.json @@ -0,0 +1,86 @@ +{ + "teams": [ + {"name": "Die Stangendreher" }, + {"name": "FC Kurbelkraft" }, + {"name": "Die Kugelblitze" }, + {"name": "Drehmoment 04" }, + {"name": "Kickerknacker" }, + {"name": "Stangenterror" }, + {"name": "Die Plattenpanther" }, + {"name": "Vollstoff United" }, + {"name": "Kurbelkönige" }, + {"name": "Die Gleitlager" }, + {"name": "Dynamo Drehpunkt" }, + {"name": "FC Hartkurbel" }, + {"name": "Turbo-Tischkicker" }, + {"name": "Kugelkontrolle" }, + {"name": "Die Griffbanditen" }, + {"name": "Tischgewitter" }, + {"name": "Rapid Rotation" }, + {"name": "Kurbelschmiede" }, + {"name": "Die Schussscheiben" }, + {"name": "Die Querpassquäler" }, + {"name": "Reibungslose Racker" }, + {"name": "Die Kugeljäger" }, + {"name": "Kurbelclub Chaos" }, + {"name": "Die Seitenwechsel" }, + {"name": "Drehakademie" }, + {"name": "Die Griffgötter" }, + {"name": "Kickerkombinat" }, + {"name": "Rotationssportverein" }, + {"name": "Die Rillenritter" }, + {"name": "Stangenhelden" }, + {"name": "Kugelkommando" }, + {"name": "Kick’n’Twist" }, + {"name": "FC Schubkraft" }, + {"name": "Die Rutschbahn-Raketen" }, + {"name": "Die Taktikdreher" }, + {"name": "Die Kickkanonen" }, + {"name": "Die Blockbuster" }, + {"name": "Vollgas Vierer" }, + {"name": "Die Tischwölfe" }, + {"name": "Rotation Rangers" }, + {"name": "Die Drehlords" }, + {"name": "Griffbereit 99" }, + {"name": "Die Bandenkrieger" }, + {"name": "Ballmagneten" }, + {"name": "Stahlstangen SC" }, + {"name": "Die Kurbelkicker" }, + {"name": "Die Abroller" }, + {"name": "Die Plattenspieler" }, + {"name": "FC Gummipuffer" }, + {"name": "Griff & Gloria" } + ], + "orte": [ + {"name": "Kurbelpark Arena" }, + {"name": "Stangenstadion Nord" }, + {"name": "Drehpunkt Dome" }, + {"name": "Griffhalle West" }, + {"name": "Kugelwerk Ost" }, + {"name": "Twistground Central" }, + {"name": "Reibungsring Arena" }, + {"name": "Plattenpalast" }, + {"name": "Kickerforum Süd" }, + {"name": "Drehkraft-Dome" }, + {"name": "Tischkampfarena Alte Platte" }, + {"name": "Kurbelkurve Stadion" }, + {"name": "Stangenwelle Stadion" }, + {"name": "Magnetfeld Mitte" }, + {"name": "Arena am Gleittisch" }, + {"name": "Schusshof Kurbeldorf" }, + {"name": "Rotationshalle 7" }, + {"name": "Griffzentrum Westkurve" }, + {"name": "Bandenbruch-Stadion" }, + {"name": "Kugeltempel Altstadt" }, + {"name": "Twistpark Westrand" }, + {"name": "Gegenstoß-Galerie" }, + {"name": "Kurbelaue Nord" }, + {"name": "Kickerklotz Kolosseum" }, + {"name": "Griffgipfel Arena" }, + {"name": "Der Tischkessel" }, + {"name": "Lagerblock Süd" }, + {"name": "Kugelkurve Klassik" }, + {"name": "Die Platte am Fluss" }, + {"name": "Haltepunkt Hohlgriff" } + ] +} diff --git a/example-output.txt b/example-output.txt new file mode 100644 index 0000000..05d35d2 --- /dev/null +++ b/example-output.txt @@ -0,0 +1,912 @@ +0 FC Kurbelkraft :: Rotation Rangers :: Stangenwelle Stadion +0 Kickerknacker :: Die Kickkanonen :: Stangenwelle Stadion +0 Kugelkontrolle :: Rapid Rotation :: Die Platte am Fluss +0 Die Seitenwechsel :: FC Schubkraft :: Kurbelpark Arena +0 * :: Kugelkommando :: * +0 * :: Die Taktikdreher :: * +1 * :: FC Kurbelkraft :: * +1 * :: Kugelkontrolle :: * +1 Rapid Rotation :: Kickerknacker :: Kugeltempel Altstadt +1 * :: Die Seitenwechsel :: * +1 Kugelkommando :: Rotation Rangers :: Twistpark Westrand +1 FC Schubkraft :: * :: * +1 Die Taktikdreher :: Die Kickkanonen :: Kurbelaue Nord +2 FC Kurbelkraft :: Rapid Rotation :: Stangenstadion Nord +2 Kickerknacker :: Kugelkommando :: Stangenwelle Stadion +2 Kugelkontrolle :: FC Schubkraft :: Die Platte am Fluss +2 Die Seitenwechsel :: Die Kickkanonen :: Schusshof Kurbeldorf +2 * :: Die Taktikdreher :: * +2 Rotation Rangers :: * :: * +3 * :: FC Kurbelkraft :: * +3 * :: Kickerknacker :: * +3 * :: Kugelkontrolle :: * +3 Rapid Rotation :: Die Seitenwechsel :: Kugeltempel Altstadt +3 Kugelkommando :: Die Kickkanonen :: Twistpark Westrand +3 FC Schubkraft :: Rotation Rangers :: Griffzentrum Westkurve +3 Die Taktikdreher :: * :: * +4 FC Kurbelkraft :: FC Schubkraft :: Stangenstadion Nord +4 Kickerknacker :: Die Taktikdreher :: Haltepunkt Hohlgriff +4 Die Seitenwechsel :: Kugelkontrolle :: Kurbelpark Arena +4 Kugelkommando :: Rapid Rotation :: Twistpark Westrand +4 Die Kickkanonen :: * :: * +4 Rotation Rangers :: * :: * +5 * :: FC Kurbelkraft :: * +5 * :: Kickerknacker :: * +5 Kugelkontrolle :: * :: * +5 Rapid Rotation :: * :: * +5 Die Seitenwechsel :: * :: * +5 * :: Kugelkommando :: * +5 FC Schubkraft :: * :: * +5 Die Taktikdreher :: * :: * +5 * :: Die Kickkanonen :: * +5 * :: Rotation Rangers :: * +6 FC Kurbelkraft :: * :: * +6 Kickerknacker :: * :: * +6 * :: Rapid Rotation :: * +6 * :: Die Seitenwechsel :: * +6 Kugelkommando :: Die Taktikdreher :: Twistpark Westrand +6 * :: FC Schubkraft :: * +6 Die Kickkanonen :: Kugelkontrolle :: Griffgipfel Arena +6 Rotation Rangers :: * :: * +7 * :: FC Kurbelkraft :: * +7 Kugelkontrolle :: Kugelkommando :: Kickerklotz Kolosseum +7 Rapid Rotation :: Die Taktikdreher :: Kugeltempel Altstadt +7 Die Seitenwechsel :: Rotation Rangers :: Schusshof Kurbeldorf +7 FC Schubkraft :: Kickerknacker :: Rotationshalle 7 +7 * :: Die Kickkanonen :: * +8 Kickerknacker :: FC Kurbelkraft :: Haltepunkt Hohlgriff +8 Kugelkommando :: Die Seitenwechsel :: Twistpark Westrand +8 * :: FC Schubkraft :: * +8 Die Taktikdreher :: Kugelkontrolle :: Die Platte am Fluss +8 Die Kickkanonen :: * :: * +8 Rotation Rangers :: Rapid Rotation :: Kickerforum Süd +9 FC Kurbelkraft :: Die Taktikdreher :: Stangenstadion Nord +9 Kugelkontrolle :: Kickerknacker :: Die Platte am Fluss +9 Rapid Rotation :: Die Kickkanonen :: Twistpark Westrand +9 Die Seitenwechsel :: * :: * +9 * :: Kugelkommando :: * +9 FC Schubkraft :: * :: * +9 * :: Rotation Rangers :: * +10 FC Kurbelkraft :: Die Seitenwechsel :: Stangenstadion Nord +10 Kickerknacker :: * :: * +10 * :: Rapid Rotation :: * +10 Kugelkommando :: * :: * +10 Die Taktikdreher :: FC Schubkraft :: Die Platte am Fluss +10 Die Kickkanonen :: * :: * +10 Rotation Rangers :: Kugelkontrolle :: Kickerforum Süd +11 * :: Kickerknacker :: * +11 Kugelkontrolle :: FC Kurbelkraft :: Die Platte am Fluss +11 * :: Rapid Rotation :: * +11 Die Seitenwechsel :: * :: * +11 FC Schubkraft :: Kugelkommando :: Griffzentrum Westkurve +11 * :: Die Kickkanonen :: * +11 Rotation Rangers :: Die Taktikdreher :: Kickerforum Süd +12 Kickerknacker :: Die Seitenwechsel :: Haltepunkt Hohlgriff +12 * :: Kugelkontrolle :: * +12 Rapid Rotation :: * :: * +12 Kugelkommando :: * :: * +12 * :: FC Schubkraft :: * +12 * :: Die Taktikdreher :: * +12 Die Kickkanonen :: FC Kurbelkraft :: Griffgipfel Arena +12 * :: Rotation Rangers :: * +13 FC Kurbelkraft :: * :: * +13 * :: Kickerknacker :: * +13 Kugelkontrolle :: * :: * +13 * :: Kugelkommando :: * +13 FC Schubkraft :: Rapid Rotation :: Rotationshalle 7 +13 Die Taktikdreher :: Die Seitenwechsel :: Die Platte am Fluss +13 Rotation Rangers :: Die Kickkanonen :: Kickerforum Süd +14 Kickerknacker :: Rotation Rangers :: Stangenwelle Stadion +14 * :: Kugelkontrolle :: * +14 * :: Rapid Rotation :: * +14 Die Seitenwechsel :: * :: * +14 Kugelkommando :: FC Kurbelkraft :: Twistpark Westrand +14 * :: Die Taktikdreher :: * +14 Die Kickkanonen :: FC Schubkraft :: Griffgipfel Arena +15 Kugelkontrolle :: * :: * +15 Rapid Rotation :: FC Kurbelkraft :: Kugeltempel Altstadt +15 FC Schubkraft :: Die Seitenwechsel :: Rotationshalle 7 +15 Die Taktikdreher :: Kickerknacker :: Die Platte am Fluss +15 * :: Die Kickkanonen :: * +15 Rotation Rangers :: Kugelkommando :: Kickerforum Süd +16 FC Kurbelkraft :: * :: * +16 * :: Kugelkontrolle :: * +16 Die Seitenwechsel :: Rapid Rotation :: Kurbelpark Arena +16 Kugelkommando :: Kickerknacker :: Twistpark Westrand +16 FC Schubkraft :: * :: * +16 Die Kickkanonen :: Die Taktikdreher :: Griffgipfel Arena +16 * :: Rotation Rangers :: * +17 Kickerknacker :: * :: * +17 Kugelkontrolle :: * :: * +17 Rapid Rotation :: * :: * +17 * :: FC Schubkraft :: * +17 Die Taktikdreher :: FC Kurbelkraft :: Die Platte am Fluss +17 Die Kickkanonen :: Kugelkommando :: Griffgipfel Arena +17 Rotation Rangers :: Die Seitenwechsel :: Kickerforum Süd +18 FC Kurbelkraft :: Kugelkontrolle :: Stangenstadion Nord +18 Rapid Rotation :: * :: * +18 Die Seitenwechsel :: * :: * +18 Kugelkommando :: * :: * +18 FC Schubkraft :: * :: * +18 * :: Die Taktikdreher :: * +18 * :: Die Kickkanonen :: * +18 Rotation Rangers :: Kickerknacker :: Kickerforum Süd +19 * :: FC Kurbelkraft :: * +19 Kickerknacker :: Rapid Rotation :: Haltepunkt Hohlgriff +19 Kugelkontrolle :: Rotation Rangers :: Kickerklotz Kolosseum +19 * :: FC Schubkraft :: * +19 Die Taktikdreher :: Kugelkommando :: Kurbelaue Nord +19 Die Kickkanonen :: Die Seitenwechsel :: Griffgipfel Arena +20 FC Kurbelkraft :: Kickerknacker :: Stangenstadion Nord +20 Rapid Rotation :: * :: * +20 Die Seitenwechsel :: Die Taktikdreher :: Kurbelpark Arena +20 Kugelkommando :: Kugelkontrolle :: Twistpark Westrand +20 FC Schubkraft :: * :: * +20 Die Kickkanonen :: * :: * +20 Rotation Rangers :: * :: * +21 FC Kurbelkraft :: * :: * +21 Kickerknacker :: * :: * +21 Kugelkontrolle :: Die Kickkanonen :: Kickerklotz Kolosseum +21 Rapid Rotation :: * :: * +21 * :: Die Seitenwechsel :: * +21 Kugelkommando :: FC Schubkraft :: Twistpark Westrand +21 Die Taktikdreher :: Rotation Rangers :: Kurbelaue Nord +22 * :: FC Kurbelkraft :: * +22 * :: Rapid Rotation :: * +22 Die Seitenwechsel :: Kugelkommando :: Schusshof Kurbeldorf +22 FC Schubkraft :: Kugelkontrolle :: Rotationshalle 7 +22 Die Taktikdreher :: * :: * +22 Die Kickkanonen :: Kickerknacker :: Griffgipfel Arena +22 Rotation Rangers :: * :: * +23 FC Kurbelkraft :: * :: * +23 Kickerknacker :: FC Schubkraft :: Haltepunkt Hohlgriff +23 Rapid Rotation :: Kugelkontrolle :: Kugeltempel Altstadt +23 * :: Die Seitenwechsel :: * +23 Kugelkommando :: * :: * +23 * :: Die Taktikdreher :: * +23 Die Kickkanonen :: * :: * +23 * :: Rotation Rangers :: * +24 * :: Kickerknacker :: * +24 Kugelkontrolle :: * :: * +24 Die Seitenwechsel :: * :: * +24 * :: Kugelkommando :: * +24 FC Schubkraft :: FC Kurbelkraft :: Rotationshalle 7 +24 Die Taktikdreher :: Rapid Rotation :: Die Platte am Fluss +24 * :: Die Kickkanonen :: * +24 Rotation Rangers :: * :: * +25 FC Kurbelkraft :: Kugelkommando :: Stangenwelle Stadion +25 Kickerknacker :: Kugelkontrolle :: Haltepunkt Hohlgriff +25 Rapid Rotation :: FC Schubkraft :: Kugeltempel Altstadt +25 * :: Die Seitenwechsel :: * +25 Die Taktikdreher :: * :: * +25 Die Kickkanonen :: Rotation Rangers :: Griffgipfel Arena +26 * :: Kickerknacker :: * +26 Kugelkontrolle :: * :: * +26 Die Seitenwechsel :: FC Kurbelkraft :: Kurbelpark Arena +26 Kugelkommando :: * :: * +26 Die Taktikdreher :: * :: * +26 Die Kickkanonen :: Rapid Rotation :: Griffgipfel Arena +26 Rotation Rangers :: FC Schubkraft :: Kickerforum Süd +27 FC Kurbelkraft :: Die Kickkanonen :: Stangenwelle Stadion +27 Kickerknacker :: * :: * +27 * :: Kugelkontrolle :: * +27 Rapid Rotation :: Kugelkommando :: Twistpark Westrand +27 * :: Die Seitenwechsel :: * +27 FC Schubkraft :: Die Taktikdreher :: Rotationshalle 7 +27 * :: Rotation Rangers :: * +28 Kugelkontrolle :: Die Taktikdreher :: Die Platte am Fluss +28 * :: Rapid Rotation :: * +28 Die Seitenwechsel :: Kickerknacker :: Kurbelpark Arena +28 Kugelkommando :: * :: * +28 * :: FC Schubkraft :: * +28 Die Kickkanonen :: * :: * +28 Rotation Rangers :: FC Kurbelkraft :: Kickerforum Süd +29 FC Kurbelkraft :: * :: * +29 Kickerknacker :: * :: * +29 Kugelkontrolle :: Die Seitenwechsel :: Die Platte am Fluss +29 Rapid Rotation :: Rotation Rangers :: Twistpark Westrand +29 * :: Kugelkommando :: * +29 FC Schubkraft :: Die Kickkanonen :: Griffzentrum Westkurve +29 Die Taktikdreher :: * :: * +0 Die Kugelblitze :: Drehmoment 04 :: Haltepunkt Hohlgriff +0 Die Querpassquäler :: Kurbelkönige :: Der Tischkessel +0 Kick’n’Twist :: * :: * +0 Die Rutschbahn-Raketen :: Die Gleitlager :: Kurbelaue Nord +0 Griffbereit 99 :: * :: * +0 Ballmagneten :: Turbo-Tischkicker :: Twistpark Westrand +0 Stahlstangen SC :: Die Stangendreher :: Griffgipfel Arena +1 Drehmoment 04 :: Die Stangendreher :: Plattenpalast +1 Kurbelkönige :: Die Kugelblitze :: Griffgipfel Arena +1 Die Gleitlager :: Griffbereit 99 :: Kickerklotz Kolosseum +1 Turbo-Tischkicker :: * :: * +1 * :: Die Querpassquäler :: * +1 * :: Kick’n’Twist :: * +1 * :: Die Rutschbahn-Raketen :: * +1 Ballmagneten :: Stahlstangen SC :: Twistpark Westrand +2 * :: Die Kugelblitze :: * +2 Drehmoment 04 :: Die Gleitlager :: Rotationshalle 7 +2 * :: Kurbelkönige :: * +2 * :: Turbo-Tischkicker :: * +2 Die Rutschbahn-Raketen :: Die Querpassquäler :: Kurbelaue Nord +2 Griffbereit 99 :: Die Stangendreher :: Kickerforum Süd +2 * :: Ballmagneten :: * +2 Stahlstangen SC :: Kick’n’Twist :: Griffgipfel Arena +3 Die Stangendreher :: * :: * +3 Die Kugelblitze :: Die Gleitlager :: Haltepunkt Hohlgriff +3 Drehmoment 04 :: Die Querpassquäler :: Rotationshalle 7 +3 Turbo-Tischkicker :: Stahlstangen SC :: Plattenpalast +3 Kick’n’Twist :: Kurbelkönige :: Griffgipfel Arena +3 Griffbereit 99 :: Die Rutschbahn-Raketen :: Kickerforum Süd +3 Ballmagneten :: * :: * +4 Die Kugelblitze :: * :: * +4 Drehmoment 04 :: Kick’n’Twist :: Rotationshalle 7 +4 Kurbelkönige :: Griffbereit 99 :: Griffgipfel Arena +4 Die Gleitlager :: Die Stangendreher :: Kickerklotz Kolosseum +4 Turbo-Tischkicker :: Die Querpassquäler :: Kurbelkurve Stadion +4 Die Rutschbahn-Raketen :: Ballmagneten :: Kurbelaue Nord +4 Stahlstangen SC :: * :: * +5 * :: Die Kugelblitze :: * +5 * :: Kurbelkönige :: * +5 Die Gleitlager :: * :: * +5 Turbo-Tischkicker :: Die Rutschbahn-Raketen :: Plattenpalast +5 Die Querpassquäler :: Stahlstangen SC :: Der Tischkessel +5 Kick’n’Twist :: * :: * +5 Griffbereit 99 :: Drehmoment 04 :: Kickerforum Süd +5 Ballmagneten :: Die Stangendreher :: Twistpark Westrand +6 Die Stangendreher :: * :: * +6 Die Kugelblitze :: Ballmagneten :: Bandenbruch-Stadion +6 Drehmoment 04 :: * :: * +6 Kurbelkönige :: * :: * +6 * :: Die Gleitlager :: * +6 Kick’n’Twist :: Die Querpassquäler :: Griffhalle West +6 Die Rutschbahn-Raketen :: Stahlstangen SC :: Kurbelaue Nord +6 Griffbereit 99 :: Turbo-Tischkicker :: Kickerforum Süd +7 Die Stangendreher :: Turbo-Tischkicker :: Griffgipfel Arena +7 * :: Drehmoment 04 :: * +7 Die Gleitlager :: * :: * +7 Die Querpassquäler :: * :: * +7 Kick’n’Twist :: Griffbereit 99 :: Griffgipfel Arena +7 Die Rutschbahn-Raketen :: Kurbelkönige :: Kurbelaue Nord +7 Ballmagneten :: * :: * +7 Stahlstangen SC :: Die Kugelblitze :: Griffgipfel Arena +8 Die Kugelblitze :: Die Stangendreher :: Bandenbruch-Stadion +8 Kurbelkönige :: Drehmoment 04 :: Griffgipfel Arena +8 Turbo-Tischkicker :: Die Gleitlager :: Kurbelkurve Stadion +8 * :: Die Rutschbahn-Raketen :: * +8 Griffbereit 99 :: Die Querpassquäler :: Kickerforum Süd +8 Ballmagneten :: Kick’n’Twist :: Twistpark Westrand +8 * :: Stahlstangen SC :: * +9 Die Stangendreher :: Kick’n’Twist :: Griffgipfel Arena +9 Die Kugelblitze :: Turbo-Tischkicker :: Haltepunkt Hohlgriff +9 * :: Kurbelkönige :: * +9 Die Gleitlager :: * :: * +9 Die Querpassquäler :: Ballmagneten :: Der Tischkessel +9 Die Rutschbahn-Raketen :: * :: * +9 * :: Griffbereit 99 :: * +9 Stahlstangen SC :: Drehmoment 04 :: Griffgipfel Arena +10 * :: Die Stangendreher :: * +10 * :: Die Kugelblitze :: * +10 Kurbelkönige :: Ballmagneten :: Griffgipfel Arena +10 Turbo-Tischkicker :: * :: * +10 * :: Die Querpassquäler :: * +10 Kick’n’Twist :: Die Gleitlager :: Griffhalle West +10 Die Rutschbahn-Raketen :: Drehmoment 04 :: Kurbelaue Nord +10 Griffbereit 99 :: * :: * +10 * :: Stahlstangen SC :: * +11 Die Stangendreher :: * :: * +11 Die Kugelblitze :: Die Rutschbahn-Raketen :: Bandenbruch-Stadion +11 * :: Drehmoment 04 :: * +11 Die Gleitlager :: Ballmagneten :: Kickerklotz Kolosseum +11 Die Querpassquäler :: * :: * +11 Kick’n’Twist :: Turbo-Tischkicker :: Griffhalle West +11 * :: Griffbereit 99 :: * +11 Stahlstangen SC :: Kurbelkönige :: Griffgipfel Arena +12 Drehmoment 04 :: Turbo-Tischkicker :: Rotationshalle 7 +12 Kurbelkönige :: Die Stangendreher :: Griffgipfel Arena +12 Die Querpassquäler :: Die Gleitlager :: Drehpunkt Dome +12 Die Rutschbahn-Raketen :: Kick’n’Twist :: Kurbelaue Nord +12 Griffbereit 99 :: Die Kugelblitze :: Kickerforum Süd +12 * :: Ballmagneten :: * +12 * :: Stahlstangen SC :: * +13 Die Stangendreher :: Die Rutschbahn-Raketen :: Griffgipfel Arena +13 Die Kugelblitze :: Die Querpassquäler :: Haltepunkt Hohlgriff +13 * :: Drehmoment 04 :: * +13 Turbo-Tischkicker :: Kurbelkönige :: Plattenpalast +13 Kick’n’Twist :: * :: * +13 Ballmagneten :: Griffbereit 99 :: Twistpark Westrand +13 Stahlstangen SC :: Die Gleitlager :: Griffgipfel Arena +14 Drehmoment 04 :: Ballmagneten :: Plattenpalast +14 Die Gleitlager :: Kurbelkönige :: Kickerklotz Kolosseum +14 * :: Turbo-Tischkicker :: * +14 Die Querpassquäler :: Die Stangendreher :: Der Tischkessel +14 Kick’n’Twist :: Die Kugelblitze :: Griffhalle West +14 * :: Die Rutschbahn-Raketen :: * +14 Griffbereit 99 :: Stahlstangen SC :: Kickerforum Süd +15 Die Stangendreher :: Die Gleitlager :: Griffgipfel Arena +15 Kurbelkönige :: * :: * +15 Turbo-Tischkicker :: * :: * +15 * :: Kick’n’Twist :: * +15 Die Rutschbahn-Raketen :: Die Kugelblitze :: Kurbelaue Nord +15 * :: Griffbereit 99 :: * +15 Ballmagneten :: Drehmoment 04 :: Twistpark Westrand +15 Stahlstangen SC :: Die Querpassquäler :: Griffgipfel Arena +16 * :: Die Stangendreher :: * +16 Die Kugelblitze :: Stahlstangen SC :: Bandenbruch-Stadion +16 * :: Kurbelkönige :: * +16 Die Gleitlager :: Die Rutschbahn-Raketen :: Kickerklotz Kolosseum +16 * :: Turbo-Tischkicker :: * +16 Die Querpassquäler :: Drehmoment 04 :: Drehpunkt Dome +16 Kick’n’Twist :: Ballmagneten :: Griffgipfel Arena +16 Griffbereit 99 :: * :: * +17 Die Stangendreher :: Die Kugelblitze :: Griffgipfel Arena +17 Kurbelkönige :: Die Querpassquäler :: Griffgipfel Arena +17 * :: Die Gleitlager :: * +17 Turbo-Tischkicker :: Drehmoment 04 :: Kurbelkurve Stadion +17 * :: Kick’n’Twist :: * +17 Die Rutschbahn-Raketen :: Griffbereit 99 :: Kurbelaue Nord +17 Ballmagneten :: * :: * +17 Stahlstangen SC :: * :: * +18 Die Stangendreher :: Ballmagneten :: Griffgipfel Arena +18 Die Kugelblitze :: * :: * +18 Drehmoment 04 :: Kurbelkönige :: Plattenpalast +18 Die Gleitlager :: * :: * +18 Die Querpassquäler :: Turbo-Tischkicker :: Drehpunkt Dome +18 Griffbereit 99 :: Kick’n’Twist :: Kickerforum Süd +18 Stahlstangen SC :: Die Rutschbahn-Raketen :: Griffgipfel Arena +19 Drehmoment 04 :: Griffbereit 99 :: Plattenpalast +19 Kurbelkönige :: Die Gleitlager :: Griffgipfel Arena +19 Turbo-Tischkicker :: Die Kugelblitze :: Kurbelkurve Stadion +19 Kick’n’Twist :: * :: * +19 Die Rutschbahn-Raketen :: Die Stangendreher :: Kurbelaue Nord +19 Ballmagneten :: Die Querpassquäler :: Twistpark Westrand +19 * :: Stahlstangen SC :: * +20 Die Stangendreher :: Griffbereit 99 :: Griffgipfel Arena +20 Die Kugelblitze :: * :: * +20 Kurbelkönige :: Stahlstangen SC :: Griffgipfel Arena +20 Die Gleitlager :: Drehmoment 04 :: Stangenstadion Nord +20 Turbo-Tischkicker :: Ballmagneten :: Plattenpalast +20 Die Querpassquäler :: Die Rutschbahn-Raketen :: Der Tischkessel +20 * :: Kick’n’Twist :: * +21 Die Kugelblitze :: Kurbelkönige :: Bandenbruch-Stadion +21 Drehmoment 04 :: * :: * +21 * :: Turbo-Tischkicker :: * +21 Die Querpassquäler :: * :: * +21 Kick’n’Twist :: Die Stangendreher :: Griffgipfel Arena +21 Die Rutschbahn-Raketen :: * :: * +21 Ballmagneten :: Die Gleitlager :: Twistpark Westrand +21 Stahlstangen SC :: Griffbereit 99 :: Griffgipfel Arena +22 Die Stangendreher :: Stahlstangen SC :: Griffgipfel Arena +22 * :: Die Kugelblitze :: * +22 * :: Drehmoment 04 :: * +22 Kurbelkönige :: * :: * +22 * :: Die Gleitlager :: * +22 Turbo-Tischkicker :: Griffbereit 99 :: Plattenpalast +22 * :: Die Querpassquäler :: * +22 Kick’n’Twist :: Die Rutschbahn-Raketen :: Griffgipfel Arena +22 * :: Ballmagneten :: * +23 * :: Die Stangendreher :: * +23 Drehmoment 04 :: Die Rutschbahn-Raketen :: Plattenpalast +23 Die Gleitlager :: Kick’n’Twist :: Stangenstadion Nord +23 Die Querpassquäler :: Die Kugelblitze :: Drehpunkt Dome +23 * :: Griffbereit 99 :: * +23 Ballmagneten :: Kurbelkönige :: Twistpark Westrand +23 Stahlstangen SC :: Turbo-Tischkicker :: Griffgipfel Arena +24 Die Stangendreher :: * :: * +24 Die Kugelblitze :: Kick’n’Twist :: Haltepunkt Hohlgriff +24 Drehmoment 04 :: * :: * +24 Kurbelkönige :: Die Rutschbahn-Raketen :: Griffgipfel Arena +24 Die Gleitlager :: Die Querpassquäler :: Stangenstadion Nord +24 Turbo-Tischkicker :: * :: * +24 Griffbereit 99 :: Ballmagneten :: Kickerforum Süd +24 Stahlstangen SC :: * :: * +25 * :: Die Stangendreher :: * +25 Die Kugelblitze :: Griffbereit 99 :: Bandenbruch-Stadion +25 Drehmoment 04 :: * :: * +25 Kurbelkönige :: Turbo-Tischkicker :: Griffgipfel Arena +25 Die Gleitlager :: Stahlstangen SC :: Kickerklotz Kolosseum +25 Die Querpassquäler :: Kick’n’Twist :: Drehpunkt Dome +25 Die Rutschbahn-Raketen :: * :: * +25 Ballmagneten :: * :: * +26 Die Stangendreher :: Drehmoment 04 :: Griffgipfel Arena +26 Kurbelkönige :: Kick’n’Twist :: Griffgipfel Arena +26 Die Gleitlager :: Die Kugelblitze :: Stangenstadion Nord +26 Die Querpassquäler :: * :: * +26 Die Rutschbahn-Raketen :: Turbo-Tischkicker :: Kurbelaue Nord +26 Griffbereit 99 :: * :: * +26 Stahlstangen SC :: Ballmagneten :: Griffgipfel Arena +27 Die Stangendreher :: Die Querpassquäler :: Griffgipfel Arena +27 Drehmoment 04 :: Stahlstangen SC :: Plattenpalast +27 * :: Die Gleitlager :: * +27 Turbo-Tischkicker :: Kick’n’Twist :: Kurbelkurve Stadion +27 * :: Die Rutschbahn-Raketen :: * +27 Griffbereit 99 :: Kurbelkönige :: Kickerforum Süd +27 Ballmagneten :: Die Kugelblitze :: Twistpark Westrand +28 Die Kugelblitze :: * :: * +28 Kurbelkönige :: * :: * +28 Turbo-Tischkicker :: Die Stangendreher :: Plattenpalast +28 * :: Die Querpassquäler :: * +28 Kick’n’Twist :: Drehmoment 04 :: Griffhalle West +28 Die Rutschbahn-Raketen :: * :: * +28 Griffbereit 99 :: Die Gleitlager :: Kickerforum Süd +28 * :: Ballmagneten :: * +28 Stahlstangen SC :: * :: * +29 Die Stangendreher :: Kurbelkönige :: Griffgipfel Arena +29 Drehmoment 04 :: Die Kugelblitze :: Rotationshalle 7 +29 Die Gleitlager :: Turbo-Tischkicker :: Stangenstadion Nord +29 Die Querpassquäler :: Griffbereit 99 :: Der Tischkessel +29 Kick’n’Twist :: Stahlstangen SC :: Griffgipfel Arena +29 Ballmagneten :: Die Rutschbahn-Raketen :: Twistpark Westrand +0 Die Plattenpanther :: Kurbelschmiede :: Die Platte am Fluss +0 Vollstoff United :: * :: * +0 Dynamo Drehpunkt :: * :: * +0 Reibungslose Racker :: Kickerkombinat :: Der Tischkessel +0 Drehakademie :: Die Abroller :: Kugeltempel Altstadt +0 Die Griffgötter :: Die Plattenspieler :: Kugelwerk Ost +0 Die Blockbuster :: Die Kurbelkicker :: Kugeltempel Altstadt +0 Die Drehlords :: * :: * +1 * :: Die Plattenpanther :: * +1 Vollstoff United :: Kickerkombinat :: Kugelkurve Klassik +1 * :: Reibungslose Racker :: * +1 Drehakademie :: Kurbelschmiede :: Kugeltempel Altstadt +1 Die Griffgötter :: Die Abroller :: Kugelwerk Ost +1 Die Blockbuster :: * :: * +1 Die Drehlords :: Die Kurbelkicker :: Griffhalle West +1 Die Plattenspieler :: Dynamo Drehpunkt :: Kurbelkurve Stadion +2 Die Plattenpanther :: Die Drehlords :: Die Platte am Fluss +2 Vollstoff United :: Die Kurbelkicker :: Kugelkurve Klassik +2 Dynamo Drehpunkt :: Kurbelschmiede :: Kugelkurve Klassik +2 Reibungslose Racker :: Die Blockbuster :: Der Tischkessel +2 Drehakademie :: * :: * +2 Die Griffgötter :: Kickerkombinat :: Kugelwerk Ost +2 Die Abroller :: Die Plattenspieler :: Rotationshalle 7 +3 Vollstoff United :: Dynamo Drehpunkt :: Kugelkurve Klassik +3 Kurbelschmiede :: * :: * +3 * :: Reibungslose Racker :: * +3 Drehakademie :: Die Plattenpanther :: Kugeltempel Altstadt +3 Die Griffgötter :: * :: * +3 Kickerkombinat :: Die Drehlords :: Drehpunkt Dome +3 Die Blockbuster :: * :: * +3 Die Kurbelkicker :: Die Plattenspieler :: Drehkraft-Dome +3 * :: Die Abroller :: * +4 Die Plattenpanther :: * :: * +4 Vollstoff United :: Die Griffgötter :: Kugelkurve Klassik +4 Dynamo Drehpunkt :: * :: * +4 Kurbelschmiede :: Die Abroller :: Gegenstoß-Galerie +4 Reibungslose Racker :: * :: * +4 Kickerkombinat :: Die Blockbuster :: Drehpunkt Dome +4 Die Drehlords :: Drehakademie :: Griffhalle West +4 * :: Die Kurbelkicker :: * +4 Die Plattenspieler :: * :: * +5 * :: Vollstoff United :: * +5 Kurbelschmiede :: Kickerkombinat :: Gegenstoß-Galerie +5 Drehakademie :: Die Plattenspieler :: Kugeltempel Altstadt +5 * :: Die Griffgötter :: * +5 Die Blockbuster :: Die Plattenpanther :: Kugeltempel Altstadt +5 Die Drehlords :: Dynamo Drehpunkt :: Griffhalle West +5 Die Kurbelkicker :: * :: * +5 Die Abroller :: Reibungslose Racker :: Twistpark Westrand +6 Vollstoff United :: Die Blockbuster :: Kugelkurve Klassik +6 Kurbelschmiede :: Die Kurbelkicker :: Gegenstoß-Galerie +6 Reibungslose Racker :: Dynamo Drehpunkt :: Der Tischkessel +6 Die Griffgötter :: Drehakademie :: Kugelwerk Ost +6 Kickerkombinat :: Die Plattenpanther :: Drehpunkt Dome +6 Die Drehlords :: Die Plattenspieler :: Griffhalle West +6 * :: Die Abroller :: * +7 Die Plattenpanther :: Reibungslose Racker :: Stangenwelle Stadion +7 Dynamo Drehpunkt :: Die Blockbuster :: Kugelkurve Klassik +7 Drehakademie :: Vollstoff United :: Kugeltempel Altstadt +7 Kickerkombinat :: Die Plattenspieler :: Drehpunkt Dome +7 Die Drehlords :: Kurbelschmiede :: Griffhalle West +7 Die Kurbelkicker :: Die Griffgötter :: Drehkraft-Dome +7 Die Abroller :: * :: * +8 Vollstoff United :: * :: * +8 Dynamo Drehpunkt :: Die Plattenpanther :: Kugelkurve Klassik +8 Kurbelschmiede :: * :: * +8 * :: Drehakademie :: * +8 Die Griffgötter :: Reibungslose Racker :: Schusshof Kurbeldorf +8 Die Blockbuster :: Die Plattenspieler :: Kugeltempel Altstadt +8 Die Drehlords :: Die Abroller :: Griffhalle West +8 Die Kurbelkicker :: Kickerkombinat :: Drehkraft-Dome +9 Die Plattenpanther :: Die Plattenspieler :: Die Platte am Fluss +9 Reibungslose Racker :: Kurbelschmiede :: Der Tischkessel +9 Drehakademie :: * :: * +9 Die Griffgötter :: * :: * +9 Kickerkombinat :: Dynamo Drehpunkt :: Drehpunkt Dome +9 Die Drehlords :: Die Blockbuster :: Griffhalle West +9 * :: Die Kurbelkicker :: * +9 Die Abroller :: Vollstoff United :: Rotationshalle 7 +10 * :: Die Plattenpanther :: * +10 Kurbelschmiede :: * :: * +10 * :: Kickerkombinat :: * +10 Die Blockbuster :: Drehakademie :: Kugeltempel Altstadt +10 Die Drehlords :: Die Griffgötter :: Griffhalle West +10 Die Kurbelkicker :: Reibungslose Racker :: Lagerblock Süd +10 Die Abroller :: Dynamo Drehpunkt :: Rotationshalle 7 +10 Die Plattenspieler :: Vollstoff United :: Kurbelkurve Stadion +11 Vollstoff United :: Reibungslose Racker :: Plattenpalast +11 Drehakademie :: Dynamo Drehpunkt :: Kugeltempel Altstadt +11 Die Griffgötter :: Die Plattenpanther :: Kugelwerk Ost +11 Kickerkombinat :: * :: * +11 Die Blockbuster :: * :: * +11 * :: Die Drehlords :: * +11 Die Abroller :: Die Kurbelkicker :: Rotationshalle 7 +11 Die Plattenspieler :: Kurbelschmiede :: Kurbelkurve Stadion +12 Dynamo Drehpunkt :: * :: * +12 Reibungslose Racker :: Die Plattenspieler :: Der Tischkessel +12 Die Griffgötter :: Kurbelschmiede :: Kugelwerk Ost +12 Kickerkombinat :: Drehakademie :: Drehpunkt Dome +12 Die Drehlords :: Vollstoff United :: Griffhalle West +12 Die Kurbelkicker :: Die Plattenpanther :: Drehkraft-Dome +12 Die Abroller :: Die Blockbuster :: Rotationshalle 7 +13 Die Plattenpanther :: Die Abroller :: Die Platte am Fluss +13 Kurbelschmiede :: Vollstoff United :: Gegenstoß-Galerie +13 Drehakademie :: Reibungslose Racker :: Stangenwelle Stadion +13 * :: Kickerkombinat :: * +13 Die Blockbuster :: Die Griffgötter :: Kugeltempel Altstadt +13 * :: Die Drehlords :: * +13 Die Kurbelkicker :: Dynamo Drehpunkt :: Drehkraft-Dome +13 Die Plattenspieler :: * :: * +14 Vollstoff United :: Die Plattenpanther :: Kugelkurve Klassik +14 Die Griffgötter :: Dynamo Drehpunkt :: Kugelwerk Ost +14 Die Blockbuster :: Kurbelschmiede :: Kugeltempel Altstadt +14 Die Drehlords :: Reibungslose Racker :: Kurbelaue Nord +14 Die Kurbelkicker :: Drehakademie :: Drehkraft-Dome +14 Die Abroller :: Kickerkombinat :: Rotationshalle 7 +14 * :: Die Plattenspieler :: * +15 Die Plattenpanther :: Vollstoff United :: Die Platte am Fluss +15 * :: Dynamo Drehpunkt :: * +15 Reibungslose Racker :: * :: * +15 * :: Die Griffgötter :: * +15 * :: Die Blockbuster :: * +15 Die Drehlords :: Kickerkombinat :: Griffhalle West +15 * :: Die Kurbelkicker :: * +15 Die Abroller :: Kurbelschmiede :: Rotationshalle 7 +15 Die Plattenspieler :: Drehakademie :: Kurbelkurve Stadion +16 * :: Vollstoff United :: * +16 Dynamo Drehpunkt :: Die Plattenspieler :: Kugelkurve Klassik +16 * :: Kurbelschmiede :: * +16 Reibungslose Racker :: Die Plattenpanther :: Der Tischkessel +16 * :: Drehakademie :: * +16 Die Griffgötter :: * :: * +16 Die Blockbuster :: Kickerkombinat :: Kugeltempel Altstadt +16 Die Kurbelkicker :: Die Drehlords :: Drehkraft-Dome +16 * :: Die Abroller :: * +17 Kurbelschmiede :: Dynamo Drehpunkt :: Gegenstoß-Galerie +17 * :: Reibungslose Racker :: * +17 Drehakademie :: Die Drehlords :: Kugeltempel Altstadt +17 Kickerkombinat :: Die Griffgötter :: Drehpunkt Dome +17 Die Kurbelkicker :: Vollstoff United :: Drehkraft-Dome +17 Die Abroller :: Die Plattenpanther :: Rotationshalle 7 +17 Die Plattenspieler :: Die Blockbuster :: Kurbelkurve Stadion +18 Die Plattenpanther :: * :: * +18 * :: Dynamo Drehpunkt :: * +18 Reibungslose Racker :: * :: * +18 * :: Drehakademie :: * +18 Die Blockbuster :: Vollstoff United :: Kugeltempel Altstadt +18 * :: Die Drehlords :: * +18 Die Kurbelkicker :: Kurbelschmiede :: Drehkraft-Dome +18 Die Abroller :: Die Griffgötter :: Rotationshalle 7 +18 Die Plattenspieler :: Kickerkombinat :: Kurbelkurve Stadion +19 Dynamo Drehpunkt :: Vollstoff United :: Kugelkurve Klassik +19 Kurbelschmiede :: Die Plattenpanther :: Gegenstoß-Galerie +19 Drehakademie :: * :: * +19 * :: Die Griffgötter :: * +19 Kickerkombinat :: Reibungslose Racker :: Kickerforum Süd +19 * :: Die Blockbuster :: * +19 Die Kurbelkicker :: Die Abroller :: Drehkraft-Dome +19 Die Plattenspieler :: Die Drehlords :: Kurbelkurve Stadion +20 Vollstoff United :: * :: * +20 * :: Dynamo Drehpunkt :: * +20 Kurbelschmiede :: Die Plattenspieler :: Gegenstoß-Galerie +20 Die Griffgötter :: Die Kurbelkicker :: Kugelwerk Ost +20 Kickerkombinat :: * :: * +20 Die Blockbuster :: Reibungslose Racker :: Twistpark Westrand +20 Die Drehlords :: Die Plattenpanther :: Griffhalle West +20 Die Abroller :: Drehakademie :: Rotationshalle 7 +21 Die Plattenpanther :: * :: * +21 Drehakademie :: Die Kurbelkicker :: Kugeltempel Altstadt +21 Die Griffgötter :: Vollstoff United :: Kugelwerk Ost +21 Kickerkombinat :: Kurbelschmiede :: Drehpunkt Dome +21 Die Blockbuster :: Dynamo Drehpunkt :: Kugeltempel Altstadt +21 Die Abroller :: Die Drehlords :: Rotationshalle 7 +21 Die Plattenspieler :: Reibungslose Racker :: Griffzentrum Westkurve +22 Dynamo Drehpunkt :: Die Kurbelkicker :: Kugelkurve Klassik +22 Kurbelschmiede :: Die Griffgötter :: Gegenstoß-Galerie +22 Reibungslose Racker :: Die Abroller :: Der Tischkessel +22 Drehakademie :: Die Blockbuster :: Kugeltempel Altstadt +22 Kickerkombinat :: Vollstoff United :: Drehpunkt Dome +22 Die Drehlords :: * :: * +22 Die Plattenspieler :: Die Plattenpanther :: Kurbelkurve Stadion +23 Die Plattenpanther :: Die Kurbelkicker :: Die Platte am Fluss +23 Dynamo Drehpunkt :: Die Abroller :: Kugelkurve Klassik +23 Kurbelschmiede :: Die Drehlords :: Gegenstoß-Galerie +23 Reibungslose Racker :: Vollstoff United :: Der Tischkessel +23 Drehakademie :: Kickerkombinat :: Kugeltempel Altstadt +23 * :: Die Blockbuster :: * +23 Die Plattenspieler :: Die Griffgötter :: Kurbelkurve Stadion +24 Die Plattenpanther :: Die Blockbuster :: Die Platte am Fluss +24 Vollstoff United :: Drehakademie :: Kugelkurve Klassik +24 Dynamo Drehpunkt :: Kickerkombinat :: Kugelkurve Klassik +24 * :: Kurbelschmiede :: * +24 Reibungslose Racker :: Die Kurbelkicker :: Der Tischkessel +24 Die Griffgötter :: Die Drehlords :: Kugelwerk Ost +24 Die Abroller :: * :: * +24 * :: Die Plattenspieler :: * +25 Die Plattenpanther :: Kickerkombinat :: Die Platte am Fluss +25 Vollstoff United :: Die Plattenspieler :: Kugelkurve Klassik +25 Dynamo Drehpunkt :: Die Drehlords :: Kugelkurve Klassik +25 Kurbelschmiede :: Drehakademie :: Gegenstoß-Galerie +25 Reibungslose Racker :: Die Griffgötter :: Der Tischkessel +25 Die Blockbuster :: Die Abroller :: Kugeltempel Altstadt +25 Die Kurbelkicker :: * :: * +26 Die Plattenpanther :: Die Griffgötter :: Die Platte am Fluss +26 * :: Vollstoff United :: * +26 Dynamo Drehpunkt :: Drehakademie :: Kugelkurve Klassik +26 Kurbelschmiede :: Die Blockbuster :: Gegenstoß-Galerie +26 Reibungslose Racker :: Die Drehlords :: Der Tischkessel +26 Kickerkombinat :: * :: * +26 Die Abroller :: * :: * +26 Die Plattenspieler :: Die Kurbelkicker :: Kurbelkurve Stadion +27 Die Plattenpanther :: Drehakademie :: Die Platte am Fluss +27 Vollstoff United :: Die Abroller :: Kugelkurve Klassik +27 Dynamo Drehpunkt :: Die Griffgötter :: Kugelkurve Klassik +27 Kurbelschmiede :: Reibungslose Racker :: Griffzentrum Westkurve +27 * :: Kickerkombinat :: * +27 Die Blockbuster :: Die Drehlords :: Kugeltempel Altstadt +27 Die Kurbelkicker :: * :: * +27 Die Plattenspieler :: * :: * +28 * :: Die Plattenpanther :: * +28 Vollstoff United :: Die Drehlords :: Kugelkurve Klassik +28 Dynamo Drehpunkt :: Reibungslose Racker :: Twistpark Westrand +28 * :: Kurbelschmiede :: * +28 Drehakademie :: Die Griffgötter :: Kugeltempel Altstadt +28 Kickerkombinat :: Die Abroller :: Drehpunkt Dome +28 Die Kurbelkicker :: Die Blockbuster :: Drehkraft-Dome +28 * :: Die Plattenspieler :: * +29 Die Plattenpanther :: Dynamo Drehpunkt :: Die Platte am Fluss +29 Vollstoff United :: Kurbelschmiede :: Kugelkurve Klassik +29 Reibungslose Racker :: Drehakademie :: Der Tischkessel +29 Die Griffgötter :: Die Blockbuster :: Kugelwerk Ost +29 Kickerkombinat :: Die Kurbelkicker :: Drehpunkt Dome +29 Die Drehlords :: * :: * +29 Die Plattenspieler :: Die Abroller :: Kurbelkurve Stadion +0 Stangenterror :: Die Bandenkrieger :: Kugelwerk Ost +0 FC Hartkurbel :: Griff & Gloria :: Drehkraft-Dome +0 Die Griffbanditen :: Tischgewitter :: Schusshof Kurbeldorf +0 Die Schussscheiben :: Die Kugeljäger :: Kickerklotz Kolosseum +0 * :: Kurbelclub Chaos :: * +0 Stangenhelden :: Die Rillenritter :: Bandenbruch-Stadion +0 Vollgas Vierer :: Die Tischwölfe :: Gegenstoß-Galerie +0 FC Gummipuffer :: Rotationssportverein :: Arena am Gleittisch +1 Die Griffbanditen :: Die Schussscheiben :: Schusshof Kurbeldorf +1 Tischgewitter :: Stangenterror :: Griffgipfel Arena +1 Kurbelclub Chaos :: Stangenhelden :: Tischkampfarena Alte Platte +1 Rotationssportverein :: FC Hartkurbel :: Drehkraft-Dome +1 Die Rillenritter :: FC Gummipuffer :: Kickerklotz Kolosseum +1 Vollgas Vierer :: Griff & Gloria :: Gegenstoß-Galerie +1 Die Tischwölfe :: * :: * +1 Die Bandenkrieger :: Die Kugeljäger :: Griffzentrum Westkurve +2 Stangenterror :: Die Schussscheiben :: Twistground Central +2 Die Kugeljäger :: Kurbelclub Chaos :: Griffgipfel Arena +2 Rotationssportverein :: * :: * +2 Die Rillenritter :: Die Griffbanditen :: Kickerklotz Kolosseum +2 Vollgas Vierer :: Stangenhelden :: Gegenstoß-Galerie +2 Die Tischwölfe :: FC Hartkurbel :: Gegenstoß-Galerie +2 Die Bandenkrieger :: Tischgewitter :: Griffzentrum Westkurve +2 Griff & Gloria :: FC Gummipuffer :: Drehkraft-Dome +3 FC Hartkurbel :: * :: * +3 Die Griffbanditen :: Vollgas Vierer :: Haltepunkt Hohlgriff +3 Tischgewitter :: FC Gummipuffer :: Griffgipfel Arena +3 Kurbelclub Chaos :: Stangenterror :: Tischkampfarena Alte Platte +3 Die Rillenritter :: Die Kugeljäger :: Kickerklotz Kolosseum +3 Die Tischwölfe :: Stangenhelden :: Gegenstoß-Galerie +3 Die Bandenkrieger :: Rotationssportverein :: Drehkraft-Dome +3 Griff & Gloria :: Die Schussscheiben :: Schusshof Kurbeldorf +4 Stangenterror :: Rotationssportverein :: Kugelwerk Ost +4 Tischgewitter :: Vollgas Vierer :: Griffgipfel Arena +4 * :: Die Schussscheiben :: * +4 Die Kugeljäger :: Die Tischwölfe :: Griffgipfel Arena +4 Die Rillenritter :: FC Hartkurbel :: Kickerklotz Kolosseum +4 Stangenhelden :: Die Bandenkrieger :: Die Platte am Fluss +4 FC Gummipuffer :: Kurbelclub Chaos :: Schusshof Kurbeldorf +4 Griff & Gloria :: Die Griffbanditen :: Drehkraft-Dome +5 Stangenterror :: Vollgas Vierer :: Kugelwerk Ost +5 FC Hartkurbel :: Die Bandenkrieger :: Drehkraft-Dome +5 Die Griffbanditen :: FC Gummipuffer :: Haltepunkt Hohlgriff +5 Tischgewitter :: Rotationssportverein :: Griffgipfel Arena +5 Die Schussscheiben :: Die Rillenritter :: Kickerklotz Kolosseum +5 Die Kugeljäger :: Stangenhelden :: Griffgipfel Arena +5 Kurbelclub Chaos :: Die Tischwölfe :: Tischkampfarena Alte Platte +5 * :: Griff & Gloria :: * +6 Die Schussscheiben :: Rotationssportverein :: Kickerklotz Kolosseum +6 Die Kugeljäger :: FC Hartkurbel :: Griffgipfel Arena +6 Kurbelclub Chaos :: Vollgas Vierer :: Tischkampfarena Alte Platte +6 * :: Die Rillenritter :: * +6 Stangenhelden :: Die Griffbanditen :: Die Platte am Fluss +6 Die Tischwölfe :: Stangenterror :: Gegenstoß-Galerie +6 FC Gummipuffer :: Die Bandenkrieger :: Arena am Gleittisch +6 Griff & Gloria :: Tischgewitter :: Schusshof Kurbeldorf +7 FC Hartkurbel :: Stangenterror :: Drehkraft-Dome +7 Die Griffbanditen :: * :: * +7 Tischgewitter :: Die Tischwölfe :: Griffgipfel Arena +7 Die Kugeljäger :: FC Gummipuffer :: Griffgipfel Arena +7 Rotationssportverein :: Stangenhelden :: Drehkraft-Dome +7 Die Rillenritter :: Die Bandenkrieger :: Kickerklotz Kolosseum +7 Vollgas Vierer :: Die Schussscheiben :: Twistpark Westrand +7 Griff & Gloria :: Kurbelclub Chaos :: Schusshof Kurbeldorf +8 Stangenterror :: FC Gummipuffer :: Kugelwerk Ost +8 Die Kugeljäger :: Die Griffbanditen :: Griffgipfel Arena +8 Rotationssportverein :: Griff & Gloria :: Drehkraft-Dome +8 Die Rillenritter :: Tischgewitter :: Kickerklotz Kolosseum +8 Stangenhelden :: FC Hartkurbel :: Die Platte am Fluss +8 * :: Vollgas Vierer :: * +8 Die Tischwölfe :: Die Schussscheiben :: Kickerforum Süd +8 Die Bandenkrieger :: Kurbelclub Chaos :: Griffzentrum Westkurve +9 Stangenterror :: Griff & Gloria :: Kugelwerk Ost +9 Die Griffbanditen :: FC Hartkurbel :: Haltepunkt Hohlgriff +9 Tischgewitter :: Die Kugeljäger :: Griffgipfel Arena +9 Die Schussscheiben :: Die Bandenkrieger :: Kickerklotz Kolosseum +9 Kurbelclub Chaos :: Die Rillenritter :: Tischkampfarena Alte Platte +9 Rotationssportverein :: Vollgas Vierer :: Drehkraft-Dome +9 * :: Stangenhelden :: * +9 FC Gummipuffer :: Die Tischwölfe :: Arena am Gleittisch +10 FC Hartkurbel :: FC Gummipuffer :: Drehkraft-Dome +10 * :: Tischgewitter :: * +10 Kurbelclub Chaos :: Die Griffbanditen :: Tischkampfarena Alte Platte +10 Rotationssportverein :: Die Kugeljäger :: Twistpark Westrand +10 Die Rillenritter :: Stangenterror :: Kickerklotz Kolosseum +10 Stangenhelden :: Die Schussscheiben :: Bandenbruch-Stadion +10 Die Tischwölfe :: Griff & Gloria :: Gegenstoß-Galerie +10 Die Bandenkrieger :: Vollgas Vierer :: Drehkraft-Dome +11 Stangenterror :: * :: * +11 Die Griffbanditen :: Die Bandenkrieger :: Haltepunkt Hohlgriff +11 Die Schussscheiben :: Tischgewitter :: Kickerklotz Kolosseum +11 Kurbelclub Chaos :: FC Hartkurbel :: Tischkampfarena Alte Platte +11 Stangenhelden :: FC Gummipuffer :: Die Platte am Fluss +11 Vollgas Vierer :: Die Kugeljäger :: Twistpark Westrand +11 Die Tischwölfe :: Rotationssportverein :: Gegenstoß-Galerie +11 Griff & Gloria :: Die Rillenritter :: Schusshof Kurbeldorf +12 FC Hartkurbel :: Die Schussscheiben :: Schusshof Kurbeldorf +12 Tischgewitter :: Kurbelclub Chaos :: Griffgipfel Arena +12 Die Kugeljäger :: Stangenterror :: Griffgipfel Arena +12 Rotationssportverein :: Die Griffbanditen :: Drehkraft-Dome +12 Die Rillenritter :: Die Tischwölfe :: Kickerklotz Kolosseum +12 Stangenhelden :: Griff & Gloria :: Die Platte am Fluss +12 Die Bandenkrieger :: * :: * +12 FC Gummipuffer :: Vollgas Vierer :: Arena am Gleittisch +13 Die Griffbanditen :: Stangenterror :: Haltepunkt Hohlgriff +13 Tischgewitter :: Stangenhelden :: Griffgipfel Arena +13 Kurbelclub Chaos :: Die Schussscheiben :: Tischkampfarena Alte Platte +13 Die Rillenritter :: Rotationssportverein :: Kickerklotz Kolosseum +13 Vollgas Vierer :: FC Hartkurbel :: Gegenstoß-Galerie +13 Die Bandenkrieger :: Die Tischwölfe :: Drehkraft-Dome +13 FC Gummipuffer :: * :: * +13 Griff & Gloria :: Die Kugeljäger :: Schusshof Kurbeldorf +14 FC Hartkurbel :: Tischgewitter :: Schusshof Kurbeldorf +14 Die Kugeljäger :: * :: * +14 Kurbelclub Chaos :: Rotationssportverein :: Tischkampfarena Alte Platte +14 Die Rillenritter :: Vollgas Vierer :: Kickerklotz Kolosseum +14 Stangenhelden :: Stangenterror :: Die Platte am Fluss +14 Die Tischwölfe :: Die Griffbanditen :: Gegenstoß-Galerie +14 FC Gummipuffer :: Die Schussscheiben :: Schusshof Kurbeldorf +14 Griff & Gloria :: Die Bandenkrieger :: Drehkraft-Dome +15 Stangenterror :: FC Hartkurbel :: Kugelwerk Ost +15 Die Griffbanditen :: Die Tischwölfe :: Haltepunkt Hohlgriff +15 Die Schussscheiben :: * :: * +15 Rotationssportverein :: Die Bandenkrieger :: Drehkraft-Dome +15 Die Rillenritter :: Griff & Gloria :: Kickerklotz Kolosseum +15 Stangenhelden :: Kurbelclub Chaos :: Bandenbruch-Stadion +15 Vollgas Vierer :: Tischgewitter :: Twistpark Westrand +15 FC Gummipuffer :: Die Kugeljäger :: Schusshof Kurbeldorf +16 FC Hartkurbel :: Kurbelclub Chaos :: Schusshof Kurbeldorf +16 Tischgewitter :: Die Griffbanditen :: Griffgipfel Arena +16 Die Kugeljäger :: Die Rillenritter :: Griffgipfel Arena +16 Rotationssportverein :: Die Schussscheiben :: Twistpark Westrand +16 Stangenhelden :: * :: * +16 Vollgas Vierer :: FC Gummipuffer :: Gegenstoß-Galerie +16 Die Tischwölfe :: Die Bandenkrieger :: Gegenstoß-Galerie +16 Griff & Gloria :: Stangenterror :: Drehkraft-Dome +17 Stangenterror :: Die Tischwölfe :: Kugelwerk Ost +17 * :: FC Hartkurbel :: * +17 Die Kugeljäger :: Die Schussscheiben :: Griffgipfel Arena +17 Die Rillenritter :: Kurbelclub Chaos :: Kickerklotz Kolosseum +17 Vollgas Vierer :: Die Griffbanditen :: Gegenstoß-Galerie +17 Die Bandenkrieger :: Stangenhelden :: Drehkraft-Dome +17 FC Gummipuffer :: Tischgewitter :: Schusshof Kurbeldorf +17 Griff & Gloria :: Rotationssportverein :: Drehkraft-Dome +18 Tischgewitter :: FC Hartkurbel :: Griffgipfel Arena +18 Die Schussscheiben :: Die Griffbanditen :: Kickerklotz Kolosseum +18 Kurbelclub Chaos :: FC Gummipuffer :: Tischkampfarena Alte Platte +18 Rotationssportverein :: Die Rillenritter :: Twistpark Westrand +18 Vollgas Vierer :: Stangenterror :: Gegenstoß-Galerie +18 Die Tischwölfe :: Die Kugeljäger :: Kickerforum Süd +18 * :: Die Bandenkrieger :: * +18 Griff & Gloria :: Stangenhelden :: Drehkraft-Dome +19 FC Hartkurbel :: Vollgas Vierer :: Drehkraft-Dome +19 * :: Die Griffbanditen :: * +19 Die Schussscheiben :: Griff & Gloria :: Kickerklotz Kolosseum +19 Kurbelclub Chaos :: Die Kugeljäger :: Tischkampfarena Alte Platte +19 Rotationssportverein :: Tischgewitter :: Twistpark Westrand +19 Stangenhelden :: Die Tischwölfe :: Die Platte am Fluss +19 Die Bandenkrieger :: Stangenterror :: Drehkraft-Dome +19 FC Gummipuffer :: Die Rillenritter :: Schusshof Kurbeldorf +20 Stangenterror :: Kurbelclub Chaos :: Twistground Central +20 FC Hartkurbel :: Stangenhelden :: Drehkraft-Dome +20 Die Griffbanditen :: Griff & Gloria :: Haltepunkt Hohlgriff +20 Tischgewitter :: Die Bandenkrieger :: Griffgipfel Arena +20 Die Schussscheiben :: FC Gummipuffer :: Kickerklotz Kolosseum +20 * :: Die Kugeljäger :: * +20 Vollgas Vierer :: Rotationssportverein :: Gegenstoß-Galerie +20 Die Tischwölfe :: Die Rillenritter :: Kickerforum Süd +21 FC Hartkurbel :: Die Rillenritter :: Schusshof Kurbeldorf +21 Tischgewitter :: Griff & Gloria :: Griffgipfel Arena +21 Die Schussscheiben :: Stangenterror :: Kickerklotz Kolosseum +21 Die Kugeljäger :: Die Bandenkrieger :: Griffgipfel Arena +21 Kurbelclub Chaos :: * :: * +21 Rotationssportverein :: Die Tischwölfe :: Drehkraft-Dome +21 Stangenhelden :: Vollgas Vierer :: Die Platte am Fluss +21 FC Gummipuffer :: Die Griffbanditen :: Arena am Gleittisch +22 Stangenterror :: Tischgewitter :: Twistground Central +22 FC Hartkurbel :: Die Griffbanditen :: Drehkraft-Dome +22 Die Schussscheiben :: Die Tischwölfe :: Kickerklotz Kolosseum +22 Rotationssportverein :: FC Gummipuffer :: Drehkraft-Dome +22 Die Rillenritter :: * :: * +22 Stangenhelden :: Die Kugeljäger :: Bandenbruch-Stadion +22 Vollgas Vierer :: Kurbelclub Chaos :: Twistpark Westrand +22 Die Bandenkrieger :: Griff & Gloria :: Drehkraft-Dome +23 FC Hartkurbel :: Die Kugeljäger :: Schusshof Kurbeldorf +23 Die Griffbanditen :: Die Rillenritter :: Schusshof Kurbeldorf +23 Tischgewitter :: * :: * +23 Die Schussscheiben :: Stangenhelden :: Kickerklotz Kolosseum +23 Kurbelclub Chaos :: Griff & Gloria :: Tischkampfarena Alte Platte +23 Rotationssportverein :: Stangenterror :: Drehkraft-Dome +23 Die Tischwölfe :: Vollgas Vierer :: Gegenstoß-Galerie +23 Die Bandenkrieger :: FC Gummipuffer :: Drehkraft-Dome +24 Die Griffbanditen :: Stangenhelden :: Haltepunkt Hohlgriff +24 Die Schussscheiben :: Kurbelclub Chaos :: Kickerklotz Kolosseum +24 Die Kugeljäger :: Vollgas Vierer :: Griffgipfel Arena +24 * :: Rotationssportverein :: * +24 Die Tischwölfe :: Tischgewitter :: Kickerforum Süd +24 Die Bandenkrieger :: Die Rillenritter :: Griffzentrum Westkurve +24 FC Gummipuffer :: Stangenterror :: Arena am Gleittisch +24 Griff & Gloria :: FC Hartkurbel :: Drehkraft-Dome +25 Stangenterror :: Stangenhelden :: Kugelwerk Ost +25 FC Hartkurbel :: Die Tischwölfe :: Drehkraft-Dome +25 Die Griffbanditen :: Rotationssportverein :: Haltepunkt Hohlgriff +25 Die Kugeljäger :: Griff & Gloria :: Griffgipfel Arena +25 Kurbelclub Chaos :: Tischgewitter :: Tischkampfarena Alte Platte +25 Die Rillenritter :: Die Schussscheiben :: Kickerklotz Kolosseum +25 Vollgas Vierer :: Die Bandenkrieger :: Gegenstoß-Galerie +25 * :: FC Gummipuffer :: * +26 * :: Stangenterror :: * +26 Tischgewitter :: Die Rillenritter :: Griffgipfel Arena +26 Die Schussscheiben :: FC Hartkurbel :: Kickerklotz Kolosseum +26 Die Kugeljäger :: Rotationssportverein :: Griffgipfel Arena +26 Die Tischwölfe :: Kurbelclub Chaos :: Kickerforum Süd +26 Die Bandenkrieger :: Die Griffbanditen :: Drehkraft-Dome +26 FC Gummipuffer :: Stangenhelden :: Arena am Gleittisch +26 Griff & Gloria :: Vollgas Vierer :: Drehkraft-Dome +27 Stangenterror :: Die Rillenritter :: Twistground Central +27 FC Hartkurbel :: Rotationssportverein :: Drehkraft-Dome +27 Die Griffbanditen :: Die Kugeljäger :: Schusshof Kurbeldorf +27 Die Schussscheiben :: Vollgas Vierer :: Kickerklotz Kolosseum +27 Kurbelclub Chaos :: Die Bandenkrieger :: Tischkampfarena Alte Platte +27 Stangenhelden :: Tischgewitter :: Bandenbruch-Stadion +27 * :: Die Tischwölfe :: * +27 FC Gummipuffer :: Griff & Gloria :: Arena am Gleittisch +28 Stangenterror :: Die Kugeljäger :: Twistground Central +28 Die Griffbanditen :: Kurbelclub Chaos :: Schusshof Kurbeldorf +28 Tischgewitter :: Die Schussscheiben :: Griffgipfel Arena +28 Stangenhelden :: Rotationssportverein :: Die Platte am Fluss +28 Vollgas Vierer :: Die Rillenritter :: Twistpark Westrand +28 Die Tischwölfe :: FC Gummipuffer :: Gegenstoß-Galerie +28 Die Bandenkrieger :: FC Hartkurbel :: Drehkraft-Dome +28 Griff & Gloria :: * :: * +29 Stangenterror :: Die Griffbanditen :: Kugelwerk Ost +29 Die Kugeljäger :: Tischgewitter :: Griffgipfel Arena +29 Rotationssportverein :: Kurbelclub Chaos :: Twistpark Westrand +29 Die Rillenritter :: Stangenhelden :: Kickerklotz Kolosseum +29 Vollgas Vierer :: * :: * +29 Die Bandenkrieger :: Die Schussscheiben :: Griffzentrum Westkurve +29 FC Gummipuffer :: FC Hartkurbel :: Arena am Gleittisch +29 Griff & Gloria :: Die Tischwölfe :: Drehkraft-Dome diff --git a/index.html b/index.html new file mode 100644 index 0000000..298723e --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + Tabula Rasa + + + + + +
+ + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4adfed0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ortools diff --git a/style.css b/style.css new file mode 100644 index 0000000..47d2d84 --- /dev/null +++ b/style.css @@ -0,0 +1,21 @@ +body { + font-family: "HelveticaNeueLight", "HelveticaNeue-Light", "Helvetica Neue Light", "HelveticaNeue", "Helvetica Neue", "TeXGyreHerosRegular", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; font-weight:400; font-stretch:normal; +} + +table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 50%; + max-width: 50%; + box-sizing: border-box; +} + +thead { + position: sticky; + top: 0px; + background-color: Canvas; + text-align: left; +} + diff --git a/tabularasa.js b/tabularasa.js new file mode 100644 index 0000000..ee45493 --- /dev/null +++ b/tabularasa.js @@ -0,0 +1,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(); -- cgit v1.2.3