Building a March Madness Bracket in PHP

Jim Hall takes his March Madness script to the next level.

Every year in March, my office closely follows the NCAA college basketball tournament, also known as March Madness. You can print out a bracket and make your own predictions as to which team will win at each round. Several of my co-workers take this somewhat seriously, and they always print out their brackets and tack them to their work cubes for all to see. Generally, the winner buys a pizza lunch for the others who played.

I like to join in, but unfortunately, I lack one thing to participate effectively: I don't really follow basketball.

But, I don't like to miss out on March Madness, so a few years ago, I created a Bash script to help me fill out my March Madness bracket. This worked pretty well. I ran the script, and then I used the results to fill out my March Madness bracket.

See my previous two articles for more info on the script: Bash Shell Script: Building a Better March Madness Bracket (2017) and Bash Shell Script: Building Your March Madness Bracket (2016).

This year, I decided to take things to the next level. Rather than run a Bash script, why not create a web page that automatically fills in the winners for each round? That way I won't really even need to fill out a March Madness bracket; I can just print out the results from my web page.

Guessing the Winners of Each Game

In the NCAA college basketball tournament, 64 teams from four regions compete in a series of single-elimination games. "Single-elimination" means that after each round, the winning teams move on to the next round; the losing teams are out. The NCAA seeds the first round with the top teams from each region and assigns a ranking value to each team. Usually, the higher-rated teams (1–8) fare better than the lower-rated teams (9–16).

You can use this "rank" value to guess who might win or lose an individual contest. A rank 1 team should perform better than a rank 16 team, but a rank 8 team should perform about the same as a rank 9 team. I found that the best simulation of a game's outcome is to create a virtual "die" that I "roll" to determine the winner. The die has faces relative to the chance of each team to win, as represented in 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 (A) versus a rank 16 team (B) would generate a virtual 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.

Writing a PHP function to guess the winner of a single contest is pretty straightforward:


function guesswinner ($rankA, $rankB)
{
  $chanceA = 16 - $rankA + 1;
  $chanceB = 16 - $rankB + 1;

  $result = rand (1, $chanceA + $chanceB);

  if ($result <= $chanceA) {
    return $rankA;
  }
  else {
    return $rankB;
  }
}

The guesswinner() function takes two arguments, the rank values of teams A and B, and returns the winner. The function calculates the relative chances to win based on ranks of the two teams, then uses the rand() function to guess a random number between 1 and the sum of the chances to win. Note that you can use rand() in two ways: if you call rand() with no arguments, the PHP function returns a random value between 0 and some maximum value. If you provide two values as arguments, rand() gives a random number between those two values. For example, rand(1, n) returns a random number between 1 and n, inclusive.

Using this simple method, a rank 1 team will have a much greater chance at winning over a rank 16 team, but a rank 8 team will have about even odds with a rank 9 team. This is the same as building a virtual "die" and making a "roll" through random numbers.

Automating the Bracket

Now that I have a function to guess the outcome of an individual contest, I can create a function that "plays" the entire bracket. Because March Madness has only a few rounds, and each round has a predictable seed round, the function to play a bracket is a matter of tracking the winner of each contest and passing those winners into the next round:


function playbracket ($name)
{
  print <<<END
<div id="$name">
  <h2>$name</h2>
END;

  /* seed round */

  print <<<END
  <div class="round0">
    <p>1</p><p>16</p>
    <p>8</p><p>9</p>
    <p>5</p><p>12</p>
    <p>4</p><p>13</p>
    <p>6</p><p>11</p>
    <p>3</p><p>14</p>
    <p>7</p><p>10</p>
    <p>2</p><p>15</p>
  </div>
END;

  /* round 1 results */

  $round1A = guesswinner (1, 16);
  $round1B = guesswinner (8,  9);
  $round1C = guesswinner (5, 12);
  $round1D = guesswinner (4, 13);
  $round1E = guesswinner (6, 11);
  $round1F = guesswinner (3, 14);
  $round1G = guesswinner (7, 10);
  $round1H = guesswinner (2, 15);

  print <<<END
  <div class="round1">
    <p>$round1A</p>
    <p>$round1B</p>
    <p>$round1C</p>
    <p>$round1D</p>
    <p>$round1E</p>
    <p>$round1F</p>
    <p>$round1G</p>
    <p>$round1H</p>
  </div>
END;

  /* round 2 results */

  $round2A = guesswinner ($round1A, $round1B);
  $round2B = guesswinner ($round1C, $round1D);
  $round2C = guesswinner ($round1E, $round1F);
  $round2D = guesswinner ($round1G, $round1H);

  print <<<END
  <div class="round2">
    <p>$round2A</p>
    <p>$round2B</p>
    <p>$round2C</p>
    <p>$round2D</p>
  </div>
END;
  /* round 3 results */

  $round3A = guesswinner ($round2A, $round2B);
  $round3B = guesswinner ($round2C, $round2D);

  print <<<END
  <div class="round3">
    <p>$round3A</p>
    <p>$round3B</p>
  </div>
END;

  /* round 4 results */

  $round4A = guesswinner ($round3A, $round3B);

print <<<END
  <div class="round4">
    <p>$round4A</p>
  </div>
</div>
END;
}

