This one was really fun contrary to other “implement something CPU like” puzzles in the past. I used a bunch of Stream API and a module for scoping the CRT logic. I really liked the visual component of part 2.
Solution
defmodule Day10 do
defmodule CRT do
defstruct pixels: [], index: 0
def render_pixel(state, x) do
pixel = if state.index in (x - 1)..(x + 1), do: "#", else: "."
%__MODULE__{
pixels: [pixel | state.pixels],
index: rem(state.index + 1, 40)
}
end
def render(state) do
state.pixels
|> Enum.reverse()
|> Enum.chunk_every(40)
|> Enum.join("\n")
end
end
def run(text) do
text
|> program()
|> Stream.filter(fn {_, cycle} -> cycle in [20, 60, 100, 140, 180, 220] end)
|> Stream.map(fn {x, cycle} -> x * cycle end)
|> Enum.take(6)
|> Enum.sum()
end
def render_to_crt(text) do
text
|> program()
|> Stream.take_while(fn {_, cycle} -> cycle <= 240 end)
|> Enum.reduce(%CRT{}, fn {x, _}, crt ->
CRT.render_pixel(crt, x)
end)
|> CRT.render()
end
defp program(text) do
text
|> String.split("\n", trim: true)
|> Stream.transform(1, fn
"noop", x -> {[x], x}
"addx " <> num, x -> {[x, x], x + String.to_integer(num)}
end)
|> Stream.with_index(1)
end
end
That will give you a stream of {cycle_no, x} tuples that you could use to find the solution to the other parts. input is parsed in to a list of tuples, the small example would look like the [:noop, {:addx, 3}, {:addx, -5}].
Just pipe that stream into this for the second part
cycle_stream # from above
|> Stream.map(fn
{pc, x} when rem(pc-1, 40) in (x-1)..(x+1) -> ?#
_ -> ?\s
end)
|> Stream.chunk_every(40)
|> Enum.intersperse(?\n)
|> IO.puts
In the end I’m reasonably pleased with my solution but I had a lot of frustration getting there. Had an off by one error in my initial attempt at part 1 that only showed up with the real input and not the sample data. Part 2 was actually much easier but when I pasted the sample data output into vim for testing one of my plugins appended a # to each line after the first thinking they were meant to be comments. I didn’t realize it and couldn’t understand why my solution was not passing the test. Classic bad input is going to give bad output.
def execute(cmds, cycles) do
cmds
|> Enum.reduce_while({%{0 => 1, 1 => 1}, 1}, fn cmd, {register, cycle} ->
if cycle > cycles do
{:halt, {register, cycle}}
else
{:cont, exec(cmd, register, cycle)}
end
end)
|> elem(0)
end
defp exec(cmd, register, cycle) do
case cmd do
"noop" ->
register = Map.put(register, cycle + 1, register[cycle])
{register, cycle + 1}
<<"addx ", val::binary>> ->
last_val = register[cycle]
register =
register
|> Map.put(cycle + 1, last_val)
|> Map.put(cycle + 2, last_val + String.to_integer(val))
{register, cycle + 2}
end
end
defp signal_strengths(signal_map, cycles) do
cycles |> Enum.map(&signal_strength(&1, signal_map))
end
defp signal_strength(cycle, signal_map) do
signal_map[cycle] * cycle
end
def part1(input) do
input
|> execute(220)
|> signal_strengths([20, 60, 100, 140, 180, 220])
|> Enum.sum()
end
def part2(input) do
input
|> execute(:to_completion)
|> render()
end
defp render(map) do
0..(map_size(map) - 2)
|> Enum.reduce("", fn cycle, render ->
sprite_pos = map[cycle + 1]
cycle = rem(cycle, 40)
case {cycle == 0, abs(cycle - sprite_pos) < 2} do
{true, true} -> render <> "\n" <> "#"
{true, false} -> render <> "\n" <> "."
{false, true} -> render <> "#"
{false, false} -> render <> "."
end
end)
|> String.split("\n", trim: true)
end
Just curious if this really works: if Enum.member?(i.x..i.x+2, rem(i.cycle, 40))
because I would have thought it needs to be (i.x - 1)..(i.x + 1) since the sprite position is given by it’s middle pixel position.
Nothing special/similar to others. Reminded me of 2021-13 which also had you print out letters with .'s and #'s. So had some fun and added some IO.ANSI to “pretty” print, e.g.
This one was definitely interesting but I thought the second part was very unclear. I though for sure that the sprite position would work the same as the pixel position where the second row was indicated by x values over 40 and I was stumped when it never went over 40.
The divmod function is plagiarized from stack overflow