## DEV Community is a community of 638,993 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

# I created a March Madness predictor CLI in Elixir, then turned it into a web app.

Joseph Lozano Originally published at joseph.engineering ・3 min read

tl;dr It's deployed at https://ncaa-predictor.onrender.com

First step was getting the bracket done. The NCAA Tournament is broken into 4 regions, each containing 16 teams seeded 1 through 16.

The 1 seeded team, plays the 16 seeded team, the 2 plays 15, etc. The tournament is organized to favor the better seeded team, so 1 and 2 are on opposite sides of the bracket. In the end, the first round looks something like this

``````  @matchups [
[1, 16],
[8, 9],
[5, 12],
[4, 13],
[6, 11],
[3, 14],
[7, 10],
[2, 15]
]
``````

Next, we recursivlely resolve the games, until there is only 1 left.

``````def resolve([ _ | _ ] = matchups) do
matchups
|> Enum.map(fn [team_a, team_b] -> resolve(team_a, team_b) end)
|> Enum.chunk_every(2)
|> resolve()
end
``````

`Enum.chunk_every/2` is especially helpful here.

Next, we implement a naive approach, assuming the better seeded team wins

``````def resolve({name_a, seed_a} = team_a, {name_b, seed_b} = team_b) do
if seed_a < seed_b do
IO.puts "#{name_a} beats #{name_b}"
team_a
else
IO.puts "#{name_b} beats #{name_a}"
team_b
end
end
``````

And our base case

``````def resolve([{name, _seed} = team]) do
IO.puts "#{name} wins!"
team
end
``````

Next, we just do this for each of the 4 regions, and put them in a tournament with eachother

``````def play() do
final_four = for region <- ["WEST", "EAST", "SOUTH", "MIDWEST"] do
winner = resolve(@matchups)
{region, winner}
end

resolve(final_four)
end
``````

And that's basically it!

But, better seeds don't always win. So I found some data on http://mcubed.net/ncaab/seeds.shtml, parsed it and came up with a map that shows the percentage of times a seed beats a given seed.

For the first seed, it looks like this

``````@data %{
1 => %{
1 => 50.0,
2 => 53.3,
3 => 62.5,
4 => 70.7,
5 => 83.3,
6 => 68.8,
7 => 85.7,
8 => 80.2,
9 => 90.0,
10 => 85.7,
11 => 57.1,
12 => 100.0,
13 => 100.0,
14 => 0.0,
15 => 0.0,
16 => 99.3
},
2 => %{ ... }
}
``````

For some cases, like against 14 and 15 seeds, there is no data, so in that case we assume the better seed wins.

Our `resolve/2` function, now looks like

``````def resolve({_, team_a} = a, {_, team_b} = b) do
team_a_win_pct = @data[team_a][team_b]

{winner, loser} =
if :rand.uniform() * 100 < team_a_win_pct or (team_a_win_pct == 0.0 and team_a < team_b) do
{a, b}
else
{b, a}
end

seed_text =
if team_a_win_pct != 0.0 do
winner_pct = @data[elem(winner, 1)][elem(loser, 1)]
"#{elem(winner, 0)} beats #{elem(loser, 0)} seeds #{winner_pct}% of the time"
else
"No data for #{elem(winner, 0)} vs #{elem(loser, 0)}. Assuming #{elem(winner, 0)} wins"
end

IO.puts(
String.pad_trailing("#{elem(winner, 0)} beats #{elem(loser, 0)}", 21) <> "\t" <> seed_text
)

winner
end
``````

And that's basically it for the CLI! You can check it out on Github if you'd like to check out the final version.

Now, for the deploy. I wrote a basic Plug router (no Phoenix for a project this small)

``````defmodule NCAA.Server do
use Plug.Router

plug(:match)
plug(:dispatch)

get "/" do
{:ok, pid} = StringIO.open("")
NCAA.play(pid)

resp_text = StringIO.flush(pid)
StringIO.close(pid)
resp_text

send_resp(conn, 200, resp_text)
end

match _ do
end
end
``````

You can see we are passing in a `pid` to our `NCAA.play` function. This is because want to capture everything written, so we can send it back to the client (instead of to STDOUT).
That means all of our `IO.puts(string)` functions change to `IO.puts(pid, string)`. Very straight-forward.

After the winner is calculated, we capture the string with `StringIO.flush`, and close the process. Then we just send it to the client with `send_resp`.

Next we need to make sure our server starts up. First in our `mix.exs` we change `def application` to

``````def application do
[
mod: {NCAA.Application, []},
extra_applications: [:logger]
]
end
``````

Then create `NCAA.Application`

``````defmodule NCAA.Application do
@moduledoc false
use Application
require Logger

def start(_type, _args) do
port =
case Mix.env() do
:prod ->
80

_ ->
Logger.info("Starting application at http://localhost:4000")
4000
end

children = [
{Plug.Cowboy, scheme: :http, plug: NCAA.Server, options: [port: port]}
]

opts = [strategy: :one_for_one, name: NCAA.Supervisor]

I deployed this to render. The build script is simply `mix deps.get && mix compile` and the run script is just `mix run --no-halt`.
Easy peasy. You can check out at at https://ncaa-predictor.onrender.com for the duration of the tournament, for just clone it yourself from Github and run it with `mix run --no-halt`.