As of today, I’ve beaten my previous year’s record (by one ), but, oh, was
it a struggle. The task today is to boost a distress signal to discover what
trouble those elves are up to now. First step is to add another parameter mode
to the intcode computer! I need to add an offset
value to the computer to keep
track of the relative mode’s base offset, and add some ways to read the memory
utilizing this parameter mode.
defstruct program: [],
position: 0,
input: [],
output: [],
offset: 0
defp parameter(0, x, prog, _computer), do: Enum.at(prog, x, 0)
defp parameter(1, x, _prog, _computer), do: x
defp parameter(2, x, prog, %__MODULE__{offset: offset}), do: Enum.at(prog, x + offset, 0)
Notice that I have to pass the computer struct into all of these functions so
that I can access the offset in the new mode. I think I could alter this here to
drop the prog
parameter and pull it from the computer struct as well, but I
have some other plans to tune up this computer.
Next, I need to augment writing values to the memory that uses this new mode.
defp store(prog, pos, v, m, computer) do
address =
if m == 2 do
computer.offset + Enum.at(prog, pos, 0)
else
Enum.at(prog, pos, 0)
end
prog =
if length(prog) < address do
prog ++ List.duplicate(0, address - length(prog))
else
prog
end
List.update_at(prog, address, fn _ -> v end)
end
If the length of the program currently is shorter than the address I am trying to write to, I should extend the program’s memory to include that new address. Then, I update the program as normal.
I also need to add a new op-code to handle changing the base offset.
def op(computer, [_a, _b, c, 0, 9]), do: {:ok, shift_offset(computer, c)}
defp shift_offset(%__MODULE__{program: prog, position: pos, offset: offset} = computer, m) do
shift = parameter(m, Enum.at(prog, shift(pos, 1), 0), prog, computer)
%__MODULE__{computer | offset: shift(offset, shift), position: shift(pos, 2)}
end
And I should be good to go! Wait…
That’s not good, the opcode 209
is failing Maybe I am not
adding enough memory when I extend my list? I’ll just add one, off by one errors
are classic.
defp store(prog, pos, v, m, computer) do
address =
if m == 2 do
computer.offset + Enum.at(prog, pos, 0)
else
Enum.at(prog, pos, 0)
end
prog =
if length(prog) < address do
prog ++ List.duplicate(0, address - length(prog) + 1)
else
prog
end
List.update_at(prog, address, fn _ -> v end)
end
Run it again!
203
!? Well… At least the result is different. Maybe… more memory?
defp store(prog, pos, v, m, computer) do
address =
if m == 2 do
computer.offset + Enum.at(prog, pos, 0)
else
Enum.at(prog, pos, 0)
end
prog =
if length(prog) < address do
prog ++ List.duplicate(0, address)
else
prog
end
List.update_at(prog, address, fn _ -> v end)
end
No more trying to be safe, I’ll just throw as much memory as I can possibly need at this thing. This time I’m serious.
Thank goodness it worked!
There’s no puzzle 2 section today, because puzzle 2 was solved by using the
newly-constructed intcode computer and no other alterations. It’s not often that
the first puzzle is the challenging one. I think I’ll have to use this more
going forward, so I plan on cleaning it up. I saw someone suggest a Map
instead of a list, so I think I will try that out. This is likely to be a
“sparse list”, or a list where a majority of indices in the list are unused, and
I can get rid of my funky memory growth code.
Conclusion
Debugging this was quite hard, and I didn’t get a chance to really dive into debugging in Elixir. Fortunately, just throwing memory at the problem fixed it! My next step so to clean this code up, and add some debug level logs to help solve any issues that come up later. I also have a pretty good set of test cases to ensure my refactoring goes smoothly, but it didn’t help me figure out my memory growth issue. It may read short, but it took me pretty much all day to figure that one out