Mostly code

# Advent of Code 2019: Day 5

06 Dec 2019 In which previous bad code shoots me in the foot.

This was a continuation of the puzzle from day 2. If you don’t recall, I made a small `intcode` computer that was able to add, multiply, and stop. I need to augment this computer by adding the ability to take input and produce output. I also need to handle “parameter modes” and expanded `intcode`s

First, I want to be able to handle expanded `intcode`s. I also wanted to ensure it is backwards compatible with day 2. `intcode`s are now 5 digit numbers, where the first three digits indicate the parameter mode for a given parameter and the final 2 digits are the operational code.

``````  def run(%{program: prog, position: pos} = computer) do
code =
prog
|> Enum.at(pos)
|> Integer.digits()
|> pad_code()

op(computer, code)
end

defp pad_code(code) do
List.duplicate(0, 5 - length(code)) ++ code
end
``````

As leading zeros are not included when calling `Integer.digits/2`, I just pad them at the front once I have a list so `[1, 0, 2]` becomes `[0, 0, 1, 0, 2]`. This also makes the codes from day 2 into “expanded” codes, and things will continue to work. To handle parameter modes, I will create a helper function that checks the mode and returns the correct value for the parameter.

``````  defp parameter(0, x, prog), do: Enum.at(prog, x)

defp parameter(1, x, _prog), do: x

defp parameter(_, x, prog), do: parameter(0, x, prog)
``````

A mode of `0` indicates the parameter is a pointer to somewhere else in the program, which is exactly how day 2 operated. A mode of `1` indicates that the value is the parameter.

• Mode `0`: A parameter of `3` means the value is found at index `3` of the program list, so I must find and return it.
• Mode `1`: A parameter of `3` means the value is `3`, so I can return it immediately.

I added a default case that ensures the mode is `0` in case something goes wrong, but crashing here would probably be better. As these parameter codes also affected the output of an opcode, I created a helper to handling storing values (it would come in handy when handling `input` as well).

``````  defp store(prog, pos, v, m) do
List.update_at(prog, parameter(m, pos, prog), fn _ -> v end)
end
``````

While I’m sure output would always be mode `0`, it doesn’t hurt to handle it just in case there’s a throwaway value. The problem also doesn’t explicitly state one way or the other, so I’d rather be safe than sorry.

I’ll also make a small helper for shifting the position of the program, to cover all my bases.

``````  defp shift(pos, x), do: pos + x
``````

Again, probably unnecessary but it felt right.

Now that I have a bunch of helpers, I’ll amplify my existing `op/2` functions to handle the new modes with a new `compute/3` method.

``````  def op(computer, [a, b, c, 0, 1]),
do: {:ok, compute(computer, &Kernel.+/2, [a, c, b])}

def op(computer, [a, b, c, 0, 2]),
do: {:ok, compute(computer, &Kernel.*/2, [a, c, b])}

defp compute(%__MODULE__{program: prog, position: pos} = computer, f, [m | modes] = args) do
vals =
modes
|> Enum.with_index(1)
|> Enum.map(fn {m, i} -> parameter(m, Enum.at(prog, pos + i), prog) end)

prog = store(prog, pos + length(args), run(f, vals), m)

pos = shift(pos, length(args) + 1)

%__MODULE__{computer | program: prog, position: pos}
end

defp run(f, args) do
apply(f, args)
end
``````

OK, so `compute/3` is the big many-step process that should run the things:

1. Fetch the values for the parameters for the op-code
2. Run the provided elixir function for the op-code with the given parameters
3. Store the result in the appropriate location
4. Shift the program pointer the right number of steps
5. Return an updated `%Intcode{}` struct.

This is still compatible with the problem from day 2! Time for the new instructions.

### Input and Output

I don’t like the idea of actually prompting for input and printing output, so I added some lists to keep track of it.

``````  defstruct program: [],
position: 0,
input: [],
output: []
``````

and alter `new` to allow the passing in of input

``````  def new(program, input \\ []), do: %__MODULE__{program: program, input: input}
``````

At this point, I realized I’d have to pass `input` and `output` all around, and decided (after an hour of mulling it over), to just make some new methods for them specifically

``````  def op(computer, [_a, _b, c, 0, 3]),
do: {:ok, input(computer, c)}

def op(computer, [_a, _b, c, 0, 4]),
do: {:ok, output(computer, c)}

defp output(%__MODULE__{program: prog, position: pos, output: out} = computer, m) do
output = out ++ [parameter(m, Enum.at(prog, pos + 1), prog)]

pos = shift(pos, 2)

%__MODULE__{computer | program: prog, position: pos, output: output}
end

defp input(%__MODULE__{program: prog, position: pos, input: [i | t]}, m) do
prog = store(prog, pos + 1, i, m)
%__MODULE__{program: prog, position: pos + 2, input: t}
end
``````

All that’s left is to update `run_all` to ensure I can access the output and do the puzzle!

``````  def run_all({:halt, computer}), do: computer
``````
``````import AdventOfCode
alias AdventOfCode.Y2019.Utils.Intcode

aoc 2019, 5 do
def p1(), do: compute()

def compute(i) do
input_stream()
|> Enum.at(0)
|> String.split(",")
|> Enum.map(&String.to_integer/1)
|> Intcode.new(i)
|> Intcode.run_all()
|> Map.get(:output)
end
end
`````` one done!

## Puzzle 2

Oh wow more instructions! Let’s get hopping!

### Jumps

To handle the jumps, I added a `jump/4` function that checks if the provided function is `true` and jumps!

``````  def op(computer, [_a, b, c, 0, 5]),
do: {:ok, jump(computer, c, b, &Kernel.!=/2)}

def op(computer, [_a, b, c, 0, 6]),
do: {:ok, jump(computer, c, b, &Kernel.==/2)}

defp jump(%__MODULE__{program: prog, position: pos} = computer, a, b, f) do
if f.(0, parameter(a, Enum.at(prog, pos + 1), prog)) do
%__MODULE__{computer | position: parameter(b, Enum.at(prog, pos + 2), prog)}
else
%__MODULE__{computer | position: pos + 3}
end
end
``````

### Comparisons

Comparisons, thankfully, work exactly as the first two op-codes, so adding them is as simple as the following.

``````  def op(computer, [a, b, c, 0, 7]),
do: {:ok, compute(computer, &Kernel.</2, [a, c, b])}

def op(computer, [a, b, c, 0, 8]),
do: {:ok, compute(computer, &Kernel.==/2, [a, c, b])}
``````

After some quick testing, and more writing, puzzle 2 is done.

``````  def p2(), do: compute()
``````

## Conclusion

There were a number of hiccups along the way. Again, I started way too late. I also spent a very long time trying to make `compute/3` handle all of my op-codes. I still think its possible, but I was running out of time and needed to get something done. I also misread a test case, so my first answer of Puzzle 2 was wrong, again staying up too late. The worst part is that my code worked correctly when I wrote it, and the bad test case made me change it. In the end, I was successful. abound, and ready for the next day!

I ❤ feedback. Let me know what you think of this article on Twitter @afontaine_ca