Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2024 - Day 14

2024/day-14.livemd

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 &amp;&amp; "*" || " "
    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(&amp;elem(&amp;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)