defmodule Day15 do defmodule Game do @derive Inspect defstruct [:grid, :robot, :moves] def from_data(data) do [grid, moves] = data |> String.split("\n\n", trim: true) grid = grid |> String.split("\n", trim: true) |> Enum.with_index() |> Enum.flat_map(fn {s, i} -> String.codepoints(s) |> Enum.with_index() |> Enum.map(fn {c, j} -> {{i, j}, c} end) end) |> Map.new() robot = grid |> Enum.find_value(fn {pos, c} -> c == "@" and pos end) moves = moves |> String.split("\n", trim: true) |> Enum.flat_map(&String.codepoints/1) %Game{grid: grid, robot: robot, moves: moves} end def expand(game) do grid = game.grid |> Enum.flat_map(fn {{i, j}, v} -> case v do "." -> [{{i, j*2}, "."}, {{i, j*2+1}, "."}] "@" -> [{{i, j*2}, "@"}, {{i, j*2+1}, "."}] "O" -> [{{i, j*2}, "["}, {{i, j*2+1}, "]"}] "#" -> [{{i, j*2}, "#"}, {{i, j*2+1}, "#"}] end end) |> Map.new() robot = {elem(game.robot, 0), elem(game.robot, 1)*2} %Game{grid: grid, robot: robot, moves: game.moves} end def do_moves(game) do if game.moves == [] do game else move(game) |> do_moves() end end defp move(game) do [move | moves] = game.moves moved? = check_moves(game.grid, move, game.robot) |> elem(0) grid = if moved? do apply_move(game.grid, move, queue_new(game.robot)) else game.grid end robot = if moved? do next_pos(game.robot, move) else game.robot end %Game{grid: grid, robot: robot, moves: moves} end defp check_moves(grid, move, k, vis \\ MapSet.new()) do if MapSet.member?(vis, k) do {true, vis} else vis = MapSet.put(vis, k) v = Map.fetch!(grid, k) case v do "." -> {true, vis} "#" -> {false, vis} "@" -> check_moves(grid, move, next_pos(k, move), vis) "O" -> check_moves(grid, move, next_pos(k, move), vis) "[" -> nk = next_pos(k, move) if move == "^" or move == "v" do {oi, _} = k {_, nj} = nk {a1, vis} = check_moves(grid, move, nk, vis) {a2, vis} = check_moves(grid, move, {oi, nj+1}, vis) {a1 and a2, vis} else check_moves(grid, move, nk, vis) end "]" -> nk = next_pos(k, move) if move == "^" or move == "v" do {oi, _} = k {_, nj} = nk {a1, vis} = check_moves(grid, move, nk, vis) {a2, vis} = check_moves(grid, move, {oi, nj-1}, vis) {a1 and a2, vis} else check_moves(grid, move, nk, vis) end end end end defp apply_move(grid, move, queue) do if queue_empty(queue) do grid else {pos, nv, queue} = queue_pop(queue, move) v = Map.fetch!(grid, pos) case v do "#" -> raise "Unexpected" "." -> apply_move(Map.put(grid, pos, nv), move, queue) v -> grid = Map.put(grid, pos, nv) npos = next_pos(pos, move) nv = Map.fetch!(grid, npos) queue = if (move == "^" or move == "v") and (nv == "[" or nv == "]") do {i, j} = npos j = if nv == "[" do j+1 else j-1 end queue = queue_add(queue, npos, v) queue_add(queue, {i, j}, ".") else queue_add(queue, npos, v) end apply_move(grid, move, queue) end end end defp queue_add(queue, pos, v) do case :gb_trees.lookup(pos, queue) do :none -> :gb_trees.insert(pos, v, queue) {_, "."} -> :gb_trees.update(pos, v, queue) _ -> queue end end defp queue_pop(queue, move) do if move == "^" do :gb_trees.take_largest(queue) else :gb_trees.take_smallest(queue) end end defp queue_new(robot) do :gb_trees.insert(robot, ".", :gb_trees.empty()) end defp queue_empty(queue) do :gb_trees.is_empty(queue) end defp next_pos({i, j}, move) do case move do ">" -> {i, j+1} "^" -> {i-1, j} "<" -> {i, j-1} "v" -> {i+1, j} end end def print(game) do w = game.grid |> Enum.map(fn {{_, j}, _} -> j end) |> Enum.max() h = game.grid |> Enum.map(fn {{i, _}, _} -> i end) |> Enum.max() for i <- 0..h do for j <- 0..w, reduce: [] do acc -> [Map.fetch!(game.grid, {i, j}) | acc] end |> Enum.reverse() |> Enum.join("") |> IO.puts() end game end end def part1(data) do data |> Game.from_data() |> Game.do_moves() |> then(& &1.grid) |> Enum.filter(fn {_, v} -> v == "O" end) |> Enum.map(fn {{i, j}, _} -> i*100 + j end) |> Enum.sum() end def part2(data) do data |> Game.from_data() |> Game.expand() |> Game.do_moves() |> then(& &1.grid) |> Enum.filter(fn {_, v} -> v == "[" end) |> Enum.map(fn {{i, j}, _} -> i*100 + j end) |> Enum.sum() end end data = IO.read(:stdio, :eof) {time1, ans1} = :timer.tc(fn -> Day15.part1(data) end) IO.puts("Time : #{time1 / 1000000}") IO.puts("Answer: #{ans1}") {time2, ans2} = :timer.tc(fn -> Day15.part2(data) end) IO.puts("Time : #{time2 / 1000000}") IO.puts("Answer: #{ans2}")