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

Day 15

livebook/15.livemd

Day 15

Mix.install([
  {:kino, "~> 0.14.2"}
])

Part 1

input = File.read!("15/example.txt") |> String.split("\n")
# input = File.read!("15/input.txt") |> String.split("\n")
# input = "########
# #..O.O.#
# ##@.O..#
# #...O..#
# #.#.O..#
# #...O..#
# #......#
# ########

# <^^>>>vv>v<<" |> String.split("\n")

# input = "#######
# #...#.#
# #.....#
# #..OO@#
# #..O..#
# #.....#
# #######

#  String.split("\n")
defmodule U do
  def parse(input) do
    parse(input, :map, {{0, 0}, {0, 0}, %{}})
  end

  def parse(["" | tail], :map, {size, robot, m}) do
    parse(tail, :moves, {size, robot, m, []})
  end

  def parse([line | tail], :map, {{_, y}, robot, m}) do
    {w, robot, m} =
      Enum.reduce(String.codepoints(line), {0, robot, m}, fn char, {x, robot, m} ->
        {m, robot} =
          case char do
            "." -> {m, robot}
            "#" -> {Map.put(m, {x, y}, :wall), robot}
            "@" -> {Map.put(m, {x, y}, :robot), {x, y}}
            "O" -> {Map.put(m, {x, y}, :box), robot}
          end

        {x + 1, robot, m}
      end)

    parse(tail, :map, {{w, y + 1}, robot, m})
  end

  def parse([], :moves, res), do: res

  def parse([line | tail], :moves, {size, robot, m, moves}) do
    parse(
      tail,
      :moves,
      {size, robot, m,
       moves ++
         Enum.map(String.codepoints(line), fn
           "^" -> :u
           "v" -> :d
           ">" -> :r
           "<" -> :l
         end)}
    )
  end

  def render({w, h}, map) do
    Enum.map(0..(h - 1), fn y ->
      [
        Enum.map(0..(w - 1), fn x ->
          o = map[{x, y}]

          case o do
            :wall -> "#"
            :robot -> [:red, "@", :reset]
            :box -> [:green, "O", :reset]
            nil -> " "
          end
        end),
        "\n"
      ]
    end)
    |> IO.ANSI.format()
    |> IO.iodata_to_binary()
    |> Kino.Text.new(terminal: true)
  end
end
{size, robot, map, moves} = U.parse(input)
U.render(size, map)
defmodule P1 do
  def move(move, pos, map) do
    n_pos = next_pos(move, pos)
    o = map[n_pos]
    case o do
      nil -> {n_pos, map |> Map.delete(pos) |> Map.put(n_pos, :robot)}
      :wall -> {pos, map}
      :box -> case move_box(move, n_pos, map) do
        nil -> {pos, map}
        map -> {n_pos, map |> Map.delete(pos) |> Map.put(n_pos, :robot)}
      end
    end
  end

  def move_box(move, pos, map) do
    n_pos = next_pos(move, pos)
    o = map[n_pos]
    case o do
      nil -> map |> Map.delete(pos) |> Map.put(n_pos, :box)
      :wall -> nil
      :box -> 
        case move_box(move, n_pos, map) do
          nil -> nil
          map -> map |> Map.delete(pos) |> Map.put(n_pos, :box)
        end
    end
  end

  def next_pos(move, {x, y}) do
    case move do
      :u -> {x, y - 1}
      :d -> {x, y + 1}
      :l -> {x - 1, y}
      :r -> {x + 1, y}
    end
  end

  def gps(m) do
    Enum.reduce(m, 0, fn {{x, y}, o}, acc ->
      case o do
        :box -> acc + x + (y * 100)
        _ -> acc
      end
    end)
  end
end
frame = Kino.Frame.new() |> Kino.render()

{_, map} = Enum.reduce(moves, {robot, map}, fn move, {robot, map} ->
  # Kino.Frame.render(frame, U.render(size, map))
  # :timer.sleep(40)
  P1.move(move, robot, map)
end)

Kino.Frame.render(frame, U.render(size, map))

