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

Day 15

day15.livemd

Day 15

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

Part 1

example = """
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########

^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv<
<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^><
^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><
v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^
"""

{:ok, input} = if true do
  KinoAOC.download_puzzle("2024", "15", System.fetch_env!("LB_AOC_SESSION"))
else
  {:ok, example}
end
  
[grid, moves] = input
  |> String.trim()
  |> String.split("\n\n")
  |> Enum.map(&amp;String.trim/1)

moves = String.graphemes(moves) |> Enum.filter(&amp; &amp;1 != "\n")

grid = grid |> String.split("\n") |> Enum.map(&amp;String.graphemes/1)

{size_r, size_c} = {  # Used for printing
  length(grid) - 1,
  length(Enum.at(grid, 0)) - 1
}

grid = for {row, i} <- Enum.with_index(grid) do
  for {col, j} <- Enum.with_index(row) do
    {{i, j}, col}
  end
end |> List.flatten()

bot_start = List.keyfind(grid, "@", 1)
grid = Enum.into(grid, %{})
# Make a pretty picture
print_grid = fn grid ->
  for r <- 0..size_r do
    row = for c <- 0..size_c do
      Map.get(grid, {r, c})
    end
    IO.puts(List.to_string(row))
  end
end

defmodule Part1 do
  # Get coordinate and value of cell in given direction
  def get_next(dir, {ri, ci}, grid) do
    pos = case dir do
      ">" -> {ri, ci + 1}
      "<" -> {ri, ci - 1}
      "v" -> {ri + 1, ci}
      "^" -> {ri - 1, ci}
    end
    val = Map.get(grid, pos)
    {pos, val}
  end

  # Handles lists of directions
  def move(grid, _, []), do: grid
  def move(grid, pos_val, [dir | rest]) do
    {pv1, grid} = move(grid, pos_val, dir)
    move(grid, pv1, rest)
  end

  # Returns position, value, and grid (with no change for invalid moves)
  def move(grid, this = {pos, val}, dir) do
    {pos1, val1} = get_next(dir, pos, grid)
    
    case val1 do
      "#" -> {this, grid}
      "." -> grid = Map.replace(grid, pos1, val) |> Map.replace(pos, ".")
        {{pos1, val}, grid}
      "O" -> {pv2, grid} = move(grid, {pos1, val1}, dir) 
        if pv2 != {pos1, val1}, do: grid |> move(this, dir), else: {this, grid}
    end
  end
end

grid = Part1.move(grid, bot_start, moves)
print_grid.(grid)
result = Map.to_list(grid)
  |> Enum.filter(fn {_, v} -> v == "O" end)  # Filter to boxes
  |> Enum.map(fn {{r, c}, _} -> 100 * r + c end)  # Calculate coordinates
  |> Enum.sum()