{
  "id": "P034",
  "slug": "tournament-bracket",
  "key": "P034-tournament-bracket",
  "title": "Tournament bracket",
  "summary": "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.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P034 - Tournament bracket\n\nA single-elimination bracket for 2 to 16 players, typed in seed order - the\nstrongest first. The bracket is the next power of two: the top seeds get the\nbyes, and the seeds are placed so that the strongest meet as late as\npossible. Results are typed match by match, and the winners go through round\nby round to the final and a champion. A result can be corrected while the\nwinner's next match is still to be played.\n\n- `main.eml` - the players and their checks, the menu, entering and\n  correcting results\n- `bracket.eml` - the draw: the bracket size, the seed order, the rounds, who\n  meets whom and who goes through, the open and the correctable matches\n- `view.eml` - the bracket on screen, round by round\n\nHow each part works:\n\n- The seed order is built by doubling: [1, 2] becomes [1, 4, 2, 3], then\n  [1, 8, 4, 5, 2, 7, 3, 6] - each seed goes next to the one that makes the\n  pair add up to the new size plus one. So if the better seed always wins,\n  every round pairs the best player left with the worst: 1 and 2 can only\n  meet in the final, 1 to 4 only from the semi-finals on. The corpus case\n  `tournament-bracket` pairs teams in the order they were listed, and there\n  its two strongest teams meet in the semi-final - the draw decided that, not\n  the ratings.\n- With fewer players than the bracket holds, the empty places are byes. They\n  sit opposite the top seeds, so a bye is a free pass to the second round for\n  the strongest, and two byes never meet.\n- Every place after the first round is the winner of the match that feeds\n  it, worked out again from the scores whenever it is needed, so correcting\n  a result changes who goes through with nothing else to update. That is\n  also why a result can only be corrected while its winner's next match has\n  no score: after that, the later result belongs to a pairing that would no\n  longer exist.\n- A winner who was the lower seed is marked as an upset.\n\nWhat is checked: names of 1 to 12 letters, each different in any case, 2 to\n16 of them; a match from the list, typed as `2` or `M2`; a score as two\nnumbers from 0 to 99 (`3-1`, `3:1` or `3 - 1`), and never a draw - a knockout\nmatch needs a winner. An empty answer cancels.\n\nSessions: `sessions/basic.in` types six players, so seeds 1 and 2 get byes;\nenters two upsets in the quarter-finals, shows the bracket, and corrects one\nof them - Dee, not Eve, now goes on to meet Ana; then plays the semi-finals\n(one more upset) and the final, which makes Ana the champion.\n`sessions/bad-input.in` gives an empty list, a name with a digit, a name of 13\nletters, the same name in capitals and an empty line after one player; then\nsixteen players, a full bracket with no byes; menu choices 0 and x; a\ncorrection before any result; matches M16, x, M and 9, none of them open;\nscores 3, a-b, 100-1, 3- and a draw. It then plays two first-round matches and\nthe quarter-final they lead to, tries to correct a first-round result that\ncan no longer change and a match not yet played, corrects the quarter-final\nand shows the bracket.\n\nBuilt on the verified corpus case `tournament-bracket` (a single-elimination\nfield that halves every round until one is left).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P034 tournament bracket: a single-elimination bracket for 2 to 16 players.\n# The players are typed in seed order; the bracket is the next power of two,\n# the top seeds get the byes, and seeds are placed so that the strongest meet\n# as late as possible. Results are typed match by match, winners go through\n# round by round, and a result can be corrected while the winner's next match\n# is still to be played.\nimport bracket\nimport view\n\n16 => most_players\n12 => longest_name\n99 => highest_score\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef lower(s):\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => big\n    \"abcdefghijklmnopqrstuvwxyz\" => small\n    \"\" => out\n    for c in s:\n        0 => k\n        while k < 26 and big[k] != c:\n            k + 1 => k\n        if k < 26:\n            out + small[k] => out\n        else:\n            out + c => out\n    return out\n\ndef is_name(s):\n    if s == \"\" or len(s) > longest_name:\n        return False\n    for c in s:\n        if not (c in \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"):\n            return False\n    return True\n\ndef number(s):\n    # The value of 1 or 2 digits, otherwise -1.\n    if s == \"\" or len(s) > 2:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef parse_score(s):\n    # \"3-1\", \"3:1\" or \"3 - 1\" as [3, 1]; [] if it is not a score.\n    0 => k\n    while k < len(s) and s[k] != \"-\" and s[k] != \":\":\n        k + 1 => k\n    if k == len(s):\n        return []\n    number(trim(s[0:k])) => a\n    number(trim(s[k + 1:len(s)])) => b\n    if a < 0 or b < 0:\n        return []\n    return [a, b]\n\ndef read_players():\n    [] => players\n    True => reading\n    while reading:\n        trim(input(\"player \" + str(len(players) + 1) + \"> \")) => name\n        if name == \"\":\n            if len(players) >= 2:\n                False => reading\n            else:\n                \"At least 2 players are needed.\" ^0\n        elif not is_name(name):\n            (\"A name is 1 to \" + str(longest_name) + \" letters.\") ^0\n        else:\n            \"\" => taken\n            for p in players:\n                if lower(p) == lower(name):\n                    p => taken\n            if taken != \"\":\n                (taken + \" is already on the list.\") ^0\n            else:\n                players + [name] => players\n                if len(players) == most_players:\n                    (\"That makes \" + str(most_players) + \", the most.\") ^0\n                    False => reading\n    return players\n\ndef ask_match(prompt, allowed):\n    # One of the allowed match numbers (typed as 2 or M2), or -1 when the\n    # answer is empty.\n    while True:\n        trim(input(prompt)) => answer\n        if answer == \"\":\n            return -1\n        if answer[0] == \"M\" or answer[0] == \"m\":\n            answer[1:len(answer)] => answer\n        number(answer) - 1 => m\n        for a in allowed:\n            if a == m:\n                return m\n        \"Type one of the matches listed.\" ^0\n\ndef ask_score(first):\n    # A score with a winner, or [] when the answer is empty.\n    while True:\n        trim(input(\"score, \" + first + \" first (like 3-1)> \")) => answer\n        if answer == \"\":\n            return []\n        parse_score(answer) => score\n        if len(score) == 0:\n            (\"Type the score as two numbers from 0 to \" + str(highest_score) + \", like 3-1.\") ^0\n        elif score[0] == score[1]:\n            \"A knockout match needs a winner: give the score after extra time or penalties.\" ^0\n        else:\n            return score\n\ndef result_text(players, sides, score):\n    # \"Eve beats Dee 3-1\", winner first.\n    if score[0] > score[1]:\n        return players[sides[0]] + \" beats \" + players[sides[1]] + \" \" + str(score[0]) + \"-\" + str(score[1])\n    return players[sides[1]] + \" beats \" + players[sides[0]] + \" \" + str(score[1]) + \"-\" + str(score[0])\n\ndef with_score(scores, m, score):\n    scores[0:m] + [score] + scores[m + 1:len(scores)] => out\n    return out\n\ndef enter(players, size, scores):\n    bracket.open_matches(len(players), size, scores) => free\n    if len(free) == 0:\n        (\"The tournament is over: \" + players[bracket.champion(len(players), size, scores)] + \" is the champion.\") ^0\n        return scores\n    \"Open matches:\" ^0\n    for m in free:\n        view.match_line(players, size, scores, m) ^0\n    ask_match(\"match> \", free) => m\n    if m == -1:\n        \"Cancelled.\" ^0\n        return scores\n    bracket.pairs(len(players), size, scores)[0][m] => sides\n    ask_score(players[sides[0]]) => score\n    if len(score) == 0:\n        \"Cancelled.\" ^0\n        return scores\n    with_score(scores, m, score) => scores\n    (\"M\" + str(m + 1) + \": \" + result_text(players, sides, score) + \".\") ^0\n    bracket.champion(len(players), size, scores) => c\n    if c >= 0:\n        (players[c] + \" is the champion!\") ^0\n    return scores\n\ndef correct(players, size, scores):\n    bracket.correctable(size, scores) => fixable\n    if len(fixable) == 0:\n        \"No result can be corrected now.\" ^0\n        return scores\n    \"Results that can still be corrected:\" ^0\n    for m in fixable:\n        view.match_line(players, size, scores, m) ^0\n    ask_match(\"match> \", fixable) => m\n    if m == -1:\n        \"Cancelled.\" ^0\n        return scores\n    len(players) => n\n    bracket.pairs(n, size, scores) => pw\n    pw[0][m] => sides\n    pw[1][m] => before\n    ask_score(players[sides[0]]) => score\n    if len(score) == 0:\n        \"Cancelled.\" ^0\n        return scores\n    with_score(scores, m, score) => scores\n    (\"M\" + str(m + 1) + \" corrected: \" + result_text(players, sides, score) + \".\") ^0\n    bracket.pairs(n, size, scores)[1][m] => after\n    if after != before:\n        bracket.next_match(size, m) => going\n        if going == -1:\n            (players[after] + \" is the champion now.\") ^0\n        else:\n            (players[after] + \" takes \" + players[before] + \"'s place in M\" + str(going + 1) + \".\") ^0\n    return scores\n\n\"== Tournament bracket ==\" ^0\n\"Type the players from the strongest down - seed 1 first - one name a line;\" ^0\n\"an empty line ends the list (2 to 16 players).\" ^0\nread_players() => players\nlen(players) => n\nbracket.size_for(n) => size\nsize - n => byes\nif byes == 0:\n    (str(n) + \" players: a bracket of \" + str(size) + \", no byes.\") ^0\nelif byes == 1:\n    (str(n) + \" players: a bracket of \" + str(size) + \", with a bye for seed 1.\") ^0\nelif byes == 2:\n    (str(n) + \" players: a bracket of \" + str(size) + \", with byes for seeds 1 and 2.\") ^0\nelse:\n    (str(n) + \" players: a bracket of \" + str(size) + \", with byes for seeds 1 to \" + str(byes) + \".\") ^0\n[[] for m in [1:size - 1]] => scores\nview.show(players, size, scores)\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) show the bracket  2) enter a result  3) correct a result  4) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        view.show(players, size, scores)\n    elif choice == \"2\":\n        enter(players, size, scores) => scores\n    elif choice == \"3\":\n        correct(players, size, scores) => scores\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import bracket\nimport view\nmost_players = 16\nlongest_name = 12\nhighest_score = 99\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef lower(s):\n    big = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    small = \"abcdefghijklmnopqrstuvwxyz\"\n    out = \"\"\n    for c in s:\n        k = 0\n        while k < 26 and big[k] != c:\n            k = k + 1\n        if k < 26:\n            out = out + small[k]\n        else:\n            out = out + c\n    return out\n\ndef is_name(s):\n    if s == \"\" or len(s) > longest_name:\n        return False\n    for c in s:\n        if not c in \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\":\n            return False\n    return True\n\ndef number(s):\n    if s == \"\" or len(s) > 2:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef parse_score(s):\n    k = 0\n    while k < len(s) and s[k] != \"-\" and s[k] != \":\":\n        k = k + 1\n    if k == len(s):\n        return []\n    a = number(trim(s[0:k]))\n    b = number(trim(s[k + 1:len(s)]))\n    if a < 0 or b < 0:\n        return []\n    return [a, b]\n\ndef read_players():\n    players = []\n    reading = True\n    while reading:\n        name = trim(input(\"player \" + str(len(players) + 1) + \"> \"))\n        if name == \"\":\n            if len(players) >= 2:\n                reading = False\n            else:\n                print(\"At least 2 players are needed.\")\n        elif not is_name(name):\n            print(\"A name is 1 to \" + str(longest_name) + \" letters.\")\n        else:\n            taken = \"\"\n            for p in players:\n                if lower(p) == lower(name):\n                    taken = p\n            if taken != \"\":\n                print(taken + \" is already on the list.\")\n            else:\n                players = players + [name]\n                if len(players) == most_players:\n                    print(\"That makes \" + str(most_players) + \", the most.\")\n                    reading = False\n    return players\n\ndef ask_match(prompt, allowed):\n    while True:\n        answer = trim(input(prompt))\n        if answer == \"\":\n            return -1\n        if answer[0] == \"M\" or answer[0] == \"m\":\n            answer = answer[1:len(answer)]\n        m = number(answer) - 1\n        for a in allowed:\n            if a == m:\n                return m\n        print(\"Type one of the matches listed.\")\n\ndef ask_score(first):\n    while True:\n        answer = trim(input(\"score, \" + first + \" first (like 3-1)> \"))\n        if answer == \"\":\n            return []\n        score = parse_score(answer)\n        if len(score) == 0:\n            print(\"Type the score as two numbers from 0 to \" + str(highest_score) + \", like 3-1.\")\n        elif score[0] == score[1]:\n            print(\"A knockout match needs a winner: give the score after extra time or penalties.\")\n        else:\n            return score\n\ndef result_text(players, sides, score):\n    if score[0] > score[1]:\n        return players[sides[0]] + \" beats \" + players[sides[1]] + \" \" + str(score[0]) + \"-\" + str(score[1])\n    return players[sides[1]] + \" beats \" + players[sides[0]] + \" \" + str(score[1]) + \"-\" + str(score[0])\n\ndef with_score(scores, m, score):\n    out = scores[0:m] + [score] + scores[m + 1:len(scores)]\n    return out\n\ndef enter(players, size, scores):\n    free = bracket.open_matches(len(players), size, scores)\n    if len(free) == 0:\n        print(\"The tournament is over: \" + players[bracket.champion(len(players), size, scores)] + \" is the champion.\")\n        return scores\n    print(\"Open matches:\")\n    for m in free:\n        print(view.match_line(players, size, scores, m))\n    m = ask_match(\"match> \", free)\n    if m == -1:\n        print(\"Cancelled.\")\n        return scores\n    sides = bracket.pairs(len(players), size, scores)[0][m]\n    score = ask_score(players[sides[0]])\n    if len(score) == 0:\n        print(\"Cancelled.\")\n        return scores\n    scores = with_score(scores, m, score)\n    print(\"M\" + str(m + 1) + \": \" + result_text(players, sides, score) + \".\")\n    c = bracket.champion(len(players), size, scores)\n    if c >= 0:\n        print(players[c] + \" is the champion!\")\n    return scores\n\ndef correct(players, size, scores):\n    fixable = bracket.correctable(size, scores)\n    if len(fixable) == 0:\n        print(\"No result can be corrected now.\")\n        return scores\n    print(\"Results that can still be corrected:\")\n    for m in fixable:\n        print(view.match_line(players, size, scores, m))\n    m = ask_match(\"match> \", fixable)\n    if m == -1:\n        print(\"Cancelled.\")\n        return scores\n    n = len(players)\n    pw = bracket.pairs(n, size, scores)\n    sides = pw[0][m]\n    before = pw[1][m]\n    score = ask_score(players[sides[0]])\n    if len(score) == 0:\n        print(\"Cancelled.\")\n        return scores\n    scores = with_score(scores, m, score)\n    print(\"M\" + str(m + 1) + \" corrected: \" + result_text(players, sides, score) + \".\")\n    after = bracket.pairs(n, size, scores)[1][m]\n    if after != before:\n        going = bracket.next_match(size, m)\n        if going == -1:\n            print(players[after] + \" is the champion now.\")\n        else:\n            print(players[after] + \" takes \" + players[before] + \"'s place in M\" + str(going + 1) + \".\")\n    return scores\n\nprint(\"== Tournament bracket ==\")\nprint(\"Type the players from the strongest down - seed 1 first - one name a line;\")\nprint(\"an empty line ends the list (2 to 16 players).\")\nplayers = read_players()\nn = len(players)\nsize = bracket.size_for(n)\nbyes = size - n\nif byes == 0:\n    print(str(n) + \" players: a bracket of \" + str(size) + \", no byes.\")\nelif byes == 1:\n    print(str(n) + \" players: a bracket of \" + str(size) + \", with a bye for seed 1.\")\nelif byes == 2:\n    print(str(n) + \" players: a bracket of \" + str(size) + \", with byes for seeds 1 and 2.\")\nelse:\n    print(str(n) + \" players: a bracket of \" + str(size) + \", with byes for seeds 1 to \" + str(byes) + \".\")\nscores = [[] for m in range(1, size)]\nview.show(players, size, scores)\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) show the bracket  2) enter a result  3) correct a result  4) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        view.show(players, size, scores)\n    elif choice == \"2\":\n        scores = enter(players, size, scores)\n    elif choice == \"3\":\n        scores = correct(players, size, scores)\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "bracket.eml",
      "eml": "# P034 tournament bracket - the draw and who goes through. Players are\n# numbered by seed, 0 for seed 1. A slot holds a player's number, -1 for a\n# bye or -2 while it waits for the winner of an earlier match. Matches are\n# numbered through the rounds: in a bracket of 8, M1-M4 are the\n# quarter-finals, M5-M6 the semi-finals and M7 the final. A score is\n# [first side, second side], or [] before the match is played.\n\ndef size_for(n):\n    # The smallest power of two that holds n players.\n    1 => size\n    while size < n:\n        size * 2 => size\n    return size\n\ndef seed_order(size):\n    # Seeds by position in the bracket, so that seeds 1 and 2 can only meet\n    # in the final, 1 to 4 only from the semi-finals on, and so on: each\n    # doubling puts seed s next to the seed that makes the pair add up to\n    # the new size plus one. 2 -> [1, 2]; 4 -> [1, 4, 2, 3];\n    # 8 -> [1, 8, 4, 5, 2, 7, 3, 6].\n    [1] => order\n    while len(order) < size:\n        2 * len(order) + 1 => total\n        [] => grown\n        for s in order:\n            grown + [s, total - s] => grown\n        grown => order\n    return order\n\ndef rounds(size):\n    # [first match, number of matches] for each round.\n    [] => out\n    0 => first\n    int(size / 2) => count\n    while count >= 1:\n        out + [[first, count]] => out\n        first + count => first\n        int(count / 2) => count\n    return out\n\ndef decide(a, b, score):\n    # The winner of one match, or -2 while it is not decided.\n    if a == -1:\n        return b\n    if b == -1:\n        return a\n    if a < 0 or b < 0 or len(score) == 0:\n        return -2\n    if score[0] > score[1]:\n        return a\n    return b\n\ndef pairs(n, size, scores):\n    # [first side, second side] of every match, and its winner:\n    # returns [sides, winners].\n    seed_order(size) => order\n    rounds(size) => rs\n    [] => sides\n    [] => winners\n    for i in [0:rs[0][1] - 1]:\n        [order[2 * i] - 1, order[2 * i + 1] - 1] => pair\n        for k in [0:1]:\n            if pair[k] >= n:\n                -1 => pair[k]\n        sides + [pair] => sides\n        winners + [decide(pair[0], pair[1], scores[i])] => winners\n    for r in [1:len(rs) - 1]:\n        rs[r - 1][0] => before\n        for i in [0:rs[r][1] - 1]:\n            [winners[before + 2 * i], winners[before + 2 * i + 1]] => pair\n            sides + [pair] => sides\n            winners + [decide(pair[0], pair[1], scores[rs[r][0] + i])] => winners\n    return [sides, winners]\n\ndef feeders(size, m):\n    # The two matches whose winners meet in match m, or [] for the first round.\n    rounds(size) => rs\n    for r in [1:len(rs) - 1]:\n        if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:\n            rs[r - 1][0] + 2 * (m - rs[r][0]) => f\n            return [f, f + 1]\n    return []\n\ndef next_match(size, m):\n    # The match the winner of m goes on to, or -1 after the final.\n    rounds(size) => rs\n    for r in [0:len(rs) - 2]:\n        if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:\n            return rs[r + 1][0] + int((m - rs[r][0]) / 2)\n    return -1\n\ndef open_matches(n, size, scores):\n    # Matches with two players known and no score yet.\n    pairs(n, size, scores)[0] => sides\n    [] => out\n    for m in [0:size - 2]:\n        if sides[m][0] >= 0 and sides[m][1] >= 0 and len(scores[m]) == 0:\n            out + [m] => out\n    return out\n\ndef correctable(size, scores):\n    # Played matches whose result can still change: the winner's next match\n    # has not been played yet (or it was the final).\n    [] => out\n    for m in [0:size - 2]:\n        if len(scores[m]) > 0:\n            next_match(size, m) => after\n            if after == -1 or len(scores[after]) == 0:\n                out + [m] => out\n    return out\n\ndef champion(n, size, scores):\n    # The winner of the final, or -2 before it is played.\n    return pairs(n, size, scores)[1][size - 2]\n",
      "python": "def size_for(n):\n    size = 1\n    while size < n:\n        size = size * 2\n    return size\n\ndef seed_order(size):\n    order = [1]\n    while len(order) < size:\n        total = 2 * len(order) + 1\n        grown = []\n        for s in order:\n            grown = grown + [s, total - s]\n        order = grown\n    return order\n\ndef rounds(size):\n    out = []\n    first = 0\n    count = int(size / 2)\n    while count >= 1:\n        out = out + [[first, count]]\n        first = first + count\n        count = int(count / 2)\n    return out\n\ndef decide(a, b, score):\n    if a == -1:\n        return b\n    if b == -1:\n        return a\n    if a < 0 or b < 0 or len(score) == 0:\n        return -2\n    if score[0] > score[1]:\n        return a\n    return b\n\ndef pairs(n, size, scores):\n    order = seed_order(size)\n    rs = rounds(size)\n    sides = []\n    winners = []\n    for i in range(0, rs[0][1]):\n        pair = [order[2 * i] - 1, order[2 * i + 1] - 1]\n        for k in range(0, 2):\n            if pair[k] >= n:\n                pair[k] = -1\n        sides = sides + [pair]\n        winners = winners + [decide(pair[0], pair[1], scores[i])]\n    for r in range(1, len(rs)):\n        before = rs[r - 1][0]\n        for i in range(0, rs[r][1]):\n            pair = [winners[before + 2 * i], winners[before + 2 * i + 1]]\n            sides = sides + [pair]\n            winners = winners + [decide(pair[0], pair[1], scores[rs[r][0] + i])]\n    return [sides, winners]\n\ndef feeders(size, m):\n    rs = rounds(size)\n    for r in range(1, len(rs)):\n        if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:\n            f = rs[r - 1][0] + 2 * (m - rs[r][0])\n            return [f, f + 1]\n    return []\n\ndef next_match(size, m):\n    rs = rounds(size)\n    for r in range(0, len(rs) - 2+1):\n        if m >= rs[r][0] and m < rs[r][0] + rs[r][1]:\n            return rs[r + 1][0] + int((m - rs[r][0]) / 2)\n    return -1\n\ndef open_matches(n, size, scores):\n    sides = pairs(n, size, scores)[0]\n    out = []\n    for m in range(0, size - 2+1):\n        if sides[m][0] >= 0 and sides[m][1] >= 0 and len(scores[m]) == 0:\n            out = out + [m]\n    return out\n\ndef correctable(size, scores):\n    out = []\n    for m in range(0, size - 2+1):\n        if len(scores[m]) > 0:\n            after = next_match(size, m)\n            if after == -1 or len(scores[after]) == 0:\n                out = out + [m]\n    return out\n\ndef champion(n, size, scores):\n    return pairs(n, size, scores)[1][size - 2]\n"
    },
    {
      "name": "view.eml",
      "eml": "# P034 tournament bracket - the bracket on screen, round by round.\nimport bracket\n\ndef pad(s, width):\n    while len(s) < width:\n        s + \" \" => s\n    return s\n\ndef without_trailing(s):\n    len(s) => j\n    while j > 0 and s[j - 1] == \" \":\n        j - 1 => j\n    return s[0:j]\n\ndef round_name(players):\n    if players == 2:\n        return \"Final\"\n    if players == 4:\n        return \"Semi-finals\"\n    if players == 8:\n        return \"Quarter-finals\"\n    return \"Round of \" + str(players)\n\ndef label(players, i):\n    return \"(\" + str(i + 1) + \") \" + players[i]\n\ndef score_text(score):\n    return str(score[0]) + \"-\" + str(score[1])\n\ndef cell(players, size, m, k, side):\n    # One side of match m: a player, a bye, or the match it waits for.\n    if side >= 0:\n        return label(players, side)\n    if side == -1:\n        return \"bye\"\n    return \"winner of M\" + str(bracket.feeders(size, m)[k] + 1)\n\ndef show(players, size, scores):\n    len(players) => n\n    bracket.pairs(n, size, scores) => pw\n    pw[0] => sides\n    pw[1] => winners\n    for r in bracket.rounds(size):\n        round_name(2 * r[1]) ^0\n        for m in [r[0]:r[0] + r[1] - 1]:\n            sides[m] => s\n            \"v\" => middle\n            if s[1] == -1:\n                \"\" => middle\n            elif len(scores[m]) > 0:\n                score_text(scores[m]) => middle\n            (\"  \" + 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\n            winners[m] => w\n            if w >= 0:\n                line + \"-> \" + players[w] => line\n                if s[0] >= 0 and s[1] >= 0 and w == max(s[0], s[1]):\n                    line + \" (upset)\" => line\n            without_trailing(line) ^0\n    bracket.champion(n, size, scores) => c\n    if c >= 0:\n        (\"Champion: \" + label(players, c)) ^0\n\ndef match_line(players, size, scores, m):\n    # \"  M2  (4) Dee v (5) Eve\", or with the score when it was played.\n    bracket.pairs(len(players), size, scores)[0][m] => s\n    \" v \" => middle\n    if len(scores[m]) > 0:\n        \" \" + score_text(scores[m]) + \" \" => middle\n    return \"  \" + pad(\"M\" + str(m + 1), 4) + label(players, s[0]) + middle + label(players, s[1])\n",
      "python": "import bracket\n\ndef pad(s, width):\n    while len(s) < width:\n        s = s + \" \"\n    return s\n\ndef without_trailing(s):\n    j = len(s)\n    while j > 0 and s[j - 1] == \" \":\n        j = j - 1\n    return s[0:j]\n\ndef round_name(players):\n    if players == 2:\n        return \"Final\"\n    if players == 4:\n        return \"Semi-finals\"\n    if players == 8:\n        return \"Quarter-finals\"\n    return \"Round of \" + str(players)\n\ndef label(players, i):\n    return \"(\" + str(i + 1) + \") \" + players[i]\n\ndef score_text(score):\n    return str(score[0]) + \"-\" + str(score[1])\n\ndef cell(players, size, m, k, side):\n    if side >= 0:\n        return label(players, side)\n    if side == -1:\n        return \"bye\"\n    return \"winner of M\" + str(bracket.feeders(size, m)[k] + 1)\n\ndef show(players, size, scores):\n    n = len(players)\n    pw = bracket.pairs(n, size, scores)\n    sides = pw[0]\n    winners = pw[1]\n    for r in bracket.rounds(size):\n        print(round_name(2 * r[1]))\n        for m in range(r[0], r[0] + r[1]):\n            s = sides[m]\n            middle = \"v\"\n            if s[1] == -1:\n                middle = \"\"\n            elif len(scores[m]) > 0:\n                middle = score_text(scores[m])\n            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)\n            w = winners[m]\n            if w >= 0:\n                line = line + \"-> \" + players[w]\n                if s[0] >= 0 and s[1] >= 0 and w == max(s[0], s[1]):\n                    line = line + \" (upset)\"\n            print(without_trailing(line))\n    c = bracket.champion(n, size, scores)\n    if c >= 0:\n        print(\"Champion: \" + label(players, c))\n\ndef match_line(players, size, scores, m):\n    s = bracket.pairs(len(players), size, scores)[0][m]\n    middle = \" v \"\n    if len(scores[m]) > 0:\n        middle = \" \" + score_text(scores[m]) + \" \"\n    return \"  \" + pad(\"M\" + str(m + 1), 4) + label(players, s[0]) + middle + label(players, s[1])\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "\nAl1\nAbcdefghijklm\nAnn\nANN\n\nBen\nCal\nDot\nEd\nFlo\nGil\nHal\nIvy\nJo\nKit\nLou\nMax\nNed\nOla\nPip\n0\nx\n3\n2\nM16\nx\nM\n9\n\n2\n1\n3\na-b\n100-1\n3-\n2-2\n\n2\n1\n2-0\n2\nM2\n0-1\n2\nM9\n1-1\n1-2\n3\nM1\nM3\n\n3\nM9\n5-4\n1\n4\n",
      "screen": "== Tournament bracket ==\nType the players from the strongest down - seed 1 first - one name a line;\nan empty line ends the list (2 to 16 players).\nplayer 1> \nAt least 2 players are needed.\nplayer 1> Al1\nA name is 1 to 12 letters.\nplayer 1> Abcdefghijklm\nA name is 1 to 12 letters.\nplayer 1> Ann\nplayer 2> ANN\nAnn is already on the list.\nplayer 2> \nAt least 2 players are needed.\nplayer 2> Ben\nplayer 3> Cal\nplayer 4> Dot\nplayer 5> Ed\nplayer 6> Flo\nplayer 7> Gil\nplayer 8> Hal\nplayer 9> Ivy\nplayer 10> Jo\nplayer 11> Kit\nplayer 12> Lou\nplayer 13> Max\nplayer 14> Ned\nplayer 15> Ola\nplayer 16> Pip\nThat makes 16, the most.\n16 players: a bracket of 16, no byes.\nRound of 16\n  M1  (1) Ann           v      (16) Pip\n  M2  (8) Hal           v      (9) Ivy\n  M3  (4) Dot           v      (13) Max\n  M4  (5) Ed            v      (12) Lou\n  M5  (2) Ben           v      (15) Ola\n  M6  (7) Gil           v      (10) Jo\n  M7  (3) Cal           v      (14) Ned\n  M8  (6) Flo           v      (11) Kit\nQuarter-finals\n  M9  winner of M1      v      winner of M2\n  M10 winner of M3      v      winner of M4\n  M11 winner of M5      v      winner of M6\n  M12 winner of M7      v      winner of M8\nSemi-finals\n  M13 winner of M9      v      winner of M10\n  M14 winner of M11     v      winner of M12\nFinal\n  M15 winner of M13     v      winner of M14\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 3\nNo result can be corrected now.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M1  (1) Ann v (16) Pip\n  M2  (8) Hal v (9) Ivy\n  M3  (4) Dot v (13) Max\n  M4  (5) Ed v (12) Lou\n  M5  (2) Ben v (15) Ola\n  M6  (7) Gil v (10) Jo\n  M7  (3) Cal v (14) Ned\n  M8  (6) Flo v (11) Kit\nmatch> M16\nType one of the matches listed.\nmatch> x\nType one of the matches listed.\nmatch> M\nType one of the matches listed.\nmatch> 9\nType one of the matches listed.\nmatch> \nCancelled.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M1  (1) Ann v (16) Pip\n  M2  (8) Hal v (9) Ivy\n  M3  (4) Dot v (13) Max\n  M4  (5) Ed v (12) Lou\n  M5  (2) Ben v (15) Ola\n  M6  (7) Gil v (10) Jo\n  M7  (3) Cal v (14) Ned\n  M8  (6) Flo v (11) Kit\nmatch> 1\nscore, Ann first (like 3-1)> 3\nType the score as two numbers from 0 to 99, like 3-1.\nscore, Ann first (like 3-1)> a-b\nType the score as two numbers from 0 to 99, like 3-1.\nscore, Ann first (like 3-1)> 100-1\nType the score as two numbers from 0 to 99, like 3-1.\nscore, Ann first (like 3-1)> 3-\nType the score as two numbers from 0 to 99, like 3-1.\nscore, Ann first (like 3-1)> 2-2\nA knockout match needs a winner: give the score after extra time or penalties.\nscore, Ann first (like 3-1)> \nCancelled.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M1  (1) Ann v (16) Pip\n  M2  (8) Hal v (9) Ivy\n  M3  (4) Dot v (13) Max\n  M4  (5) Ed v (12) Lou\n  M5  (2) Ben v (15) Ola\n  M6  (7) Gil v (10) Jo\n  M7  (3) Cal v (14) Ned\n  M8  (6) Flo v (11) Kit\nmatch> 1\nscore, Ann first (like 3-1)> 2-0\nM1: Ann beats Pip 2-0.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M2  (8) Hal v (9) Ivy\n  M3  (4) Dot v (13) Max\n  M4  (5) Ed v (12) Lou\n  M5  (2) Ben v (15) Ola\n  M6  (7) Gil v (10) Jo\n  M7  (3) Cal v (14) Ned\n  M8  (6) Flo v (11) Kit\nmatch> M2\nscore, Hal first (like 3-1)> 0-1\nM2: Ivy beats Hal 1-0.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M3  (4) Dot v (13) Max\n  M4  (5) Ed v (12) Lou\n  M5  (2) Ben v (15) Ola\n  M6  (7) Gil v (10) Jo\n  M7  (3) Cal v (14) Ned\n  M8  (6) Flo v (11) Kit\n  M9  (1) Ann v (9) Ivy\nmatch> M9\nscore, Ann first (like 3-1)> 1-1\nA knockout match needs a winner: give the score after extra time or penalties.\nscore, Ann first (like 3-1)> 1-2\nM9: Ivy beats Ann 2-1.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 3\nResults that can still be corrected:\n  M9  (1) Ann 1-2 (9) Ivy\nmatch> M1\nType one of the matches listed.\nmatch> M3\nType one of the matches listed.\nmatch> \nCancelled.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 3\nResults that can still be corrected:\n  M9  (1) Ann 1-2 (9) Ivy\nmatch> M9\nscore, Ann first (like 3-1)> 5-4\nM9 corrected: Ann beats Ivy 5-4.\nAnn takes Ivy's place in M13.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 1\nRound of 16\n  M1  (1) Ann           2-0    (16) Pip          -> Ann\n  M2  (8) Hal           0-1    (9) Ivy           -> Ivy (upset)\n  M3  (4) Dot           v      (13) Max\n  M4  (5) Ed            v      (12) Lou\n  M5  (2) Ben           v      (15) Ola\n  M6  (7) Gil           v      (10) Jo\n  M7  (3) Cal           v      (14) Ned\n  M8  (6) Flo           v      (11) Kit\nQuarter-finals\n  M9  (1) Ann           5-4    (9) Ivy           -> Ann\n  M10 winner of M3      v      winner of M4\n  M11 winner of M5      v      winner of M6\n  M12 winner of M7      v      winner of M8\nSemi-finals\n  M13 (1) Ann           v      winner of M10\n  M14 winner of M11     v      winner of M12\nFinal\n  M15 winner of M13     v      winner of M14\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "Ana\nBo\nCy\nDee\nEve\nFay\n\n2\nM2\n1-3\n2\nm4\n2-4\n1\n3\nM2\n3-1\n2\n5\n2-1\n2\n6\n0:1\n2\n7\n3 - 2\n1\n4\n",
      "screen": "== Tournament bracket ==\nType the players from the strongest down - seed 1 first - one name a line;\nan empty line ends the list (2 to 16 players).\nplayer 1> Ana\nplayer 2> Bo\nplayer 3> Cy\nplayer 4> Dee\nplayer 5> Eve\nplayer 6> Fay\nplayer 7> \n6 players: a bracket of 8, with byes for seeds 1 and 2.\nQuarter-finals\n  M1  (1) Ana                  bye               -> Ana\n  M2  (4) Dee           v      (5) Eve\n  M3  (2) Bo                   bye               -> Bo\n  M4  (3) Cy            v      (6) Fay\nSemi-finals\n  M5  (1) Ana           v      winner of M2\n  M6  (2) Bo            v      winner of M4\nFinal\n  M7  winner of M5      v      winner of M6\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M2  (4) Dee v (5) Eve\n  M4  (3) Cy v (6) Fay\nmatch> M2\nscore, Dee first (like 3-1)> 1-3\nM2: Eve beats Dee 3-1.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M4  (3) Cy v (6) Fay\n  M5  (1) Ana v (5) Eve\nmatch> m4\nscore, Cy first (like 3-1)> 2-4\nM4: Fay beats Cy 4-2.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 1\nQuarter-finals\n  M1  (1) Ana                  bye               -> Ana\n  M2  (4) Dee           1-3    (5) Eve           -> Eve (upset)\n  M3  (2) Bo                   bye               -> Bo\n  M4  (3) Cy            2-4    (6) Fay           -> Fay (upset)\nSemi-finals\n  M5  (1) Ana           v      (5) Eve\n  M6  (2) Bo            v      (6) Fay\nFinal\n  M7  winner of M5      v      winner of M6\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 3\nResults that can still be corrected:\n  M2  (4) Dee 1-3 (5) Eve\n  M4  (3) Cy 2-4 (6) Fay\nmatch> M2\nscore, Dee first (like 3-1)> 3-1\nM2 corrected: Dee beats Eve 3-1.\nDee takes Eve's place in M5.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M5  (1) Ana v (4) Dee\n  M6  (2) Bo v (6) Fay\nmatch> 5\nscore, Ana first (like 3-1)> 2-1\nM5: Ana beats Dee 2-1.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M6  (2) Bo v (6) Fay\nmatch> 6\nscore, Bo first (like 3-1)> 0:1\nM6: Fay beats Bo 1-0.\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 2\nOpen matches:\n  M7  (1) Ana v (6) Fay\nmatch> 7\nscore, Ana first (like 3-1)> 3 - 2\nM7: Ana beats Fay 3-2.\nAna is the champion!\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 1\nQuarter-finals\n  M1  (1) Ana                  bye               -> Ana\n  M2  (4) Dee           3-1    (5) Eve           -> Dee\n  M3  (2) Bo                   bye               -> Bo\n  M4  (3) Cy            2-4    (6) Fay           -> Fay (upset)\nSemi-finals\n  M5  (1) Ana           2-1    (4) Dee           -> Ana\n  M6  (2) Bo            0-1    (6) Fay           -> Fay (upset)\nFinal\n  M7  (1) Ana           3-2    (6) Fay           -> Ana\nChampion: (1) Ana\n\n1) show the bracket  2) enter a result  3) correct a result  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "tournament-bracket",
      "caseId": "108-tournament-bracket",
      "title": "Tournament bracket"
    }
  ],
  "updated": "2026-10-10"
}
