]> git.sesse.net Git - ultimatescore/blobdiff - roster/twofields.py
Check in some scripts based on OR-tools to try to generate good group schedules.
[ultimatescore] / roster / twofields.py
diff --git a/roster/twofields.py b/roster/twofields.py
new file mode 100644 (file)
index 0000000..faeb266
--- /dev/null
@@ -0,0 +1,169 @@
+from __future__ import print_function
+import sys
+from ortools.constraint_solver import pywrapcp
+
+
+num_groups = 2  # NOTE: 2 is hard-coded in some places.
+num_teams_per_group = 6
+num_teams = num_teams_per_group * num_teams_per_group
+num_rounds = (num_teams_per_group * (num_teams_per_group - 1)) // 2
+num_matches = num_rounds * num_groups
+
+def excitedness_weight(match_idx):
+  field = match_idx % num_groups
+  match_order = match_idx // num_groups
+  if field == 0:
+    return match_order + 5
+  else:
+    return match_order
+
+def team_name(team_idx):
+  if team_idx < num_teams_per_group:
+    return "A%d" % (team_idx)
+  else:
+    return "B%d" % (team_idx - num_teams_per_group)
+
+solver = pywrapcp.Solver("schedule_games")
+
+# Create match variables.
+matchnums = []
+for match_idx in range(num_matches):
+  matchnums.append(solver.IntVar(0, num_matches - 1, "matchnum(%i)" % (match_idx)))
+solver.Add(solver.AllDifferent(matchnums));
+
+# Create list of matches.
+match_num = 0
+excitedness = []
+home_teams_for_match_num = []
+away_teams_for_match_num = []
+for group in range(num_groups):
+  for team_idx_1 in range(num_teams_per_group):
+    for team_idx_2 in range(num_teams_per_group):
+      if team_idx_2 > team_idx_1:
+        real_team_idx_1 = team_idx_1 + num_teams_per_group * group
+        real_team_idx_2 = team_idx_2 + num_teams_per_group * group
+        home_teams_for_match_num.append(real_team_idx_1)
+        away_teams_for_match_num.append(real_team_idx_2)
+        if team_idx_2 - team_idx_1 == 1:
+          excitedness.append(5)
+        elif team_idx_2 - team_idx_1 == 2:
+          excitedness.append(2)
+        else:
+          excitedness.append(0)
+        print("matchnum %2d: %2d vs. %2d, excited: %d" % (match_num, real_team_idx_1, real_team_idx_2, excitedness[match_num]))
+        match_num = match_num + 1
+
+# Create match variables.
+home_teams = []
+away_teams = []
+for match_idx in range(num_matches):
+  home_teams.append(solver.IntVar(0, num_teams - 1, "home_team_on_match(%i)" % (match_idx)))
+  away_teams.append(solver.IntVar(0, num_teams - 1, "away_team_on_match(%i)" % (match_idx)))
+matches_flat = home_teams + away_teams
+
+for match_num in range(num_matches):
+  solver.Add(home_teams[match_num] == solver.Element(home_teams_for_match_num, matchnums[match_num]))
+  solver.Add(away_teams[match_num] == solver.Element(away_teams_for_match_num, matchnums[match_num]))
+
+# Fields always play opposing groups (FIXME?)
+for round_idx in range(num_rounds):
+  solver.Add((matchnums[round_idx * 2 + 0] >= num_rounds) != (matchnums[round_idx * 2 + 1] >= num_rounds))
+
+# A team can never play on the same field at the same time
+#for match_idx in range(num_rounds):
+#  solver.Add(home_teams[match_idx * 2 + 0] != home_teams[match_idx * 2 + 1])
+#  solver.Add(home_teams[match_idx * 2 + 0] != away_teams[match_idx * 2 + 1])
+#  solver.Add(away_teams[match_idx * 2 + 0] != home_teams[match_idx * 2 + 1])
+#  solver.Add(away_teams[match_idx * 2 + 0] != away_teams[match_idx * 2 + 1])
+
+plays_in_round = {}
+for team_idx in range(num_teams):
+  plays_in_round[team_idx] = {}
+  for round_idx in range(num_rounds):
+    plays_in_round[team_idx][round_idx] = (
+       (home_teams[round_idx * 2 + 0] == team_idx) +
+       (home_teams[round_idx * 2 + 1] == team_idx) +
+       (away_teams[round_idx * 2 + 0] == team_idx) +
+       (away_teams[round_idx * 2 + 1] == team_idx))
+
+# A team can never play two matches in a row
+for round_idx in range(num_rounds - 1):
+  for team_idx in range(num_teams):
+    solver.Add(plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 1] <= 1)
+
+# More waiting time is good
+tired_matches = []
+for round_idx in range(num_rounds - 2):
+  for team_idx in range(num_teams):
+    tired = plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] >= 2
+    tired_matches.append(tired)
+sum_tiredness = solver.Sum(tired_matches)
+
+# Double-tired is not cool
+for round_idx in range(num_rounds - 4):
+  for team_idx in range(num_teams):
+    #doubletired = plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] + plays_in_round[team_idx][round_idx + 4] >= 3
+    #tired_matches.append(doubletired * 100)
+    solver.Add(plays_in_round[team_idx][round_idx] + plays_in_round[team_idx][round_idx + 2] + plays_in_round[team_idx][round_idx + 4] < 3)
+
+
+## TFK can not play the first two matches
+solver.Add(home_teams[0] != 0)
+solver.Add(away_teams[0] != 0)
+solver.Add(home_teams[1] != 0)
+solver.Add(away_teams[1] != 0)
+solver.Add(home_teams[2] != 0)
+solver.Add(away_teams[2] != 0)
+solver.Add(home_teams[3] != 0)
+solver.Add(away_teams[3] != 0)
+
+# Group finals come last, and on the stream field.
+solver.Add((matchnums[num_matches - 2] == 0) + (matchnums[num_matches - 2] == num_rounds) >= 1)
+solver.Add((matchnums[num_matches - 4] == 0) + (matchnums[num_matches - 4] == num_rounds) >= 1)
+
+# Put the more exciting games later, and on stream fields
+sum_excitedness = solver.Sum([(matchnums[match_num].IndexOf(excitedness) * excitedness_weight(match_num)) for match_num in range(num_matches)])
+objective = solver.Maximize(sum_excitedness - 10 * sum_tiredness, 1)
+
+db = solver.Phase(matchnums, solver.CHOOSE_FIRST_UNBOUND,
+                  solver.ASSIGN_MIN_VALUE)
+search_log = solver.SearchLog(1000000, objective)
+#global_limit = solver.TimeLimit(1000)
+global_limit = solver.TimeLimit(100000000)
+
+solver.NewSearch(db, [search_log, objective, global_limit])
+while solver.NextSolution():
+  recently_played_0 = [False for team_idx in range(num_teams)]
+  recently_played_1 = [False for team_idx in range(num_teams)]
+  recently_played_2 = [False for team_idx in range(num_teams)]
+  recently_played_3 = [False for team_idx in range(num_teams)]
+  for i in range(num_matches // num_groups):
+    recently_played_4 = [False for team_idx in range(num_teams)]
+    print("%2d. " % (i), end='')
+    for g in range(num_groups):
+      j = i * num_groups + g
+      home_team = home_teams[j].Value()
+      away_team = away_teams[j].Value()
+      matchnum = matchnums[j].Value()
+
+      tiredness = 0
+      if recently_played_2[home_team]:
+        tiredness = tiredness + 1
+        if recently_played_0[home_team]:
+          tiredness = tiredness + 100
+      if recently_played_2[away_team]:
+        tiredness = tiredness + 1
+        if recently_played_0[away_team]:
+          tiredness = tiredness + 100
+      recently_played_4[home_team] = True
+      recently_played_4[away_team] = True
+      print("%s vs. %s  (matchnum %2d, excitedness %d*%2d, tiredness %3d)  " % (team_name(home_team), team_name(away_team), matchnum, excitedness[matchnum], excitedness_weight(j), tiredness), end='')
+    print()
+    recently_played_0 = recently_played_1
+    recently_played_1 = recently_played_2
+    recently_played_2 = recently_played_3
+    recently_played_3 = recently_played_4
+  
+  print()
+solver.EndSearch()