AoC19/lib/day9/day9.ex

295 lines
7.3 KiB
Elixir

defmodule Day9 do
def read_program do
File.read!("lib/day9/input.txt")
|> String.trim()
|> String.split(",")
|> Enum.map(&String.to_integer/1)
end
def run_program do
read_program()
|> expand_memory()
|> run_cli()
end
def test do
# prog = [109, 1, 204, -1, 1001, 100, 1, 100, 1008, 100, 16, 101, 1006, 101, 0, 99]
# prog = [1102, 34_915_192, 34_915_192, 7, 4, 7, 99, 0]
prog = [104, 1_125_899_906_842_624, 99]
expanded = expand_memory(prog, 150)
run_cli(expanded)
end
def run_cli(memory, opts \\ []) do
parent = self()
proc =
spawn(fn ->
run(memory, parent)
end)
handle_cli_messages(proc, opts)
end
def handle_cli_messages(proc, opts) do
receive do
{:halt, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:ok, _, memory} ->
if Keyword.get(opts, :memory, false) do
memory
else
:ok
end
{:in, _} ->
res = IO.gets("intcode> ") |> String.trim() |> String.to_integer()
send(proc, {:in, res})
handle_cli_messages(proc, opts)
{:out, _, out} ->
IO.puts("Output: #{out}")
handle_cli_messages(proc, opts)
end
end
def expand_memory(memory, length \\ nil) do
memory ++ repeat(0, length || length(memory) * 10)
end
def repeat(_, 0), do: []
def repeat(val, count), do: [val | repeat(val, count - 1)]
def run(state, parent, ip \\ 0, debug \\ false)
def run(memory, parent, ip, debug) when is_list(memory) do
run({memory, 0}, parent, ip, debug)
end
def run({memory, _relative_base} = state, parent, ip, debug) when ip < length(memory) do
# IO.puts("IP: #{ip}")
# IO.inspect(memory)
case eval(Enum.drop(memory, ip), state, parent) do
{memory, :halt} ->
send(parent, {:halt, self(), memory})
{state, :cont, offset} ->
if debug do
receive do
:step ->
run(state, parent, ip + offset)
:abort ->
send(parent, {:abort, self(), state})
end
else
run(state, parent, ip + offset)
end
{state, :jump, ip} ->
if debug do
receive do
:step ->
run(state, parent, ip)
:abort ->
send(parent, {:abort, self(), state})
end
else
run(state, parent, ip)
end
end
end
def run(state, parent, _, _) do
send(parent, {:ok, self(), state})
end
defmacro opcode(op, expected) do
quote do
rem(unquote(op), 100) == unquote(expected)
end
end
def get_param(op, param, val, {memory, relative_base}) do
place = param + 2
case op |> div(pow(10, place)) |> rem(10) do
0 -> {Enum.at(memory, val), {:position, val}}
1 -> {val, {:immediate, val}}
2 -> {Enum.at(memory, relative_base + val), {:relative, val}}
end
end
def get_write_param(op, param, val, {memory, relative_base}) do
place = param + 2
case op |> div(pow(10, place)) |> rem(10) do
2 -> {relative_base + val, {:relative, val}}
_ -> {val, {:immediate, val}}
end
end
def pow(base, 1), do: base
def pow(base, exp) do
base * pow(base, exp - 1)
end
# halt
def eval([99 | _], memory, parent) do
send(parent, {:debug, "Halted", [], memory})
{memory, :halt}
end
# add
def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 1) do
{a_val, a_param} = get_param(op, 0, a, state)
{b_val, b_param} = get_param(op, 1, b, state)
{dest, dest_param} = get_write_param(op, 2, dest, state)
new_state = {List.replace_at(memory, dest, a_val + b_val), relative_base}
send(parent, {:debug, "Add", [a_param, b_param, dest_param], new_state})
{
new_state,
:cont,
4
}
end
# multiply
def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 2) do
{a_val, a_param} = get_param(op, 0, a, state)
{b_val, b_param} = get_param(op, 1, b, state)
{dest, dest_param} = get_write_param(op, 2, dest, state)
new_state = {List.replace_at(memory, dest, a_val * b_val), relative_base}
send(parent, {:debug, "Multiply", [a_param, b_param, dest_param], new_state})
{
new_state,
:cont,
4
}
end
# input
def eval([op, addr | _], {memory, relative_base} = state, parent) when opcode(op, 3) do
{addr, addr_param} = get_write_param(op, 0, addr, state)
send(parent, {:in, self()})
res =
receive do
{:in, val} -> val
end
new_state = {List.replace_at(memory, addr, res), relative_base}
send(parent, {:debug, "Input", [addr_param], new_state})
{
new_state,
:cont,
2
}
end
# output
def eval([op, param | _], state, parent) when opcode(op, 4) do
{out, out_param} = get_param(op, 0, param, state)
send(parent, {:out, self(), out})
send(parent, {:debug, "Output", [out_param], state})
{state, :cont, 2}
end
# jump if non-zero
def eval([op, condition, ip | _], state, parent) when opcode(op, 5) do
{cond_val, cond_param} = get_param(op, 0, condition, state)
{dest_val, dest_param} = get_param(op, 1, ip, state)
new_state =
case cond_val do
0 ->
{state, :cont, 3}
_ ->
{state, :jump, dest_val}
end
send(parent, {:debug, "Jump if non-zero", [cond_param, dest_param], new_state})
new_state
end
# jump if zero
def eval([op, condition, ip | _], state, parent) when opcode(op, 6) do
{cond_val, cond_param} = get_param(op, 0, condition, state)
{dest_val, dest_param} = get_param(op, 1, ip, state)
new_state =
case cond_val do
0 ->
{state, :jump, dest_val}
_ ->
{state, :cont, 3}
end
send(parent, {:debug, "Jump if zero", [cond_param, dest_param], new_state})
new_state
end
# less than
def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 7) do
{a_val, a_param} = get_param(op, 0, a, state)
{b_val, b_param} = get_param(op, 1, b, state)
{dest, dest_param} = get_write_param(op, 2, dest, state)
new_state = {
if a_val < b_val do
List.replace_at(memory, dest, 1)
else
List.replace_at(memory, dest, 0)
end,
relative_base
}
send(parent, {:debug, "Less than", [a_param, b_param, dest_param], new_state})
{new_state, :cont, 4}
end
# equals
def eval([op, a, b, dest | _], {memory, relative_base} = state, parent) when opcode(op, 8) do
{a_val, a_param} = get_param(op, 0, a, state)
{b_val, b_param} = get_param(op, 1, b, state)
{dest, dest_param} = get_write_param(op, 2, dest, state)
new_state = {
if a_val == b_val do
List.replace_at(memory, dest, 1)
else
List.replace_at(memory, dest, 0)
end,
relative_base
}
send(parent, {:debug, "Equal to", [a_param, b_param, dest_param], new_state})
{new_state, :cont, 4}
end
# set relative base
def eval([op, new_base | _], {memory, relative_base} = state, parent) when opcode(op, 9) do
{new_base_val, new_base_param} = get_param(op, 0, new_base, state)
new_state = {memory, relative_base + new_base_val}
send(parent, {:debug, "Set relative base", [new_base_param], new_state})
{new_state, :cont, 2}
end
end