2023 Day 02
Mix.install([
{:req, "~> 0.4.5"},
{:vega_lite, "~> 0.1.8"},
{:kino_vega_lite, "~> 0.1.11"}
])
alias VegaLite, as: Vl
defmodule AOC do
@aoc_session System.fetch_env!("LB_AOC_SESSION")
def day(day, year) when day in 1..25 do
headers = [cookie: "session=#{@aoc_session}"]
req = Req.new(base_url: "https://adventofcode.com/#{year}/day/#{day}", headers: headers)
input = Req.get!(req, url: "/input").body
if input =~ "Please don't repeatedly request this endpoint before it unlocks",
do: {:error, "Day #{day} hasn't been unlocked yet"},
else: {:ok, %{req: req, input: input}}
end
def submit(answer, %{req: req}, part \\ :part_1) when part in [:part_1, :part_2] do
part = if part == :part_2, do: 2, else: 1
if !part_already_completed?(part, req) do
body = Req.post!(req, url: "/answer", form: [level: part, answer: answer]).body
cond do
body =~ "That's the right answer" -> {:correct, answer}
body =~ "your answer is too low" -> {:too_low, answer}
body =~ "your answer is too high" -> {:too_high, answer}
:else -> {:incorrect, answer}
end
else
{:already_completed_part, answer}
end
end
defp part_already_completed?(part, req) do
body = Req.get!(req).body
cond do
body =~ "Both parts of this puzzle are complete!" -> true
body =~ "The first half of this puzzle is complete!" -> part == 1
:else -> false
end
end
end
Day
{:ok, day} = AOC.day(2, 2023)
day.input
test_input = """
Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green
"""
Part 1
defmodule P1 do
def rgb(str) do
{color(str, :red), color(str, :green), color(str, :blue)}
end
def color(str, color) do
Regex.scan(~r/(\d+) #{color}/, str, capture: :all_but_first)
|> List.flatten()
|> Enum.map(&String.to_integer/1)
|> Enum.max()
end
def solve(input) do
input
|> String.split("\n", trim: true)
|> Enum.with_index(1)
|> Enum.reduce(0, fn {str, game_id}, acc ->
{r, g, b} = P1.rgb(str)
if r <= 12 && g <= 13 && b <= 14,
do: acc + game_id,
else: acc
end)
end
end
day.input |> P1.solve()
Part 2
defmodule P2 do
def solve(input) do
input
|> String.split("\n", trim: true)
|> Enum.reduce(0, fn str, acc ->
{r, g, b} = P1.rgb(str)
r * g * b + acc
end)
end
end
day.input |> P2.solve()
Visualization
data =
day.input
|> String.split("\n", trim: true)
|> Enum.with_index(1)
|> Enum.flat_map(fn {str, game_id} ->
{r, g, b} = P1.rgb(str)
[
%{"game_id" => game_id, "color" => "red", "cubes" => r},
%{"game_id" => game_id, "color" => "green", "cubes" => g},
%{"game_id" => game_id, "color" => "blue", "cubes" => b}
]
end)
Vl.new(width: 798, height: 300)
|> Vl.data_from_values(data)
|> Vl.mark(:bar, tooltip: true)
|> Vl.encode_field(:x, "game_id", title: "Game", axis: [labels: false, ticks: false])
|> Vl.encode(:y, aggregate: :sum, field: "cubes", type: :quantitative, title: "Cubes")
|> Vl.encode_field(:color, "color",
type: :nominal,
legend: false,
scale: [
domain: ["red", "green", "blue"],
range: ["#bc4749", "#6a994e", "#62b6cb"]
]
)