P1.gps(map)
defmodule U2 do
  def parse(input) do
    parse(input, :map, {{0, 0}, {0, 0}, %{}})
  end

  def parse(["" | tail], :map, {size, robot, m}) do
    U.parse(tail, :moves, {size, robot, m, []})
  end

  def parse([line | tail], :map, {{_, y}, robot, m}) do
    {w, robot, m} =
      Enum.reduce(String.codepoints(line), {0, robot, m}, fn char, {x, robot, m} ->
        {m, robot} =
          case char do
            "." -> {m, robot}
            "#" -> {m |> Map.put({x, y}, :wall) |> Map.put({x + 1, y}, :wall), robot}
            "@" -> {Map.put(m, {x, y}, :robot), {x, y}}
            "O" -> {m |> Map.put({x, y}, {:b, :l}) |> Map.put({x + 1, y}, {:b, :r}), robot}
          end

        {x + 2, robot, m}
      end)

    parse(tail, :map, {{w, y + 1}, robot, m})
  end

  def render({w, h}, map) do
    Enum.map(0..(h - 1), fn y ->
      [
        Enum.map(0..(w - 1), fn x ->
          o = map[{x, y}]

          case o do
            :wall -> "#"
            :robot -> [:red, "@", :reset]
            {:b, :l} -> [:green, "[", :reset]
            {:b, :r} -> [:green, "]", :reset]
            nil -> "."
          end
        end),
        "\n"
      ]
    end)
    |> IO.ANSI.format()
    |> IO.iodata_to_binary()
    |> Kino.Text.new(terminal: true)
  end
end
{size, robot, map, moves} = U2.parse(input)
U2.render(size, map)
defmodule P2 do
  def move(move, {x, y} = pos, m) do
    o = m[pos]

    case o do
      nil ->
        m

      :robot ->
        n_pos = P1.next_pos(move, pos)
        m2 = move(move, n_pos, m)

        if m2 do
          m2 = m2 |> Map.delete(pos) |> Map.put(n_pos, o)
          {n_pos, m2}
        else
          {pos, m}
        end

      :wall ->
        nil

      {:b, bpart} ->
        to_move =
          case {move, bpart} do
            {:l, :r} -> [{x - 2, y}]
            {:r, :l} -> [{x + 2, y}]
            {:u, :l} -> [{x, y - 1}, {x + 1, y - 1}]
            {:d, :l} -> [{x, y + 1}, {x + 1, y + 1}]
            {:u, :r} -> [{x - 1, y - 1}, {x, y - 1}]
            {:d, :r} -> [{x - 1, y + 1}, {x, y + 1}]
          end

        m2 =
          Enum.reduce_while(to_move, m, fn pos, m ->
            m2 = move(move, pos, m)

            if m2 do
              {:cont, m2}
            else
              {:halt, nil}
            end
          end)

        if m2 do
          me =
            case bpart do
              :l -> [{pos, {:b, :l}}, {{x + 1, y}, {:b, :r}}]
              :r -> [{{x - 1, y}, {:b, :l}}, {pos, {:b, :r}}]
            end
          new_me_update = case move do
            :r -> fn {x, y} -> {x + 1, y} end
            :d -> fn {x, y} -> {x, y + 1} end
            :l -> fn {x, y} -> {x - 1, y} end
            :u -> fn {x, y} -> {x, y - 1} end
          end
          m2 = Enum.reduce(me, m2, fn {pos, _}, m -> Map.delete(m, pos) end)
          Enum.reduce(me, m2, fn {pos, o}, m -> Map.put(m, new_me_update.(pos), o) end)
        end
    end
  end

  def gps(m) do
    Enum.reduce(m, 0, fn {{x, y}, o}, acc ->
      case o do
        {:b, :l} -> acc + x + y * 100
        _ -> acc
      end
    end)
  end
end
frame = Kino.Frame.new() |> Kino.render()

{_, map} = Enum.reduce(moves, {robot, map}, fn move, {robot, map} ->
  :timer.sleep(40)
  {_, map} = res = P2.move(move, robot, map)
  Kino.Frame.render(frame, U2.render(size, map))
  res
end)

P2.gps(map)