240 lines
5.4 KiB
Elixir
240 lines
5.4 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)
|
||
|
|
||
|
def run(memory, parent, ip) when is_list(memory) do
|
||
|
run({memory, 0}, parent, ip)
|
||
|
end
|
||
|
|
||
|
def run({memory, _relative_base} = state, parent, ip) 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} ->
|
||
|
run(state, parent, ip + offset)
|
||
|
|
||
|
{state, :jump, ip} ->
|
||
|
run(state, parent, ip)
|
||
|
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)
|
||
|
1 -> val
|
||
|
2 -> Enum.at(memory, relative_base + 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
|
||
|
_ -> 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, _) do
|
||
|
{memory, :halt}
|
||
|
end
|
||
|
|
||
|
# add
|
||
|
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 1) do
|
||
|
a_val = get_param(op, 0, a, state)
|
||
|
b_val = get_param(op, 1, b, state)
|
||
|
dest = get_write_param(op, 2, dest, state)
|
||
|
|
||
|
{
|
||
|
{List.replace_at(memory, dest, a_val + b_val), relative_base},
|
||
|
:cont,
|
||
|
4
|
||
|
}
|
||
|
end
|
||
|
|
||
|
# multiply
|
||
|
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 2) do
|
||
|
a_val = get_param(op, 0, a, state)
|
||
|
b_val = get_param(op, 1, b, state)
|
||
|
dest = get_write_param(op, 2, dest, state)
|
||
|
|
||
|
{
|
||
|
{List.replace_at(memory, dest, a_val * b_val), relative_base},
|
||
|
:cont,
|
||
|
4
|
||
|
}
|
||
|
end
|
||
|
|
||
|
# input
|
||
|
def eval([op, addr | _], {memory, relative_base} = state, parent) when opcode(op, 3) do
|
||
|
addr = get_write_param(op, 0, addr, state)
|
||
|
send(parent, {:in, self()})
|
||
|
|
||
|
res =
|
||
|
receive do
|
||
|
{:in, val} -> val
|
||
|
end
|
||
|
|
||
|
{
|
||
|
{List.replace_at(memory, addr, res), relative_base},
|
||
|
:cont,
|
||
|
2
|
||
|
}
|
||
|
end
|
||
|
|
||
|
# output
|
||
|
def eval([op, param | _], state, parent) when opcode(op, 4) do
|
||
|
out = get_param(op, 0, param, state)
|
||
|
send(parent, {:out, self(), out})
|
||
|
|
||
|
{state, :cont, 2}
|
||
|
end
|
||
|
|
||
|
# jump if non-zero
|
||
|
def eval([op, condition, ip | _], state, _) when opcode(op, 5) do
|
||
|
case get_param(op, 0, condition, state) do
|
||
|
0 ->
|
||
|
{state, :cont, 3}
|
||
|
|
||
|
_ ->
|
||
|
{state, :jump, get_param(op, 1, ip, state)}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# jump if zero
|
||
|
def eval([op, condition, ip | _], state, _) when opcode(op, 6) do
|
||
|
case get_param(op, 0, condition, state) do
|
||
|
0 ->
|
||
|
{state, :jump, get_param(op, 1, ip, state)}
|
||
|
|
||
|
_ ->
|
||
|
{state, :cont, 3}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# less than
|
||
|
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 7) do
|
||
|
a_val = get_param(op, 0, a, state)
|
||
|
b_val = get_param(op, 1, b, state)
|
||
|
dest = get_write_param(op, 2, dest, state)
|
||
|
|
||
|
memory =
|
||
|
if a_val < b_val do
|
||
|
List.replace_at(memory, dest, 1)
|
||
|
else
|
||
|
List.replace_at(memory, dest, 0)
|
||
|
end
|
||
|
|
||
|
{{memory, relative_base}, :cont, 4}
|
||
|
end
|
||
|
|
||
|
# equals
|
||
|
def eval([op, a, b, dest | _], {memory, relative_base} = state, _) when opcode(op, 8) do
|
||
|
a_val = get_param(op, 0, a, state)
|
||
|
b_val = get_param(op, 1, b, state)
|
||
|
dest = get_write_param(op, 2, dest, state)
|
||
|
|
||
|
memory =
|
||
|
if a_val == b_val do
|
||
|
List.replace_at(memory, dest, 1)
|
||
|
else
|
||
|
List.replace_at(memory, dest, 0)
|
||
|
end
|
||
|
|
||
|
{{memory, relative_base}, :cont, 4}
|
||
|
end
|
||
|
|
||
|
# set relative base
|
||
|
def eval([op, new_base | _], {memory, relative_base} = state, _) when opcode(op, 9) do
|
||
|
{{memory, relative_base + get_param(op, 0, new_base, state)}, :cont, 2}
|
||
|
end
|
||
|
end
|