Teams and round robin
Split up to 16 rated players into two teams as even as possible - a backtracking search that drops any branch past half - or into three or four by a quick rule; then a league where everyone meets everyone once, scheduled by the circle method, with results entered as they come and a table sorted by points, goal difference and goals.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Split up to 16 rated players into teams of even strength, then play a league where every entrant meets every other once. The schedule comes from the circle method, results are entered as they come in, and the table follows the usual rules.
main.eml- the menu, players, teams, the league and its messagesteams.eml- splitting players into two teams by search, or into three or four by a quick ruleleague.eml- the schedule and the table
How each part works:
- Two teams are split as evenly as possible. One side's rating sum must not pass half the total and should come as close to it as it can. Backtracking finds it, as in the corpus case
subset-sum-backtracking: each player goes in or stays out, and any branch whose sum has passed half is dropped at once. That is only safe because every rating is positive. With an odd count the lighter side may have fewer players or more, so both team sizes are searched. - Three or four teams use a quick rule instead: the strongest players first, each to the team with the fewest players and then the lowest sum. It is close, but not always the best split, and the program says so.
- The schedule uses the circle method. Entrants sit around a circle with the first fixed, pairs face each other across it, and the others turn one place each round. Everyone meets everyone once in n - 1 rounds. With an odd count a rest takes the extra seat, so each entrant rests once and there are n rounds.
- The table gives 3 points for a win and 1 for a draw. Ties are broken by goal difference, then goals scored, then name.
What is checked: menu choices 1 to 8; players as a name of up to 12 characters, a space and a rating from 1 to 100, each name once, at most 16; 2 to 4 teams with at least two players each; 3 to 8 league entrants, each name once; a round and a match that exist and are not a rest; scores 0 to 99 written like 2-1. An empty answer cancels.
Sessions: sessions/basic.in loads ten sample players. - Two teams of exactly 325 each. - Three teams by the quick rule, 34 apart, the four-player team the strongest. - A league of those three teams: one match a round, one team resting. - A league of five named teams, four results, the schedule with the scores, and the table.
sessions/bad-input.in gives: - menu choices 0 and x; - teams, a league, a result and the table before any exist; - player lines: "Ben", "Cy 0", "Dee 101", a long name, Ana twice, 5 and x; - two teams of four players, 127 against 131, the closest there is; - leagues of two entrants, and with A twice, then A, B, C; - rounds 0, 4 and x; match 3; the rest in round 1; and the scores "2-", a-b and 100-1; - the empty table.
Built on the verified corpus case subset-sum-backtracking (subsets that reach a target, with branches pruned once they overshoot).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Teams and round robin ==
Split players into even teams, then play a league where everyone meets everyone once.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 0
Pick a number from 1 to 8.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> x
Pick a number from 1 to 8.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 2
Add at least 4 players first.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)> 4
A league needs 3 to 8 entrants.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
No league yet.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 6
No league yet.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 1
One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.
player> Ana 72
player> Ben
Type a name, a space and a rating from 1 to 100.
player> Cy 0
Type a name, a space and a rating from 1 to 100.
player> Dee 101
Type a name, a space and a rating from 1 to 100.
player> A very long name here 50
Keep names to 12 characters.
player> Ana 60
There is a player called Ana already.
player> Eve 55
player> Fay 61
player>
3 players.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 2
Add at least 4 players first.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 1
One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.
player> 5
Type a name, a space and a rating from 1 to 100.
player> x
Type a name, a space and a rating from 1 to 100.
player>
3 players.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 1
One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.
player> Gil 70
player>
4 players.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 2
how many teams (2 to 4)> 2
The most even split there is, found by trying the choices and dropping those that pass half:
Team 1 127 Ana 72, Eve 55
Team 2 131 Fay 61, Gil 70
Strongest team minus weakest: 4.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)> A, B
A league needs 3 to 8 entrants.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)> A, B, A
A is there twice.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)> A, B, C
3 entrants: 3 rounds, each with 1 match and one entrant resting.
Round 1
1. A rests
2. B - C
Round 2
1. C - A
2. B rests
Round 3
1. A - B
2. C rests
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 3)> 0
Type a round from 1 to 3.
round (1 to 3)> 4
Type a round from 1 to 3.
round (1 to 3)> x
Type a round from 1 to 3.
round (1 to 3)>
Cancelled.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 3)> 1
match (1 to 2)> 3
Type a match from 1 to 2.
match (1 to 2)>
Cancelled.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 3)> 1
match (1 to 2)> 1
That one is a rest, with nothing to score.
match (1 to 2)> 2
B - C, score (like 2-1)> 2-
Type the two scores with - between, like 2-1.
B - C, score (like 2-1)> a-b
Type the two scores with - between, like 2-1.
B - C, score (like 2-1)> 100-1
Type the two scores with - between, like 2-1.
B - C, score (like 2-1)>
Cancelled.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 6
team P W D L GF GA GD Pts
1. A 0 0 0 0 0 0 0 0
2. B 0 0 0 0 0 0 0 0
3. C 0 0 0 0 0 0 0 0
0 of 3 matches played. Win 3, draw 1; ties go to goal difference, then goals scored.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 8
Bye.
What was typed (52 lines)
0
x
2
3
4
5
6
1
Ana 72
Ben
Cy 0
Dee 101
A very long name here 50
Ana 60
Eve 55
Fay 61
2
1
5
x
1
Gil 70
2
2
3
A, B
3
A, B, A
3
A, B, C
5
0
4
x
5
1
3
5
1
1
2
2-
a-b
100-1
6
8
basic
interpreter: byte-equal== Teams and round robin ==
Split players into even teams, then play a league where everyone meets everyone once.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 7
Ten sample players, rated 38 to 90.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 2
how many teams (2 to 4)> 2
The most even split there is, found by trying the choices and dropping those that pass half:
Team 1 325 Ana 72, Ben 55, Dev 64, Ivan 58, Jo 76
Team 2 325 Chloe 81, Ema 47, Finn 90, Gus 38, Hana 69
Strongest team minus weakest: 0.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 2
how many teams (2 to 4)> 3
Strongest first, each to the weakest team so far - close, though not always the best:
Team 1 239 Finn 90, Dev 64, Ema 47, Gus 38
Team 2 205 Chloe 81, Hana 69, Ben 55
Team 3 206 Jo 76, Ana 72, Ivan 58
Strongest team minus weakest: 34.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)>
3 entrants: 3 rounds, each with 1 match and one entrant resting.
Round 1
1. Team 1 rests
2. Team 2 - Team 3
Round 2
1. Team 3 - Team 1
2. Team 2 rests
Round 3
1. Team 1 - Team 2
2. Team 3 rests
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 3
entrants, separated by commas (nothing to use the teams)> Lions, Tigers, Bears, Wolves, Hawks
5 entrants: 5 rounds, each with 2 matches and one entrant resting.
Round 1
1. Lions rests
2. Tigers - Hawks
3. Bears - Wolves
Round 2
1. Hawks - Lions
2. Wolves rests
3. Tigers - Bears
Round 3
1. Lions - Wolves
2. Hawks - Bears
3. Tigers rests
Round 4
1. Bears - Lions
2. Wolves - Tigers
3. Hawks rests
Round 5
1. Lions - Tigers
2. Bears rests
3. Wolves - Hawks
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 5)> 1
match (1 to 3)> 2
Tigers - Hawks, score (like 2-1)> 2-1
Round 1: Tigers 2-1 Hawks.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 5)> 1
match (1 to 3)> 3
Bears - Wolves, score (like 2-1)> 0-0
Round 1: Bears 0-0 Wolves.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 5)> 2
match (1 to 3)> 1
Hawks - Lions, score (like 2-1)> 3-1
Round 2: Hawks 3-1 Lions.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 5
round (1 to 5)> 2
match (1 to 3)> 3
Tigers - Bears, score (like 2-1)> 1-1
Round 2: Tigers 1-1 Bears.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 4
Round 1
1. Lions rests
2. Tigers - Hawks 2-1
3. Bears - Wolves 0-0
Round 2
1. Hawks - Lions 3-1
2. Wolves rests
3. Tigers - Bears 1-1
Round 3
1. Lions - Wolves
2. Hawks - Bears
3. Tigers rests
Round 4
1. Bears - Lions
2. Wolves - Tigers
3. Hawks rests
Round 5
1. Lions - Tigers
2. Bears rests
3. Wolves - Hawks
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 6
team P W D L GF GA GD Pts
1. Tigers 2 1 1 0 3 2 +1 4
2. Hawks 2 1 0 1 4 3 +1 3
3. Bears 2 0 2 0 1 1 0 2
4. Wolves 1 0 1 0 0 0 0 1
5. Lions 1 0 0 1 1 3 -2 0
4 of 10 matches played. Win 3, draw 1; ties go to goal difference, then goals scored.
1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit
choice> 8
Bye.
What was typed (28 lines)
7
2
2
2
3
3
3
Lions, Tigers, Bears, Wolves, Hawks
5
1
2
2-1
5
1
3
0-0
5
2
1
3-1
5
2
3
1-1
4
6
8
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P058 teams and round robin: split players into teams of even strength,
# then play a league where everyone meets everyone once - a schedule by the
# circle method, results as they come in, and the table.
import teams
import league
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s):
if s == "" or len(s) > 6:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def padded(s, width):
while len(s) < width:
s + " " => s
return s
def right(s, width):
while len(s) < width:
" " + s => s
return s
def add_players(state):
"One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop." ^0
while len(state[0]) < 16:
trim(input("player> ")) => line
if line == "":
break
len(line) - 1 => k
while k > 0 and line[k] != " ":
k - 1 => k
trim(line[0:k]) => name
number(line[k + 1:len(line)]) => rating
False => taken
for p in state[0]:
if p[0] == name:
True => taken
if k <= 0 or name == "" or rating < 1 or rating > 100:
"Type a name, a space and a rating from 1 to 100." ^0
elif len(name) > 12:
"Keep names to 12 characters." ^0
elif taken:
("There is a player called " + name + " already.") ^0
else:
state[0] + [[name, rating]] => state[0]
if len(state[0]) == 16:
"16 players is the most." ^0
(str(len(state[0])) + " players.") ^0
def show_team(label, members):
"" => s
for p in members:
if s != "":
s + ", " => s
s + p[0] + " " + str(p[1]) => s
(padded(label, 8) + right(str(teams.total(members)), 4) + " " + s) ^0
def make_teams(state):
if len(state[0]) < 4:
"Add at least 4 players first." ^0
return
while True:
trim(input("how many teams (2 to 4)> ")) => answer
if answer == "":
"Cancelled." ^0
return
number(answer) => k
if k >= 2 and k <= 4 and k * 2 <= len(state[0]):
break
"Type 2, 3 or 4 - at least two players a team." ^0
if k == 2:
teams.two_teams(state[0]) => ts
"The most even split there is, found by trying the choices and dropping those that pass half:" ^0
else:
teams.many_teams(state[0], k) => ts
"Strongest first, each to the weakest team so far - close, though not always the best:" ^0
ts => state[1]
for t in [0:len(ts) - 1]:
show_team("Team " + str(t + 1), ts[t])
teams.total(ts[0]) => lo
lo => hi
for t in ts:
if teams.total(t) < lo:
teams.total(t) => lo
if teams.total(t) > hi:
teams.total(t) => hi
("Strongest team minus weakest: " + str(hi - lo) + ".") ^0
def start_league(state):
trim(input("entrants, separated by commas (nothing to use the teams)> ")) => line
[] => names
if line == "":
if len(state[1]) == 0:
"There are no teams yet - make some, or type the entrants." ^0
return
for t in [1:len(state[1])]:
names + ["Team " + str(t)] => names
else:
"" => word
for c in line + ",":
if c == ",":
trim(word) => w
if w != "":
names + [w] => names
"" => word
else:
word + c => word
for i in [0:len(names) - 1]:
if len(names[i]) > 12:
"Keep names to 12 characters." ^0
return
for j in [0:i - 1]:
if names[j] == names[i]:
(names[i] + " is there twice.") ^0
return
if len(names) < 3 or len(names) > 8:
"A league needs 3 to 8 entrants." ^0
return
names => state[2]
league.schedule(names) => state[3]
{} => state[4]
int(len(names) / 2) => per_round
str(len(names)) + " entrants: " + str(len(state[3])) + " rounds, each with " + str(per_round) + " match" => s
if per_round > 1:
s + "es" => s
if len(names) % 2 == 1:
s + " and one entrant resting" => s
(s + ".") ^0
show_schedule(state)
def entrant(state, i):
if i == -1:
return "(rest)"
return state[2][i]
def show_schedule(state):
if len(state[2]) == 0:
"No league yet." ^0
return
for r in [0:len(state[3]) - 1]:
("Round " + str(r + 1)) ^0
for m in [0:len(state[3][r]) - 1]:
state[3][r][m] => match
"" => score
r * 10 + m => key
if key in state[4]:
" " + str(state[4][key][0]) + "-" + str(state[4][key][1]) => score
if match[0] == -1 or match[1] == -1:
(" " + str(m + 1) + ". " + entrant(state, match[0] + match[1] + 1) + " rests") ^0
else:
entrant(state, match[1]) => away
if score != "":
padded(away, 12) + score => away
(" " + str(m + 1) + ". " + padded(entrant(state, match[0]), 12) + " - " + away) ^0
def enter_result(state):
if len(state[2]) == 0:
"No league yet." ^0
return
while True:
trim(input("round (1 to " + str(len(state[3])) + ")> ")) => answer
if answer == "":
"Cancelled." ^0
return
number(answer) => r
if r >= 1 and r <= len(state[3]):
break
("Type a round from 1 to " + str(len(state[3])) + ".") ^0
state[3][r - 1] => matches
while True:
trim(input("match (1 to " + str(len(matches)) + ")> ")) => answer
if answer == "":
"Cancelled." ^0
return
number(answer) => m
if m >= 1 and m <= len(matches):
if matches[m - 1][0] == -1 or matches[m - 1][1] == -1:
"That one is a rest, with nothing to score." ^0
else:
break
else:
("Type a match from 1 to " + str(len(matches)) + ".") ^0
matches[m - 1] => match
while True:
trim(input(entrant(state, match[0]) + " - " + entrant(state, match[1]) + ", score (like 2-1)> ")) => answer
if answer == "":
"Cancelled." ^0
return
0 => k
while k < len(answer) and answer[k] != "-":
k + 1 => k
number(trim(answer[0:k])) => a
-1 => b
if k < len(answer):
number(trim(answer[k + 1:len(answer)])) => b
if a >= 0 and b >= 0 and a <= 99 and b <= 99:
break
"Type the two scores with - between, like 2-1." ^0
[a, b] => state[4][(r - 1) * 10 + m - 1]
("Round " + str(r) + ": " + entrant(state, match[0]) + " " + str(a) + "-" + str(b) + " " + entrant(state, match[1]) + ".") ^0
def table(state):
if len(state[2]) == 0:
"No league yet." ^0
return
[] => results
for r in [0:len(state[3]) - 1]:
for m in [0:len(state[3][r]) - 1]:
r * 10 + m => key
if key in state[4]:
state[3][r][m] => match
results + [[match[0], match[1], state[4][key][0], state[4][key][1]]] => results
league.standings(state[2], results) => rows
" team P W D L GF GA GD Pts" ^0
for i in [0:len(rows) - 1]:
rows[i] => row
str(row[5] - row[6]) => gd
if row[5] - row[6] > 0:
"+" + gd => gd
(right(str(i + 1), 3) + ". " + padded(row[0], 12) + right(str(row[1]), 3) + right(str(row[2]), 3) + right(str(row[3]), 3) + right(str(row[4]), 3) + right(str(row[5]), 5) + right(str(row[6]), 4) + right(gd, 5) + right(str(row[7]), 5)) ^0
(str(len(results)) + " of " + str(int(len(state[2]) * (len(state[2]) - 1) / 2)) + " matches played. Win 3, draw 1; ties go to goal difference, then goals scored.") ^0
def sample(state):
[["Ana", 72], ["Ben", 55], ["Chloe", 81], ["Dev", 64], ["Ema", 47],
["Finn", 90], ["Gus", 38], ["Hana", 69], ["Ivan", 58], ["Jo", 76]] => state[0]
[] => state[1]
"Ten sample players, rated 38 to 90." ^0
"== Teams and round robin ==" ^0
"Split players into even teams, then play a league where everyone meets everyone once." ^0
# [players, teams, league entrants, schedule, results by round * 10 + match]
[[], [], [], [], {}] => state
True => running
while running:
"" ^0
"1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
add_players(state)
elif choice == "2":
make_teams(state)
elif choice == "3":
start_league(state)
elif choice == "4":
show_schedule(state)
elif choice == "5":
enter_result(state)
elif choice == "6":
table(state)
elif choice == "7":
sample(state)
elif choice == "8":
False => running
else:
"Pick a number from 1 to 8." ^0
"Bye." ^0
Python projection (main.py)
import teams
import league
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s):
if s == "" or len(s) > 6:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def padded(s, width):
while len(s) < width:
s = s + " "
return s
def right(s, width):
while len(s) < width:
s = " " + s
return s
def add_players(state):
print("One player a line, as a name and a rating from 1 to 100, like Ana 72; nothing to stop.")
while len(state[0]) < 16:
line = trim(input("player> "))
if line == "":
break
k = len(line) - 1
while k > 0 and line[k] != " ":
k = k - 1
name = trim(line[0:k])
rating = number(line[k + 1:len(line)])
taken = False
for p in state[0]:
if p[0] == name:
taken = True
if k <= 0 or name == "" or rating < 1 or rating > 100:
print("Type a name, a space and a rating from 1 to 100.")
elif len(name) > 12:
print("Keep names to 12 characters.")
elif taken:
print("There is a player called " + name + " already.")
else:
state[0] = state[0] + [[name, rating]]
if len(state[0]) == 16:
print("16 players is the most.")
print(str(len(state[0])) + " players.")
def show_team(label, members):
s = ""
for p in members:
if s != "":
s = s + ", "
s = s + p[0] + " " + str(p[1])
print(padded(label, 8) + right(str(teams.total(members)), 4) + " " + s)
def make_teams(state):
if len(state[0]) < 4:
print("Add at least 4 players first.")
return
while True:
answer = trim(input("how many teams (2 to 4)> "))
if answer == "":
print("Cancelled.")
return
k = number(answer)
if k >= 2 and k <= 4 and k * 2 <= len(state[0]):
break
print("Type 2, 3 or 4 - at least two players a team.")
if k == 2:
ts = teams.two_teams(state[0])
print("The most even split there is, found by trying the choices and dropping those that pass half:")
else:
ts = teams.many_teams(state[0], k)
print("Strongest first, each to the weakest team so far - close, though not always the best:")
state[1] = ts
for t in range(0, len(ts)):
show_team("Team " + str(t + 1), ts[t])
lo = teams.total(ts[0])
hi = lo
for t in ts:
if teams.total(t) < lo:
lo = teams.total(t)
if teams.total(t) > hi:
hi = teams.total(t)
print("Strongest team minus weakest: " + str(hi - lo) + ".")
def start_league(state):
line = trim(input("entrants, separated by commas (nothing to use the teams)> "))
names = []
if line == "":
if len(state[1]) == 0:
print("There are no teams yet - make some, or type the entrants.")
return
for t in range(1, len(state[1])+1):
names = names + ["Team " + str(t)]
else:
word = ""
for c in line + ",":
if c == ",":
w = trim(word)
if w != "":
names = names + [w]
word = ""
else:
word = word + c
for i in range(0, len(names)):
if len(names[i]) > 12:
print("Keep names to 12 characters.")
return
for j in range(0, i):
if names[j] == names[i]:
print(names[i] + " is there twice.")
return
if len(names) < 3 or len(names) > 8:
print("A league needs 3 to 8 entrants.")
return
state[2] = names
state[3] = league.schedule(names)
state[4] = {}
per_round = int(len(names) / 2)
s = str(len(names)) + " entrants: " + str(len(state[3])) + " rounds, each with " + str(per_round) + " match"
if per_round > 1:
s = s + "es"
if len(names) % 2 == 1:
s = s + " and one entrant resting"
print(s + ".")
show_schedule(state)
def entrant(state, i):
if i == -1:
return "(rest)"
return state[2][i]
def show_schedule(state):
if len(state[2]) == 0:
print("No league yet.")
return
for r in range(0, len(state[3])):
print("Round " + str(r + 1))
for m in range(0, len(state[3][r])):
match = state[3][r][m]
score = ""
key = r * 10 + m
if key in state[4]:
score = " " + str(state[4][key][0]) + "-" + str(state[4][key][1])
if match[0] == -1 or match[1] == -1:
print(" " + str(m + 1) + ". " + entrant(state, match[0] + match[1] + 1) + " rests")
else:
away = entrant(state, match[1])
if score != "":
away = padded(away, 12) + score
print(" " + str(m + 1) + ". " + padded(entrant(state, match[0]), 12) + " - " + away)
def enter_result(state):
if len(state[2]) == 0:
print("No league yet.")
return
while True:
answer = trim(input("round (1 to " + str(len(state[3])) + ")> "))
if answer == "":
print("Cancelled.")
return
r = number(answer)
if r >= 1 and r <= len(state[3]):
break
print("Type a round from 1 to " + str(len(state[3])) + ".")
matches = state[3][r - 1]
while True:
answer = trim(input("match (1 to " + str(len(matches)) + ")> "))
if answer == "":
print("Cancelled.")
return
m = number(answer)
if m >= 1 and m <= len(matches):
if matches[m - 1][0] == -1 or matches[m - 1][1] == -1:
print("That one is a rest, with nothing to score.")
else:
break
else:
print("Type a match from 1 to " + str(len(matches)) + ".")
match = matches[m - 1]
while True:
answer = trim(input(entrant(state, match[0]) + " - " + entrant(state, match[1]) + ", score (like 2-1)> "))
if answer == "":
print("Cancelled.")
return
k = 0
while k < len(answer) and answer[k] != "-":
k = k + 1
a = number(trim(answer[0:k]))
b = -1
if k < len(answer):
b = number(trim(answer[k + 1:len(answer)]))
if a >= 0 and b >= 0 and a <= 99 and b <= 99:
break
print("Type the two scores with - between, like 2-1.")
state[4][(r - 1) * 10 + m - 1] = [a, b]
print("Round " + str(r) + ": " + entrant(state, match[0]) + " " + str(a) + "-" + str(b) + " " + entrant(state, match[1]) + ".")
def table(state):
if len(state[2]) == 0:
print("No league yet.")
return
results = []
for r in range(0, len(state[3])):
for m in range(0, len(state[3][r])):
key = r * 10 + m
if key in state[4]:
match = state[3][r][m]
results = results + [[match[0], match[1], state[4][key][0], state[4][key][1]]]
rows = league.standings(state[2], results)
print(" team P W D L GF GA GD Pts")
for i in range(0, len(rows)):
row = rows[i]
gd = str(row[5] - row[6])
if row[5] - row[6] > 0:
gd = "+" + gd
print(right(str(i + 1), 3) + ". " + padded(row[0], 12) + right(str(row[1]), 3) + right(str(row[2]), 3) + right(str(row[3]), 3) + right(str(row[4]), 3) + right(str(row[5]), 5) + right(str(row[6]), 4) + right(gd, 5) + right(str(row[7]), 5))
print(str(len(results)) + " of " + str(int(len(state[2]) * (len(state[2]) - 1) / 2)) + " matches played. Win 3, draw 1; ties go to goal difference, then goals scored.")
def sample(state):
state[0] = [["Ana", 72], ["Ben", 55], ["Chloe", 81], ["Dev", 64], ["Ema", 47], ["Finn", 90], ["Gus", 38], ["Hana", 69], ["Ivan", 58], ["Jo", 76]]
state[1] = []
print("Ten sample players, rated 38 to 90.")
print("== Teams and round robin ==")
print("Split players into even teams, then play a league where everyone meets everyone once.")
state = [[], [], [], [], {}]
running = True
while running:
print("")
print("1) add players 2) teams 3) start a league 4) schedule 5) result 6) table 7) sample 8) quit")
choice = trim(input("choice> "))
if choice == "1":
add_players(state)
elif choice == "2":
make_teams(state)
elif choice == "3":
start_league(state)
elif choice == "4":
show_schedule(state)
elif choice == "5":
enter_result(state)
elif choice == "6":
table(state)
elif choice == "7":
sample(state)
elif choice == "8":
running = False
else:
print("Pick a number from 1 to 8.")
print("Bye.")
teams.eml
eml# P058 teams and round robin - splitting players into teams of even
# strength. A player is [name, rating], rating 1 to 100.
def total(players):
0 => s
for p in players:
s + p[1] => s
return s
def best_half(ratings, size):
# Which `size` of the ratings to put in the first team so that its sum
# comes as close to half the total as it can without passing it - the
# other team then has the same sum or a little more. Backtracking, as in
# the corpus case subset-sum-backtracking: at each player branch on in
# or out, and drop a branch once its sum has passed half, which only
# works because every rating is positive. Returns [chosen indexes, sum].
0 => all
for r in ratings:
all + r => all
int(all / 2) => cap
# best[0]: the chosen indexes so far, best[1]: their sum
[[], -1] => best
search(ratings, size, cap, 0, [], 0, best)
return best
def search(ratings, size, cap, i, chosen, sum, best):
if sum > cap:
return
if len(chosen) == size:
if sum > best[1]:
chosen => best[0]
sum => best[1]
return
if i == len(ratings) or len(chosen) + len(ratings) - i < size:
return
if best[1] == cap:
return
search(ratings, size, cap, i + 1, chosen + [i], sum + ratings[i], best)
search(ratings, size, cap, i + 1, chosen, sum, best)
def two_teams(players):
# [team one, team two], as even as the ratings allow: team one is the
# side whose sum is at most half, as close to half as it can be. With an
# odd count that side may be the one with fewer players or the one with
# more, so both sizes are searched.
[] => ratings
for p in players:
ratings + [p[1]] => ratings
best_half(ratings, int(len(players) / 2)) => b
if len(players) % 2 == 1:
best_half(ratings, int(len(players) / 2) + 1) => b2
if b2[1] > b[1]:
b2 => b
[] => one
[] => two
for i in [0:len(players) - 1]:
False => mine
for j in b[0]:
if j == i:
True => mine
if mine:
one + [players[i]] => one
else:
two + [players[i]] => two
return [one, two]
def many_teams(players, k):
# k teams by a quick rule, not a search: strongest player first, each
# to the team with the lowest sum so far among those with the fewest
# players. Close, but not always the best split.
players[0:len(players)] => order
# sort by rating, highest first (insertion sort; equal ratings keep
# their order)
for i in [1:len(order) - 1]:
order[i] => p
i => j
while j > 0 and order[j - 1][1] < p[1]:
order[j - 1] => order[j]
j - 1 => j
p => order[j]
[] => out
for t in [1:k]:
out + [[]] => out
for p in order:
0 => pick
for t in [1:k - 1]:
if len(out[t]) < len(out[pick]) or (len(out[t]) == len(out[pick]) and total(out[t]) < total(out[pick])):
t => pick
out[pick] + [p] => out[pick]
return out
Python projection (teams.py)
def total(players):
s = 0
for p in players:
s = s + p[1]
return s
def best_half(ratings, size):
all = 0
for r in ratings:
all = all + r
cap = int(all / 2)
best = [[], -1]
search(ratings, size, cap, 0, [], 0, best)
return best
def search(ratings, size, cap, i, chosen, sum, best):
if sum > cap:
return
if len(chosen) == size:
if sum > best[1]:
best[0] = chosen
best[1] = sum
return
if i == len(ratings) or len(chosen) + len(ratings) - i < size:
return
if best[1] == cap:
return
search(ratings, size, cap, i + 1, chosen + [i], sum + ratings[i], best)
search(ratings, size, cap, i + 1, chosen, sum, best)
def two_teams(players):
ratings = []
for p in players:
ratings = ratings + [p[1]]
b = best_half(ratings, int(len(players) / 2))
if len(players) % 2 == 1:
b2 = best_half(ratings, int(len(players) / 2) + 1)
if b2[1] > b[1]:
b = b2
one = []
two = []
for i in range(0, len(players)):
mine = False
for j in b[0]:
if j == i:
mine = True
if mine:
one = one + [players[i]]
else:
two = two + [players[i]]
return [one, two]
def many_teams(players, k):
order = players[0:len(players)]
for i in range(1, len(order)):
p = order[i]
j = i
while j > 0 and order[j - 1][1] < p[1]:
order[j] = order[j - 1]
j = j - 1
order[j] = p
out = []
for t in range(1, k+1):
out = out + [[]]
for p in order:
pick = 0
for t in range(1, k):
if len(out[t]) < len(out[pick]) or len(out[t]) == len(out[pick]) and total(out[t]) < total(out[pick]):
pick = t
out[pick] = out[pick] + [p]
return out
league.eml
eml# P058 teams and round robin - the schedule and the table.
def schedule(names):
# The circle method: put the entrants round a circle with the first one
# fixed in place, pair them across, and turn the others one step for
# each round. With an odd count a "bye" joins the circle, and whoever
# meets it rests that round. Everyone meets everyone once in n - 1
# rounds. Returns rounds of matches [home, away] as indexes, -1 = bye.
[] => idx
for i in [0:len(names) - 1]:
idx + [i] => idx
if len(idx) % 2 == 1:
idx + [-1] => idx
len(idx) => n
[] => rounds
for r in [1:n - 1]:
[] => matches
for m in [0:int(n / 2) - 1]:
idx[m] => a
idx[n - 1 - m] => b
# the fixed entrant switches home and away each round
if m == 0 and r % 2 == 0:
matches + [[b, a]] => matches
else:
matches + [[a, b]] => matches
rounds + [matches] => rounds
# turn: the last moves to second place, the rest shift along
[idx[0], idx[n - 1]] + idx[1:n - 1] => idx
return rounds
def standings(names, results):
# results: [home, away, home goals, away goals] for each match played.
# Rows [name, played, won, drawn, lost, for, against, points], sorted by
# points, then goal difference, then goals scored, then name.
[] => rows
for nm in names:
rows + [[nm, 0, 0, 0, 0, 0, 0, 0]] => rows
for res in results:
record(rows[res[0]], res[2], res[3])
record(rows[res[1]], res[3], res[2])
for i in [1:len(rows) - 1]:
rows[i] => row
i => j
while j > 0 and ahead(row, rows[j - 1]):
rows[j - 1] => rows[j]
j - 1 => j
row => rows[j]
return rows
def record(row, scored, conceded):
row[1] + 1 => row[1]
row[5] + scored => row[5]
row[6] + conceded => row[6]
if scored > conceded:
row[2] + 1 => row[2]
row[7] + 3 => row[7]
elif scored == conceded:
row[3] + 1 => row[3]
row[7] + 1 => row[7]
else:
row[4] + 1 => row[4]
def ahead(a, b):
if a[7] != b[7]:
return a[7] > b[7]
if a[5] - a[6] != b[5] - b[6]:
return a[5] - a[6] > b[5] - b[6]
if a[5] != b[5]:
return a[5] > b[5]
return a[0] < b[0]
Python projection (league.py)
def schedule(names):
idx = []
for i in range(0, len(names)):
idx = idx + [i]
if len(idx) % 2 == 1:
idx = idx + [-1]
n = len(idx)
rounds = []
for r in range(1, n):
matches = []
for m in range(0, int(n / 2)):
a = idx[m]
b = idx[n - 1 - m]
if m == 0 and r % 2 == 0:
matches = matches + [[b, a]]
else:
matches = matches + [[a, b]]
rounds = rounds + [matches]
idx = [idx[0], idx[n - 1]] + idx[1:n - 1]
return rounds
def standings(names, results):
rows = []
for nm in names:
rows = rows + [[nm, 0, 0, 0, 0, 0, 0, 0]]
for res in results:
record(rows[res[0]], res[2], res[3])
record(rows[res[1]], res[3], res[2])
for i in range(1, len(rows)):
row = rows[i]
j = i
while j > 0 and ahead(row, rows[j - 1]):
rows[j] = rows[j - 1]
j = j - 1
rows[j] = row
return rows
def record(row, scored, conceded):
row[1] = row[1] + 1
row[5] = row[5] + scored
row[6] = row[6] + conceded
if scored > conceded:
row[2] = row[2] + 1
row[7] = row[7] + 3
elif scored == conceded:
row[3] = row[3] + 1
row[7] = row[7] + 1
else:
row[4] = row[4] + 1
def ahead(a, b):
if a[7] != b[7]:
return a[7] > b[7]
if a[5] - a[6] != b[5] - b[6]:
return a[5] - a[6] > b[5] - b[6]
if a[5] != b[5]:
return a[5] > b[5]
return a[0] < b[0]