Bash Shell Script: Building a Better March Madness Bracket

There's no scenario in which a rank #16 team B can win over a rank #1 team A. It's a forgone conclusion that in any match of a rank 1 team versus a rank 16 team, the rank 1 team will always win. That's not right. There should be a slim chance for the rank 16 team to win over the rank 1 team.

A Better Algorithm

Instead of a "static" D16 die, we need a custom "die" that has faces relative to the chance of each team to win. Let's consider this simple algorithm to generate a custom die:

  • Team A gets a=16-rankA+1 sides.

  • Team B gets b=16-rankB+1 sides.

Under this assumption, a rank 1 team versus a rank 16 team would generate a die with a=16-1+1=16 "team A" sides and b=16-16+1=1 "team B" sides, resulting in a 17-sided die. Similarly, a more even match, such as a rank 8 team versus a rank 9 team, would create a die with a=16-8+1=9 "team A" sides and b=16-9+1=8 "team B" sides, resulting in another 17-sided die.

It's not always a 17-sided die, however. A rank 1 team against a rank 9 team would generate a die with a=16-1+1=16 "team A" sides and b=16-9+1=8 "team B" sides, or a 24-sided die.

In Bash, you can simulate a virtual custom "die" through a file. It's simple enough to generate a file with the correct number of "team A" sides and "team B" sides. If you already have calculated a and b as above, you can write a file like this:

( for teamA in $(seq 0 $a) ; do echo $1 ; done
for teamB in $(seq 0 $b) ; do echo $2 ; done ) > die.file

Picking a random value from this file is as easy as randomizing or "shuffling" the file, then selecting the first line. On Linux systems, you can use the shuf(1) program from GNU coreutils to generate a random permutation of lines from a file. This randomizes whatever data you feed into shuf. Once shuffled, you easily can select the first line of the randomized output using head:

( for teamA in $(seq 0 $a) ; do echo $1 ; done
for teamB in $(seq 0 $b) ; do echo $2 ; done ) | shuf | head -1

That simple expression becomes the heart of the improved March Madness script. It operates the way I want it to: a rank 1 team almost always (but not always) will win over a team 16 team, yet more closely matched games, such as a rank 8 team versus a rank 9 team or a rank 2 team against a rank 3 team, will present more even odds.

Building a Better March Madness Script

The above can be wrapped into a new guesswinner function to predict a contest between two teams, whose ranks are passed as arguments. The function generates the virtual "die" and uses that to guess a winner:

function guesswinner {
  # $1 = team A rank
  # $2 = team B rank

  a=$(( 16 - $1 + 1 ))
  b=$(( 16 - $2 + 1 ))

  win=$( ( for teamA in $(seq 1 $a) ; do echo $1 ; done
  for teamB in $(seq 1 $b) ; do echo $2 ; done ) | shuf | head -1 )

  echo "$1 vs $2 : $win"
  return $win

Since the March Madness brackets are always played in order, you can write a playbracket function to run through the different iterations of the bracket. Winners from round one are carried into rounds two and three to select an ultimate winner for the bracket in round four:

function playbracket {
  # $1 = name of bracket

  echo -e "\n___ $1 ___"
  echo -e '\nround 1\n'

  guesswinner 1 16

  guesswinner 8 9

  guesswinner 5 12

  guesswinner 4 13

  guesswinner 6 11

  guesswinner 3 14

  guesswinner 7 10

  guesswinner 2 15

  echo -e '\nround 2\n'

  guesswinner $round1A $round1B

  guesswinner $round1C $round1D

  guesswinner $round1E $round1F

  guesswinner $round1G $round1H

  echo -e '\nround 3\n'

  guesswinner $round2A $round2B

  guesswinner $round2C $round2D

  echo -e '\nround 4\n'

  guesswinner $round3A $round3B

  return $?


Jim Hall is an advocate for free and open-source software, best known for his work on the FreeDOS Project, and he also focuses on the usability of open-source software. Jim is the Chief Information Officer at Ramsey County, Minn.