Tournament bracket
A single-elimination bracket for 2 to 16 players typed in seed order: the top seeds get the byes, seeds are placed so that the strongest meet as late as possible, results are typed match by match and winners go through round by round to a champion. A result can be corrected while the winner's next match is still to be played.
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
A single-elimination bracket for 2 to 16 players, typed in seed order - the strongest first. The bracket is the next power of two: the top seeds get the byes, and the seeds are placed so that the strongest meet as late as possible. Results are typed match by match, and the winners go through round by round to the final and a champion. A result can be corrected while the winner's next match is still to be played.
main.eml- the players and their checks, the menu, entering and correcting resultsbracket.eml- the draw: the bracket size, the seed order, the rounds, who meets whom and who goes through, the open and the correctable matchesview.eml- the bracket on screen, round by round
How each part works:
- The seed order is built by doubling: [1, 2] becomes [1, 4, 2, 3], then [1, 8, 4, 5, 2, 7, 3, 6] - each seed goes next to the one that makes the pair add up to the new size plus one. So if the better seed always wins, every round pairs the best player left with the worst: 1 and 2 can only meet in the final, 1 to 4 only from the semi-finals on. The corpus case
tournament-bracketpairs teams in the order they were listed, and there its two strongest teams meet in the semi-final - the draw decided that, not the ratings. - With fewer players than the bracket holds, the empty places are byes. They sit opposite the top seeds, so a bye is a free pass to the second round for the strongest, and two byes never meet.
- Every place after the first round is the winner of the match that feeds it, worked out again from the scores whenever it is needed, so correcting a result changes who goes through with nothing else to update. That is also why a result can only be corrected while its winner's next match has no score: after that, the later result belongs to a pairing that would no longer exist.
- A winner who was the lower seed is marked as an upset.
What is checked: names of 1 to 12 letters, each different in any case, 2 to 16 of them; a match from the list, typed as 2 or M2; a score as two numbers from 0 to 99 (3-1, 3:1 or 3 - 1), and never a draw - a knockout match needs a winner. An empty answer cancels.
Sessions: sessions/basic.in types six players, so seeds 1 and 2 get byes; enters two upsets in the quarter-finals, shows the bracket, and corrects one of them - Dee, not Eve, now goes on to meet Ana; then plays the semi-finals (one more upset) and the final, which makes Ana the champion. sessions/bad-input.in gives an empty list, a name with a digit, a name of 13 letters, the same name in capitals and an empty line after one player; then sixteen players, a full bracket with no byes; menu choices 0 and x; a correction before any result; matches M16, x, M and 9, none of them open; scores 3, a-b, 100-1, 3- and a draw. It then plays two first-round matches and the quarter-final they lead to, tries to correct a first-round result that can no longer change and a match not yet played, corrects the quarter-final and shows the bracket.
Built on the verified corpus case tournament-bracket (a single-elimination field that halves every round until one is left).
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== Tournament bracket ==
Type the players from the strongest down - seed 1 first - one name a line;
an empty line ends the list (2 to 16 players).
player 1>
At least 2 players are needed.
player 1> Al1
A name is 1 to 12 letters.
player 1> Abcdefghijklm
A name is 1 to 12 letters.
player 1> Ann
player 2> ANN
Ann is already on the list.
player 2>
At least 2 players are needed.
player 2> Ben
player 3> Cal
player 4> Dot
player 5> Ed
player 6> Flo
player 7> Gil
player 8> Hal
player 9> Ivy
player 10> Jo
player 11> Kit
player 12> Lou
player 13> Max
player 14> Ned
player 15> Ola
player 16> Pip
That makes 16, the most.
16 players: a bracket of 16, no byes.
Round of 16
M1 (1) Ann v (16) Pip
M2 (8) Hal v (9) Ivy
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
Quarter-finals
M9 winner of M1 v winner of M2
M10 winner of M3 v winner of M4
M11 winner of M5 v winner of M6
M12 winner of M7 v winner of M8
Semi-finals
M13 winner of M9 v winner of M10
M14 winner of M11 v winner of M12
Final
M15 winner of M13 v winner of M14
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 0
Pick a number from 1 to 4.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> x
Pick a number from 1 to 4.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 3
No result can be corrected now.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M1 (1) Ann v (16) Pip
M2 (8) Hal v (9) Ivy
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
match> M16
Type one of the matches listed.
match> x
Type one of the matches listed.
match> M
Type one of the matches listed.
match> 9
Type one of the matches listed.
match>
Cancelled.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M1 (1) Ann v (16) Pip
M2 (8) Hal v (9) Ivy
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
match> 1
score, Ann first (like 3-1)> 3
Type the score as two numbers from 0 to 99, like 3-1.
score, Ann first (like 3-1)> a-b
Type the score as two numbers from 0 to 99, like 3-1.
score, Ann first (like 3-1)> 100-1
Type the score as two numbers from 0 to 99, like 3-1.
score, Ann first (like 3-1)> 3-
Type the score as two numbers from 0 to 99, like 3-1.
score, Ann first (like 3-1)> 2-2
A knockout match needs a winner: give the score after extra time or penalties.
score, Ann first (like 3-1)>
Cancelled.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M1 (1) Ann v (16) Pip
M2 (8) Hal v (9) Ivy
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
match> 1
score, Ann first (like 3-1)> 2-0
M1: Ann beats Pip 2-0.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M2 (8) Hal v (9) Ivy
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
match> M2
score, Hal first (like 3-1)> 0-1
M2: Ivy beats Hal 1-0.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
M9 (1) Ann v (9) Ivy
match> M9
score, Ann first (like 3-1)> 1-1
A knockout match needs a winner: give the score after extra time or penalties.
score, Ann first (like 3-1)> 1-2
M9: Ivy beats Ann 2-1.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 3
Results that can still be corrected:
M9 (1) Ann 1-2 (9) Ivy
match> M1
Type one of the matches listed.
match> M3
Type one of the matches listed.
match>
Cancelled.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 3
Results that can still be corrected:
M9 (1) Ann 1-2 (9) Ivy
match> M9
score, Ann first (like 3-1)> 5-4
M9 corrected: Ann beats Ivy 5-4.
Ann takes Ivy's place in M13.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 1
Round of 16
M1 (1) Ann 2-0 (16) Pip -> Ann
M2 (8) Hal 0-1 (9) Ivy -> Ivy (upset)
M3 (4) Dot v (13) Max
M4 (5) Ed v (12) Lou
M5 (2) Ben v (15) Ola
M6 (7) Gil v (10) Jo
M7 (3) Cal v (14) Ned
M8 (6) Flo v (11) Kit
Quarter-finals
M9 (1) Ann 5-4 (9) Ivy -> Ann
M10 winner of M3 v winner of M4
M11 winner of M5 v winner of M6
M12 winner of M7 v winner of M8
Semi-finals
M13 (1) Ann v winner of M10
M14 winner of M11 v winner of M12
Final
M15 winner of M13 v winner of M14
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 4
Bye.
What was typed (57 lines)
Al1
Abcdefghijklm
Ann
ANN
Ben
Cal
Dot
Ed
Flo
Gil
Hal
Ivy
Jo
Kit
Lou
Max
Ned
Ola
Pip
0
x
3
2
M16
x
M
9
2
1
3
a-b
100-1
3-
2-2
2
1
2-0
2
M2
0-1
2
M9
1-1
1-2
3
M1
M3
3
M9
5-4
1
4
basic
interpreter: byte-equal== Tournament bracket ==
Type the players from the strongest down - seed 1 first - one name a line;
an empty line ends the list (2 to 16 players).
player 1> Ana
player 2> Bo
player 3> Cy
player 4> Dee
player 5> Eve
player 6> Fay
player 7>
6 players: a bracket of 8, with byes for seeds 1 and 2.
Quarter-finals
M1 (1) Ana bye -> Ana
M2 (4) Dee v (5) Eve
M3 (2) Bo bye -> Bo
M4 (3) Cy v (6) Fay
Semi-finals
M5 (1) Ana v winner of M2
M6 (2) Bo v winner of M4
Final
M7 winner of M5 v winner of M6
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M2 (4) Dee v (5) Eve
M4 (3) Cy v (6) Fay
match> M2
score, Dee first (like 3-1)> 1-3
M2: Eve beats Dee 3-1.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M4 (3) Cy v (6) Fay
M5 (1) Ana v (5) Eve
match> m4
score, Cy first (like 3-1)> 2-4
M4: Fay beats Cy 4-2.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 1
Quarter-finals
M1 (1) Ana bye -> Ana
M2 (4) Dee 1-3 (5) Eve -> Eve (upset)
M3 (2) Bo bye -> Bo
M4 (3) Cy 2-4 (6) Fay -> Fay (upset)
Semi-finals
M5 (1) Ana v (5) Eve
M6 (2) Bo v (6) Fay
Final
M7 winner of M5 v winner of M6
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 3
Results that can still be corrected:
M2 (4) Dee 1-3 (5) Eve
M4 (3) Cy 2-4 (6) Fay
match> M2
score, Dee first (like 3-1)> 3-1
M2 corrected: Dee beats Eve 3-1.
Dee takes Eve's place in M5.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M5 (1) Ana v (4) Dee
M6 (2) Bo v (6) Fay
match> 5
score, Ana first (like 3-1)> 2-1
M5: Ana beats Dee 2-1.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M6 (2) Bo v (6) Fay
match> 6
score, Bo first (like 3-1)> 0:1
M6: Fay beats Bo 1-0.
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 2
Open matches:
M7 (1) Ana v (6) Fay
match> 7
score, Ana first (like 3-1)> 3 - 2
M7: Ana beats Fay 3-2.
Ana is the champion!
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 1
Quarter-finals
M1 (1) Ana bye -> Ana
M2 (4) Dee 3-1 (5) Eve -> Dee
M3 (2) Bo bye -> Bo
M4 (3) Cy 2-4 (6) Fay -> Fay (upset)
Semi-finals
M5 (1) Ana 2-1 (4) Dee -> Ana
M6 (2) Bo 0-1 (6) Fay -> Fay (upset)
Final
M7 (1) Ana 3-2 (6) Fay -> Ana
Champion: (1) Ana
1) show the bracket 2) enter a result 3) correct a result 4) quit
choice> 4
Bye.
What was typed (28 lines)
Ana
Bo
Cy
Dee
Eve
Fay
2
M2
1-3
2
m4
2-4
1
3
M2
3-1
2
5
2-1
2
6
0:1
2
7
3 - 2
1
4
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# P034 tournament bracket: a single-elimination bracket for 2 to 16 players.
# The players are typed in seed order; the bracket is the next power of two,
# the top seeds get the byes, and seeds are placed so that the strongest meet
# as late as possible. Results are typed match by match, winners go through
# round by round, and a result can be corrected while the winner's next match
# is still to be played.
import bracket
import view
16 => most_players
12 => longest_name
99 => highest_score
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 lower(s):
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => big
"abcdefghijklmnopqrstuvwxyz" => small
"" => out
for c in s:
0 => k
while k < 26 and big[k] != c:
k + 1 => k
if k < 26:
out + small[k] => out
else:
out + c => out
return out
def is_name(s):
if s == "" or len(s) > longest_name:
return False
for c in s:
if not (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"):
return False
return True
def number(s):
# The value of 1 or 2 digits, otherwise -1.
if s == "" or len(s) > 2:
return -1
0 => n
for c in s:
if not (c in "0123456789"):
return -1
n * 10 + int(c) => n
return n
def parse_score(s):
# "3-1", "3:1" or "3 - 1" as [3, 1]; [] if it is not a score.
0 => k
while k < len(s) and s[k] != "-" and s[k] != ":":
k + 1 => k
if k == len(s):
return []
number(trim(s[0:k])) => a
number(trim(s[k + 1:len(s)])) => b
if a < 0 or b < 0:
return []
return [a, b]
def read_players():
[] => players
True => reading
while reading:
trim(input("player " + str(len(players) + 1) + "> ")) => name
if name == "":
if len(players) >= 2:
False => reading
else:
"At least 2 players are needed." ^0
elif not is_name(name):
("A name is 1 to " + str(longest_name) + " letters.") ^0
else:
"" => taken
for p in players:
if lower(p) == lower(name):
p => taken
if taken != "":
(taken + " is already on the list.") ^0
else:
players + [name] => players
if len(players) == most_players:
("That makes " + str(most_players) + ", the most.") ^0
False => reading
return players
def ask_match(prompt, allowed):
# One of the allowed match numbers (typed as 2 or M2), or -1 when the
# answer is empty.
while True:
trim(input(prompt)) => answer
if answer == "":
return -1
if answer[0] == "M" or answer[0] == "m":
answer[1:len(answer)] => answer
number(answer) - 1 => m
for a in allowed:
if a == m:
return m
"Type one of the matches listed." ^0
def ask_score(first):
# A score with a winner, or [] when the answer is empty.
while True:
trim(input("score, " + first + " first (like 3-1)> ")) => answer
if answer == "":
return []
parse_score(answer) => score
if len(score) == 0:
("Type the score as two numbers from 0 to " + str(highest_score) + ", like 3-1.") ^0
elif score[0] == score[1]:
"A knockout match needs a winner: give the score after extra time or penalties." ^0
else:
return score
def result_text(players, sides, score):
# "Eve beats Dee 3-1", winner first.
if score[0] > score[1]:
return players[sides[0]] + " beats " + players[sides[1]] + " " + str(score[0]) + "-" + str(score[1])
return players[sides[1]] + " beats " + players[sides[0]] + " " + str(score[1]) + "-" + str(score[0])
def with_score(scores, m, score):
scores[0:m] + [score] + scores[m + 1:len(scores)] => out
return out
def enter(players, size, scores):
bracket.open_matches(len(players), size, scores) => free
if len(free) == 0:
("The tournament is over: " + players[bracket.champion(len(players), size, scores)] + " is the champion.") ^0
return scores
"Open matches:" ^0
for m in free:
view.match_line(players, size, scores, m) ^0
ask_match("match> ", free) => m
if m == -1:
"Cancelled." ^0
return scores
bracket.pairs(len(players), size, scores)[0][m] => sides
ask_score(players[sides[0]]) => score
if len(score) == 0:
"Cancelled." ^0
return scores
with_score(scores, m, score) => scores
("M" + str(m + 1) + ": " + result_text(players, sides, score) + ".") ^0
bracket.champion(len(players), size, scores) => c
if c >= 0:
(players[c] + " is the champion!") ^0
return scores
def correct(players, size, scores):
bracket.correctable(size, scores) => fixable
if len(fixable) == 0:
"No result can be corrected now." ^0
return scores
"Results that can still be corrected:" ^0
for m in fixable:
view.match_line(players, size, scores, m) ^0
ask_match("match> ", fixable) => m
if m == -1:
"Cancelled." ^0
return scores
len(players) => n
bracket.pairs(n, size, scores) => pw
pw[0][m] => sides
pw[1][m] => before
ask_score(players[sides[0]]) => score
if len(score) == 0:
"Cancelled." ^0
return scores
with_score(scores, m, score) => scores
("M" + str(m + 1) + " corrected: " + result_text(players, sides, score) + ".") ^0
bracket.pairs(n, size, scores)[1][m] => after
if after != before:
bracket.next_match(size, m) => going
if going == -1:
(players[after] + " is the champion now.") ^0
else:
(players[after] + " takes " + players[before] + "'s place in M" + str(going + 1) + ".") ^0
return scores
"== Tournament bracket ==" ^0
"Type the players from the strongest down - seed 1 first - one name a line;" ^0
"an empty line ends the list (2 to 16 players)." ^0
read_players() => players
len(players) => n
bracket.size_for(n) => size
size - n => byes
if byes == 0:
(str(n) + " players: a bracket of " + str(size) + ", no byes.") ^0
elif byes == 1:
(str(n) + " players: a bracket of " + str(size) + ", with a bye for seed 1.") ^0
elif byes == 2:
(str(n) + " players: a bracket of " + str(size) + ", with byes for seeds 1 and 2.") ^0
else:
(str(n) + " players: a bracket of " + str(size) + ", with byes for seeds 1 to " + str(byes) + ".") ^0
[[] for m in [1:size - 1]] => scores
view.show(players, size, scores)
True => running
while running:
"" ^0
"1) show the bracket 2) enter a result 3) correct a result 4) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
view.show(players, size, scores)
elif choice == "2":
enter(players, size, scores) => scores
elif choice == "3":
correct(players, size, scores) => scores
elif choice == "4":
False => running
else:
"Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import bracket
import view
most_players = 16
longest_name = 12
highest_score = 99
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 lower(s):
big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
small = "abcdefghijklmnopqrstuvwxyz"
out = ""
for c in s:
k = 0
while k < 26 and big[k] != c:
k = k + 1
if k < 26:
out = out + small[k]
else:
out = out + c
return out
def is_name(s):
if s == "" or len(s) > longest_name:
return False
for c in s:
if not c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz":
return False
return True
def number(s):
if s == "" or len(s) > 2:
return -1
n = 0
for c in s:
if not c in "0123456789":
return -1
n = n * 10 + int(c)
return n
def parse_score(s):
k = 0
while k < len(s) and s[k] != "-" and s[k] != ":":
k = k + 1
if k == len(s):
return []
a = number(trim(s[0:k]))
b = number(trim(s[k + 1:len(s)]))
if a < 0 or b < 0:
return []
return [a, b]
def read_players():
players = []
reading = True
while reading:
name = trim(input("player " + str(len(players) + 1) + "> "))
if name == "":
if len(players) >= 2:
reading = False
else:
print("At least 2 players are needed.")
elif not is_name(name):
print("A name is 1 to " + str(longest_name) + " letters.")
else:
taken = ""
for p in players:
if lower(p) == lower(name):
taken = p
if taken != "":
print(taken + " is already on the list.")
else:
players = players + [name]
if len(players) == most_players:
print("That makes " + str(most_players) + ", the most.")
reading = False
return players
def ask_match(prompt, allowed):
while True:
answer = trim(input(prompt))
if answer == "":
return -1
if answer[0] == "M" or answer[0] == "m":
answer = answer[1:len(answer)]
m = number(answer) - 1
for a in allowed:
if a == m:
return m
print("Type one of the matches listed.")
def ask_score(first):
while True:
answer = trim(input("score, " + first + " first (like 3-1)> "))
if answer == "":
return []
score = parse_score(answer)
if len(score) == 0:
print("Type the score as two numbers from 0 to " + str(highest_score) + ", like 3-1.")
elif score[0] == score[1]:
print("A knockout match needs a winner: give the score after extra time or penalties.")
else:
return score
def result_text(players, sides, score):
if score[0] > score[1]:
return players[sides[0]] + " beats " + players[sides[1]] + " " + str(score[0]) + "-" + str(score[1])
return players[sides[1]] + " beats " + players[sides[0]] + " " + str(score[1]) + "-" + str(score[0])
def with_score(scores, m, score):
out = scores[0:m] + [score] + scores[m + 1:len(scores)]
return out
def enter(players, size, scores):
free = bracket.open_matches(len(players), size, scores)
if len(free) == 0:
print("The tournament is over: " + players[bracket.champion(len(players), size, scores)] + " is the champion.")
return scores
print("Open matches:")
for m in free:
print(view.match_line(players, size, scores, m))
m = ask_match("match> ", free)
if m == -1:
print("Cancelled.")
return scores
sides = bracket.pairs(len(players), size, scores)[0][m]
score = ask_score(players[sides[0]])
if len(score) == 0:
print("Cancelled.")
return scores
scores = with_score(scores, m, score)
print("M" + str(m + 1) + ": " + result_text(players, sides, score) + ".")
c = bracket.champion(len(players), size, scores)
if c >= 0:
print(players[c] + " is the champion!")
return scores
def correct(players, size, scores):
fixable = bracket.correctable(size, scores)
if len(fixable) == 0:
print("No result can be corrected now.")
return scores
print("Results that can still be corrected:")
for m in fixable:
print(view.match_line(players, size, scores, m))
m = ask_match("match> ", fixable)
if m == -1:
print("Cancelled.")
return scores
n = len(players)
pw = bracket.pairs(n, size, scores)
sides = pw[0][m]
before = pw[1][m]
score = ask_score(players[sides[0]])
if len(score) == 0:
print("Cancelled.")
return scores
scores = with_score(scores, m, score)
print("M" + str(m + 1) + " corrected: " + result_text(players, sides, score) + ".")
after = bracket.pairs(n, size, scores)[1][m]
if after != before:
going = bracket.next_match(size, m)
if going == -1:
print(players[after] + " is the champion now.")
else:
print(players[after] + " takes " + players[before] + "'s place in M" + str(going + 1) + ".")
return scores
print("== Tournament bracket ==")
print("Type the players from the strongest down - seed 1 first - one name a line;")
print("an empty line ends the list (2 to 16 players).")
players = read_players()
n = len(players)
size = bracket.size_for(n)
byes = size - n
if byes == 0:
print(str(n) + " players: a bracket of " + str(size) + ", no byes.")
elif byes == 1:
print(str(n) + " players: a bracket of " + str(size) + ", with a bye for seed 1.")
elif byes == 2:
print(str(n) + " players: a bracket of " + str(size) + ", with byes for seeds 1 and 2.")
else:
print(str(n) + " players: a bracket of " + str(size) + ", with byes for seeds 1 to " + str(byes) + ".")
scores = [[] for m in range(1, size)]
view.show(players, size, scores)
running = True
while running:
print("")
print("1) show the bracket 2) enter a result 3) correct a result 4) quit")
choice = trim(input("choice> "))
if choice == "1":
view.show(players, size, scores)
elif choice == "2":
scores = enter(players, size, scores)
elif choice == "3":
scores = correct(players, size, scores)
elif choice == "4":
running = False
else:
print("Pick a number from 1 to 4.")
print("Bye.")
bracket.eml
eml# P034 tournament bracket - the draw and who goes through. Players are
# numbered by seed, 0 for seed 1. A slot holds a player's number, -1 for a
# bye or -2 while it waits for the winner of an earlier match. Matches are
# numbered through the rounds: in a bracket of 8, M1-M4 are the
# quarter-finals, M5-M6 the semi-finals and M7 the final. A score is
# [first side, second side], or [] before the match is played.
def size_for(n):
# The smallest power of two that holds n players.
1 => size
while size < n:
size * 2 => size
return size
def seed_order(size):
# Seeds by position in the bracket, so that seeds 1 and 2 can only meet
# in the final, 1 to 4 only from the semi-finals on, and so on: each
# doubling puts seed s next to the seed that makes the pair add up to
# the new size plus one. 2 -> [1, 2]; 4 -> [1, 4, 2, 3];
# 8 -> [1, 8, 4, 5, 2, 7, 3, 6].
[1] => order
while len(order) < size:
2 * len(order) + 1 => total
[] => grown
for s in order:
grown + [s, total - s] => grown
grown => order
return order
def rounds(size):
# [first match, number of matches] for each round.
[] => out
0 => first
int(size / 2) => count
while count >= 1:
out + [[first, count]] => out
first + count => first
int(count / 2) => count
return out
def decide(a, b, score):
# The winner of one match, or -2 while it is not decided.
if a == -1:
return b
if b == -1:
return a
if a < 0 or b < 0 or len(score) == 0:
return -2
if score[0] > score[1]:
return a
return b
def pairs(n, size, scores):
# [first side, second side] of every match, and its winner:
# returns [sides, winners].
seed_order(size) => order
rounds(size) => rs
[] => sides
[] => winners
for i in [0:rs[0][1] - 1]:
[order[2 * i] - 1, order[2 * i + 1] - 1] => pair
for k in [0:1]:
if pair[k] >= n:
-1 => pair[k]
sides + [pair] => sides
winners + [decide(pair[0], pair[1], scores[i])] => winners
for r in [1:len(rs) - 1]:
rs[r - 1][0] => before
for i in [0:rs[r][1] - 1]:
[winners[before + 2 * i], winners[before + 2 * i + 1]] => pair
sides + [pair] => sides
winners + [decide(pair[0], pair[1], scores[rs[r][0] + i])] => winners
return [sides, winners]
def feeders(size, m):
# The two matches whose winners meet in match m, or [] for the first round.
rounds(size) => rs
for r in [1:len(rs) - 1]:
if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:
rs[r - 1][0] + 2 * (m - rs[r][0]) => f
return [f, f + 1]
return []
def next_match(size, m):
# The match the winner of m goes on to, or -1 after the final.
rounds(size) => rs
for r in [0:len(rs) - 2]:
if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:
return rs[r + 1][0] + int((m - rs[r][0]) / 2)
return -1
def open_matches(n, size, scores):
# Matches with two players known and no score yet.
pairs(n, size, scores)[0] => sides
[] => out
for m in [0:size - 2]:
if sides[m][0] >= 0 and sides[m][1] >= 0 and len(scores[m]) == 0:
out + [m] => out
return out
def correctable(size, scores):
# Played matches whose result can still change: the winner's next match
# has not been played yet (or it was the final).
[] => out
for m in [0:size - 2]:
if len(scores[m]) > 0:
next_match(size, m) => after
if after == -1 or len(scores[after]) == 0:
out + [m] => out
return out
def champion(n, size, scores):
# The winner of the final, or -2 before it is played.
return pairs(n, size, scores)[1][size - 2]
Python projection (bracket.py)
def size_for(n):
size = 1
while size < n:
size = size * 2
return size
def seed_order(size):
order = [1]
while len(order) < size:
total = 2 * len(order) + 1
grown = []
for s in order:
grown = grown + [s, total - s]
order = grown
return order
def rounds(size):
out = []
first = 0
count = int(size / 2)
while count >= 1:
out = out + [[first, count]]
first = first + count
count = int(count / 2)
return out
def decide(a, b, score):
if a == -1:
return b
if b == -1:
return a
if a < 0 or b < 0 or len(score) == 0:
return -2
if score[0] > score[1]:
return a
return b
def pairs(n, size, scores):
order = seed_order(size)
rs = rounds(size)
sides = []
winners = []
for i in range(0, rs[0][1]):
pair = [order[2 * i] - 1, order[2 * i + 1] - 1]
for k in range(0, 2):
if pair[k] >= n:
pair[k] = -1
sides = sides + [pair]
winners = winners + [decide(pair[0], pair[1], scores[i])]
for r in range(1, len(rs)):
before = rs[r - 1][0]
for i in range(0, rs[r][1]):
pair = [winners[before + 2 * i], winners[before + 2 * i + 1]]
sides = sides + [pair]
winners = winners + [decide(pair[0], pair[1], scores[rs[r][0] + i])]
return [sides, winners]
def feeders(size, m):
rs = rounds(size)
for r in range(1, len(rs)):
if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:
f = rs[r - 1][0] + 2 * (m - rs[r][0])
return [f, f + 1]
return []
def next_match(size, m):
rs = rounds(size)
for r in range(0, len(rs) - 2+1):
if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:
return rs[r + 1][0] + int((m - rs[r][0]) / 2)
return -1
def open_matches(n, size, scores):
sides = pairs(n, size, scores)[0]
out = []
for m in range(0, size - 2+1):
if sides[m][0] >= 0 and sides[m][1] >= 0 and len(scores[m]) == 0:
out = out + [m]
return out
def correctable(size, scores):
out = []
for m in range(0, size - 2+1):
if len(scores[m]) > 0:
after = next_match(size, m)
if after == -1 or len(scores[after]) == 0:
out = out + [m]
return out
def champion(n, size, scores):
return pairs(n, size, scores)[1][size - 2]
view.eml
eml# P034 tournament bracket - the bracket on screen, round by round.
import bracket
def pad(s, width):
while len(s) < width:
s + " " => s
return s
def without_trailing(s):
len(s) => j
while j > 0 and s[j - 1] == " ":
j - 1 => j
return s[0:j]
def round_name(players):
if players == 2:
return "Final"
if players == 4:
return "Semi-finals"
if players == 8:
return "Quarter-finals"
return "Round of " + str(players)
def label(players, i):
return "(" + str(i + 1) + ") " + players[i]
def score_text(score):
return str(score[0]) + "-" + str(score[1])
def cell(players, size, m, k, side):
# One side of match m: a player, a bye, or the match it waits for.
if side >= 0:
return label(players, side)
if side == -1:
return "bye"
return "winner of M" + str(bracket.feeders(size, m)[k] + 1)
def show(players, size, scores):
len(players) => n
bracket.pairs(n, size, scores) => pw
pw[0] => sides
pw[1] => winners
for r in bracket.rounds(size):
round_name(2 * r[1]) ^0
for m in [r[0]:r[0] + r[1] - 1]:
sides[m] => s
"v" => middle
if s[1] == -1:
"" => middle
elif len(scores[m]) > 0:
score_text(scores[m]) => middle
(" " + pad("M" + str(m + 1), 4) + pad(cell(players, size, m, 0, s[0]), 18) + pad(middle, 7) + pad(cell(players, size, m, 1, s[1]), 18)) => line
winners[m] => w
if w >= 0:
line + "-> " + players[w] => line
if s[0] >= 0 and s[1] >= 0 and w == max(s[0], s[1]):
line + " (upset)" => line
without_trailing(line) ^0
bracket.champion(n, size, scores) => c
if c >= 0:
("Champion: " + label(players, c)) ^0
def match_line(players, size, scores, m):
# " M2 (4) Dee v (5) Eve", or with the score when it was played.
bracket.pairs(len(players), size, scores)[0][m] => s
" v " => middle
if len(scores[m]) > 0:
" " + score_text(scores[m]) + " " => middle
return " " + pad("M" + str(m + 1), 4) + label(players, s[0]) + middle + label(players, s[1])
Python projection (view.py)
import bracket
def pad(s, width):
while len(s) < width:
s = s + " "
return s
def without_trailing(s):
j = len(s)
while j > 0 and s[j - 1] == " ":
j = j - 1
return s[0:j]
def round_name(players):
if players == 2:
return "Final"
if players == 4:
return "Semi-finals"
if players == 8:
return "Quarter-finals"
return "Round of " + str(players)
def label(players, i):
return "(" + str(i + 1) + ") " + players[i]
def score_text(score):
return str(score[0]) + "-" + str(score[1])
def cell(players, size, m, k, side):
if side >= 0:
return label(players, side)
if side == -1:
return "bye"
return "winner of M" + str(bracket.feeders(size, m)[k] + 1)
def show(players, size, scores):
n = len(players)
pw = bracket.pairs(n, size, scores)
sides = pw[0]
winners = pw[1]
for r in bracket.rounds(size):
print(round_name(2 * r[1]))
for m in range(r[0], r[0] + r[1]):
s = sides[m]
middle = "v"
if s[1] == -1:
middle = ""
elif len(scores[m]) > 0:
middle = score_text(scores[m])
line = " " + pad("M" + str(m + 1), 4) + pad(cell(players, size, m, 0, s[0]), 18) + pad(middle, 7) + pad(cell(players, size, m, 1, s[1]), 18)
w = winners[m]
if w >= 0:
line = line + "-> " + players[w]
if s[0] >= 0 and s[1] >= 0 and w == max(s[0], s[1]):
line = line + " (upset)"
print(without_trailing(line))
c = bracket.champion(n, size, scores)
if c >= 0:
print("Champion: " + label(players, c))
def match_line(players, size, scores, m):
s = bracket.pairs(len(players), size, scores)[0][m]
middle = " v "
if len(scores[m]) > 0:
middle = " " + score_text(scores[m]) + " "
return " " + pad("M" + str(m + 1), 4) + label(players, s[0]) + middle + label(players, s[1])