defmodule Day10 do defmodule Instr do defstruct cmd: nil, cycles: 0, amt: nil end def part1(input) do process(input, _cycle = 1, _x = 1, _sample_at = 20, _sig = 0) end defp process([], _cycle, _x, _sample_at, sig), do: sig defp process(instrs, cycle, x, sample_at, sig) when cycle == sample_at do process(instrs, cycle, x, sample_at + 40, sig + cycle * x) end defp process([%Instr{cycles: 0} | rest], cycle, x, sample_at, sig) do process(rest, cycle + 1, x, sample_at, sig) end defp process([instr = %Instr{cycles: 1, cmd: :noop} | rest], cycle, x, sample_at, sig) do process([%Instr{instr | cycles: 0} | rest], cycle, x, sample_at, sig) end defp process([instr = %Instr{cycles: 1, cmd: :addx, amt: amt} | rest], cycle, x, sample_at, sig) do process([%Instr{instr | cycles: 0} | rest], cycle, x + amt, sample_at, sig) end defp process([instr = %Instr{cycles: cycles} | rest], cycle, x, sample_at, sig) do process([%Instr{instr | cycles: cycles - 1} | rest], cycle + 1, x, sample_at, sig) end def part2(input) do input |> scan(_x = 1, _pixel = 0, _pixels = [[]]) |> Enum.map(&Enum.reverse/1) |> Enum.reverse() |> Enum.join("\n") end defp scan([], _x, _pixel, pixels), do: pixels defp scan(instrs, x, _pixel = 40, pixels) do scan(instrs, x, _pixel = 0, [[] | pixels]) end defp scan([%Instr{cmd: :noop} | rest], x, pixel, [row | pixels]) do scan(rest, x, pixel + 1, [draw_pixel(pixel, x, row) | pixels]) end defp scan([%Instr{cycles: 1, cmd: :addx, amt: amt} | rest], x, pixel, [row | pixels]) do scan(rest, x + amt, pixel + 1, [draw_pixel(pixel, x, row) | pixels]) end defp scan([instr = %Instr{cycles: cycles} | rest], x, pixel, [row | pixels]) do scan([%Instr{instr | cycles: cycles - 1} | rest], x, pixel + 1, [ draw_pixel(pixel, x, row) | pixels ]) end defp draw_pixel(pixel, x, row) when pixel in (x - 1)..(x + 1), do: ["#" | row] defp draw_pixel(_pixel, _x, row), do: [" " | row] def input do with [input_filename] <- System.argv(), {:ok, input} <- File.read(input_filename) do input |> String.trim() |> String.split("\n") |> Enum.map(fn "noop" -> %Instr{cmd: :noop, cycles: 1} "addx " <> x -> %Instr{cmd: :addx, cycles: 2, amt: String.to_integer(x)} end) 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("#{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 day10.exs input_filename") end end Day10.run()