Finally, if you're following along, you need only call the playbracket() function for each of the four regions. You are left with the "Final Four" with the winners of each bracket, but I'll leave the final determination of those contests for you to resolve on your own:


<html>
<head>
  <title>My Brackets</title>
  <style>
...
  </style>
</head>
<body>
<?php
function guesswinner ($rankA, $rankB)
{
...
}

function playbracket ($name)
{
...
}
?>

<h1>My Brackets</h1>

<div id="me">
<?php playbracket ("midwest"); ?>
<?php playbracket ("east"); ?>
</div>

<div id="ws">
<?php playbracket ("west"); ?>
<?php playbracket ("south"); ?>
</div>
</body>
</html>

Styling the Brackets

The above PHP code gives a series of results for a single-elimination bracket, but the resulting HTML code needs some styling for it to look like a bracket. For that, I've used a neat function in Cascading Style Sheets: the Flexbox.

To explain the Flexbox, first understand that the HTML is a set of nested elements, <div> and <p>. Let's walk through them. The outermost elements are two divs. The first div contains the results from the Midwest and East regions: div#me.

The second div contains the results from the West and South regions: div#ws.

The contents of these divs are basically identical: one region to display on the left, and another region to display on the right. For example, it looks something like this:

Each region div contains a header and a series of divs for each round in March Madness, which looks something like this:

The paragraphs for the results of each contest are not shown, or the example would be quite long!

Enter the powerful style layout methods of Flexbox. For each Flexbox parent, you can define various properties that define arrangement within the Flexbox, such as direction:

  • row: left to right
  • row-reverse: right to left
  • column: top to bottom
  • column-reverse: bottom to top

My CSS code sets the "left" column, "Midwest" and "West", to display contents left to right ("row") and the "right" column, "East" and "South", to display their contents right to left ("row-reverse"). That's the essence of my styling of the Flexbox. You can see more properties of the Flexbox at the popular CSS Tricks website. And now for the code:


body {
  background-color: #fff;
  color: #000;
  font-family: sans-serif;
  margin: 0;
}
h1 {
  background-color: #333;
  color: #fff;
  font-size: 1em;
  text-align: center;
}

div#me,
div#ws {
  display: flex;
  flex-direction: row;
}

div#midwest,
div#east,
div#west,
div#south {
  display: flex;
  width: 50%;
}

div#midwest,
div#west {
  flex-direction: row;
}
div#east,
div#south {
  flex-direction: row-reverse;
}

h2 {
  background-color: #369;
  color: #fff;
  font-size: 1em;
  text-align: center;
  text-transform: uppercase;
  width: 25%;
}
div.round0,
div.round1,
div.round2,
div.round3,
div.round4 {
  display: flex;
  flex-direction: column;
  width: 15%;
}
div.round0 p {
  background-color: rgba(0,0,255,.1);
}
div.round1 p {
  background-color: rgba(0,0,255,.2);
}
div.round2 p {
  background-color: rgba(0,0,255,.3);
}
div.round3 p {
  background-color: rgba(0,0,255,.4);
}
div.round4 p {
  background-color: rgba(0,0,255,.5);
}

div > p {
  border: 1px solid #333;
  flex-grow: 1;
}

My Brackets

Every time you run the PHP code, such as in a web page, you will generate a fresh NCAA March Madness basketball bracket. It's entirely random, so each iteration of the bracket will be different. Figures 1 and 2 show sample runs.

Figure 1. Sample Run: Midwest and East

Figure 2. Sample Run: West and South

In this sample run, my script selects team 5 in the Midwest region, team 1 in the East, team 5 in the West and team 2 in the South. The bracket is mostly predictable; higher-ranking teams tend to win over lower-ranking teams. But there are a few upsets, such as Midwest team 11 advancing through several rounds, despite going up against higher-ranked teams (6 vs. 11, and 11 vs. 3) before facing defeat to a higher-ranked team. This bracket is a fairly realistic simulation of an NCAA March Madness bracket, and that's enough to match my brackets against my friends who follow college basketball.

The point of using a script to build your NCAA March Madness basket bracket isn't to take away the fun of the game. On the contrary, since I don't have much familiarity with basketball, building my bracket programmatically allows me to participate in the office basketball pool. It's entertaining without requiring much familiarity with sports statistics. My script gives me a reason to follow the games, but without the emotional investment if my bracket doesn't perform well.

Jim Hall is an open source software advocate and developer, probably best known as the founder of FreeDOS. Jim is also very active in usability testing for open source software projects like GNOME. At work, Jim is CEO of Hallmentum, an IT executive consulting company that helps CIOs and IT Leaders with strategic planning and organizational development.

Load Disqus comments