BattleBots WC7 ELO
Mix.install([
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.7"},
{:floki, "~> 0.26.0"},
{:httpoison, "~> 2.0"}
])
alias VegaLite, as: Vl
Setup
resultsTable = Kino.Input.text("Google Sheets Results URL") |> Kino.render()
botsTable = Kino.Input.text("Google Sheets Bots URL") |> Kino.render()
Kino.nothing()
startingElo = 1500
winString = "WIN"
kFactor = 512
{_botList, botratings} =
Kino.Input.read(botsTable)
|> HTTPoison.get!()
|> Map.get(:body)
|> Floki.parse_document!()
|> Floki.find("table tr")
|> Enum.drop(2)
|> Enum.map(fn row ->
row |> Floki.find("td") |> Enum.drop(2) |> Enum.drop(-1) |> Floki.text()
end)
|> Enum.map_reduce(%{}, fn bot, botMap -> {bot, Map.put(botMap, bot, startingElo)} end)
{_, matches} =
Kino.Input.read(resultsTable)
|> HTTPoison.get!()
|> Map.get(:body)
|> Floki.parse_document!()
|> Floki.find("table tr")
|> Enum.drop(3)
|> Enum.chunk_every(2)
|> Enum.map_reduce(%{1 => [], 2 => [], 3 => [], 4 => []}, fn tallRow, acc ->
[matches | botAndResults] = tallRow
[botElement | resultElements] = botAndResults |> Floki.find("td") |> Enum.drop(1)
botName = Floki.text(botElement)
results =
resultElements
|> Enum.map(fn result -> String.contains?(result |> Floki.text(), winString) end)
{_, {_, resultsParsed}} =
matches
|> Floki.find("td")
|> Enum.drop(4)
|> Floki.find("a")
|> Enum.map_reduce({1, acc}, fn opponent, {i, wonMatches} ->
return =
if Enum.at(results, i - 1, false) do
Map.put(wonMatches, i, [
%{botName => opponent |> Floki.text()} | Map.get(wonMatches, i)
])
else
wonMatches
end
{opponent, {i + 1, return}}
end)
{tallRow, resultsParsed}
end)
{_, flatMatches} =
matches
|> Enum.map_reduce([], fn {_, match}, matches ->
{match, matches ++ match}
end)
{_, elo} =
Enum.map_reduce(flatMatches, botratings, fn match, botratings ->
[winner | _] = Map.keys(match)
loser = Map.fetch!(match, winner)
winnerRating = Map.fetch!(botratings, winner)
loserRating = Map.fetch!(botratings, loser)
expectedWinnerScore = 1 / (1 + :math.pow((loserRating - winnerRating) / 400, 10))
expectedLoserScore = 1 / (1 + :math.pow((winnerRating - loserRating) / 400, 10))
{match,
%{
botratings
| winner => winnerRating + kFactor * (1 - expectedWinnerScore),
loser => loserRating + kFactor * (0 - expectedLoserScore)
}}
end)
chartableELO =
Enum.map(elo, fn bot ->
botName = elem(bot, 0)
%{"Bot Name" => botName, "ELO" => round(elem(bot, 1))}
end)
{:ok}
Vl.new()
|> Vl.data_from_values(chartableELO, only: ["Bot Name", "ELO"])
|> Vl.mark(:bar)
|> Vl.encode_field(:x, "Bot Name", type: :nominal)
|> Vl.encode_field(:y, "ELO", type: :quantitative)
|> Vl.encode_field(:color, "ELO", type: :quantitative)
Kino.DataTable.new(chartableELO)