#!/usr/bin/env elixir defmodule Day15 do def part1({warehouse, robot, moves}) do attempt_moves(moves, robot, warehouse) |> Enum.map(fn {{x, y}, :box} -> x + y * 100 _ -> 0 end) |> Enum.sum() end def attempt_moves([], _robot, warehouse), do: warehouse def attempt_moves([move | next_moves], robot, warehouse) do next = move.(robot) case Map.fetch(warehouse, next) do :error -> attempt_moves(next_moves, next, warehouse) {:ok, :wall} -> attempt_moves(next_moves, robot, warehouse) {:ok, :box} -> case get_pushable_boxes(next, move, warehouse, [next]) do :wall -> attempt_moves(next_moves, robot, warehouse) boxes -> warehouse = Enum.reduce(boxes, warehouse, fn box, warehouse -> warehouse |> Map.delete(box) |> Map.put(move.(box), :box) end) attempt_moves(next_moves, next, warehouse) end end end def get_pushable_boxes(box, move, warehouse, pushable_boxes) do next = move.(box) case Map.fetch(warehouse, next) do :error -> pushable_boxes {:ok, :wall} -> :wall {:ok, :box} -> get_pushable_boxes(next, move, warehouse, [next | pushable_boxes]) end end def part2(_input) do :boring_skipped end def input do with [input_filename] <- System.argv(), {:ok, input} <- File.read(input_filename) do [raw_map, raw_moves] = String.split(input, "\n\n") {_, _y, robot, map} = for < "\n">>, reduce: {0, 0, nil, %{}} do {x, y, robot, map} -> case char do "." -> {x + 1, y, robot, map} "#" -> {x + 1, y, robot, Map.put(map, {x, y}, :wall)} "O" -> {x + 1, y, robot, Map.put(map, {x, y}, :box)} "@" -> {x + 1, y, {x, y}, map} "\n" -> {0, y + 1, robot, map} end end moves = for <> do case char do "^" -> fn {x, y} -> {x, y - 1} end "v" -> fn {x, y} -> {x, y + 1} end "<" -> fn {x, y} -> {x - 1, y} end ">" -> fn {x, y} -> {x + 1, y} end end end {map, robot, moves} else _ -> :error end end ####################### # HERE BE BOILERPLATE # ####################### def run do case input() do :error -> print_usage() input -> run_parts_with_timer(input) end end defp run_parts_with_timer(input) do run_with_timer(1, fn -> part1(input) end) run_with_timer(2, fn -> part2(input) end) end defp run_with_timer(part, fun) do {time, result} = :timer.tc(fun) IO.puts("Part #{part} (completed in #{format_time(time)}):\n") IO.puts("#{inspect(result)}\n") end defp format_time(μsec) when μsec < 1_000, do: "#{μsec}μs" defp format_time(μsec) when μsec < 1_000_000, do: "#{μsec / 1000}ms" defp format_time(μsec), do: "#{μsec / 1_000_000}s" defp print_usage do IO.puts("Usage: elixir day15.exs input_filename") end end Day15.run()