Advent of Code 2024 - Day 14
Mix.install([
{:kino_aoc, "~> 0.1.7"},
{:bandit, "~> 1.6"}
])
Input Parsing
{:ok, puzzle_input} =
KinoAOC.download_puzzle("2024", "14", System.fetch_env!("LB_AOC_SESSION"))
IO.puts(puzzle_input)
robots =
~r/-?\d+/
|> Regex.scan(puzzle_input)
|> Enum.map(&hd/1)
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(4)
|> Enum.map(fn [x, y, vx, vy] ->
{{x, y}, {vx, vy}}
end)
width = 101
height= 103
half_width = div(width, 2)
half_height = div(height, 2)
robots
|> Enum.map(fn {{x, y}, {vx, vy}} ->
{
(x + vx * 100) |> rem(width) |> Kernel.+(width) |> rem(width),
(y + vy * 100) |> rem(height) |> Kernel.+(height) |> rem(height)
}
end)
|> Enum.reject(fn {x, y} ->
x == half_width or y == half_height
end)
|> Enum.frequencies_by(fn {x, y} ->
{div(x, half_width + 1), div(y, half_height + 1)}
end)
|> Map.values()
|> Enum.product()
show = fn robots ->
positions =
robots
|> Enum.map(&elem(&1, 0))
|> MapSet.new()
for i <- 0..width - 1, into: "" do
for j <- 0..height - 1, into: "" do
{i, j} in positions && "*" || " "
end
|> Kernel.<>("\n")
end
end
defmodule BathroomServer do
use GenServer, restart: :temporary
@width 101
@height 103
def start_link(robots) do
GenServer.start_link(__MODULE__, robots, name: __MODULE__)
end
@impl true
def init(robots) do
{:ok, {robots, 0}}
end
def tick() do
GenServer.call(__MODULE__, :tick)
end
@impl true
def handle_call(:tick, _from, {robots, tick}) do
robots2 =
Enum.map(robots, fn {{x, y}, {vx, vy} = v} ->
x = rem(x + vx + @width, @width)
y = rem(y + vy + @height, @height)
{{x, y}, v}
end)
{:reply, {robots, tick}, {robots2, tick + 1}}
end
end
defmodule MyPlug do
@behaviour Plug
@impl true
def init(opts) do
opts
end
def call(%Plug.Conn{path_info: []} = conn, _opts) do
{robots, tick} = BathroomServer.tick()
conn
|> Plug.Conn.put_resp_content_type("text/html")
|> Plug.Conn.send_resp(200, """
Tick:
#{tick}
#{render(robots)}
"""
)
end
@impl true
def call(%Plug.Conn{} = conn, _opts) do
Plug.Conn.send_resp(conn, 404, "")
end
defp render(robots) do
positions =
robots
|> Enum.map(&elem(&1, 0))
|> MapSet.new()
for i <- 0..100, into: "" do
for j <- 0..102, into: "" do
if {i, j} in positions, do: "*", else: " "
end
|> Kernel.<>("\n")
end
end
end
Kino.start_child({BathroomServer, robots})
Kino.start_child({Bandit, plug: MyPlug, port: 9292, scheme: :http, ip: :loopback})
length(robots)
{{x, y}, {vx, vy}} = hd(robots)
File.open("/home/slothopher/tmp/aoc-2024-d14.txt", [:write], fn f ->
robots
|> Stream.iterate(fn robots ->
Enum.map(robots, fn {{x, y}, {vx, vy} = v} ->
{
{
x |> Kernel.+(vx) |> Kernel.+(width) |> rem(width),
y |> Kernel.+(vy) |> Kernel.+(height) |> rem(height)
},
v
}
end)
end)
|> Stream.map(show)
|> Stream.with_index()
|> Stream.take(10000)
|> Enum.each(fn {rendered, i} ->
IO.puts(f, "Tick #{i}\n")
IO.puts(f, rendered)
IO.puts(f, "\n")
end)
end)