~cdv/aoc-2018-rs

88767b34c4b2a8bd9109e15ed6777708025f668c — Christopher Vittal 1 year, 9 months ago 2917fda day19
Solve day nineteen
3 files changed, 294 insertions(+), 1 deletions(-)

A data/day19
A descriptions/Day19.md
M src/bin/day19.rs
A data/day19 => data/day19 +37 -0
@@ 0,0 1,37 @@
#ip 4
addi 4 16 4
seti 1 2 3
seti 1 6 1
mulr 3 1 2
eqrr 2 5 2
addr 2 4 4
addi 4 1 4
addr 3 0 0
addi 1 1 1
gtrr 1 5 2
addr 4 2 4
seti 2 8 4
addi 3 1 3
gtrr 3 5 2
addr 2 4 4
seti 1 4 4
mulr 4 4 4
addi 5 2 5
mulr 5 5 5
mulr 4 5 5
muli 5 11 5
addi 2 5 2
mulr 2 4 2
addi 2 18 2
addr 5 2 5
addr 4 0 4
seti 0 6 4
setr 4 8 2
mulr 2 4 2
addr 4 2 2
mulr 4 2 2
muli 2 14 2
mulr 2 4 2
addr 5 2 5
seti 0 1 0
seti 0 5 4

A descriptions/Day19.md => descriptions/Day19.md +119 -0
@@ 0,0 1,119 @@
\-\-- Day 19: Go With The Flow \-\--
------------------------------------

With the Elves well on their way constructing the North Pole base, you
turn your attention back to understanding the inner workings of
programming the device.

You can\'t help but notice that the [device\'s opcodes](16) don\'t
contain any *flow control* like jump instructions. The device\'s
[manual](16) goes on to explain:

\"In programs where flow control is required, the [instruction
pointer](https://en.wikipedia.org/wiki/Program_counter) can be *bound to
a register* so that it can be manipulated directly. This way,
`setr`/`seti` can function as absolute jumps, `addr`/`addi` can function
as relative jumps, and other opcodes can cause [truly
fascinating]{title="Good luck maintaining a program that uses a bitwise operation on its instruction pointer, though."}
effects.\"

This mechanism is achieved through a declaration like `#ip 1`, which
would modify register `1` so that accesses to it let the program
indirectly access the instruction pointer itself. To compensate for this
kind of binding, there are now *six* registers (numbered `0` through
`5`); the five not bound to the instruction pointer behave as normal.
Otherwise, the same rules apply as [the last time you worked with this
device](16).

When the *instruction pointer* is bound to a register, its value is
written to that register just before each instruction is executed, and
the value of that register is written back to the instruction pointer
immediately after each instruction finishes execution. Afterward, move
to the next instruction by adding one to the instruction pointer, even
if the value in the instruction pointer was just updated by an
instruction. (Because of this, instructions must effectively set the
instruction pointer to the instruction *before* the one they want
executed next.)

The instruction pointer is `0` during the first instruction, `1` during
the second, and so on. If the instruction pointer ever causes the device
to attempt to load an instruction outside the instructions defined in
the program, the program instead immediately halts. The instruction
pointer starts at `0`.

It turns out that this new information is already proving useful: the
CPU in the device is not very powerful, and a background process is
occupying most of its time. You dump the background process\'
declarations and instructions to a file (your puzzle input), making sure
to use the names of the opcodes rather than the numbers.

For example, suppose you have the following program:

    #ip 0
    seti 5 0 1
    seti 6 0 2
    addi 0 1 0
    addr 1 2 3
    setr 1 0 0
    seti 8 0 4
    seti 9 0 5

When executed, the following instructions are executed. Each line
contains the value of the instruction pointer at the time the
instruction started, the values of the six registers before executing
the instructions (in square brackets), the instruction itself, and the
values of the six registers after executing the instruction (also in
square brackets).

    ip=0 [0, 0, 0, 0, 0, 0] seti 5 0 1 [0, 5, 0, 0, 0, 0]
    ip=1 [1, 5, 0, 0, 0, 0] seti 6 0 2 [1, 5, 6, 0, 0, 0]
    ip=2 [2, 5, 6, 0, 0, 0] addi 0 1 0 [3, 5, 6, 0, 0, 0]
    ip=4 [4, 5, 6, 0, 0, 0] setr 1 0 0 [5, 5, 6, 0, 0, 0]
    ip=6 [6, 5, 6, 0, 0, 0] seti 9 0 5 [6, 5, 6, 0, 0, 9]

In detail, when running this program, the following events occur:

-   The first line (`#ip 0`) indicates that the instruction pointer
    should be bound to register `0` in this program. This is not an
    instruction, and so the value of the instruction pointer does not
    change during the processing of this line.
-   The instruction pointer contains `0`, and so the first instruction
    is executed (`seti 5 0 1`). It updates register `0` to the current
    instruction pointer value (`0`), sets register `1` to `5`, sets the
    instruction pointer to the value of register `0` (which has no
    effect, as the instruction did not modify register `0`), and then
    adds one to the instruction pointer.
-   The instruction pointer contains `1`, and so the second instruction,
    `seti 6 0 2`, is executed. This is very similar to the instruction
    before it: `6` is stored in register `2`, and the instruction
    pointer is left with the value `2`.
-   The instruction pointer is `2`, which points at the instruction
    `addi 0 1 0`. This is like a *relative jump*: the value of the
    instruction pointer, `2`, is loaded into register `0`. Then, `addi`
    finds the result of adding the value in register `0` and the value
    `1`, storing the result, `3`, back in register `0`. Register `0` is
    then copied back to the instruction pointer, which will cause it to
    end up `1` larger than it would have otherwise and skip the next
    instruction (`addr 1 2 3`) entirely. Finally, `1` is added to the
    instruction pointer.
-   The instruction pointer is `4`, so the instruction `setr 1 0 0` is
    run. This is like an *absolute jump*: it copies the value contained
    in register `1`, `5`, into register `0`, which causes it to end up
    in the instruction pointer. The instruction pointer is then
    incremented, leaving it at `6`.
-   The instruction pointer is `6`, so the instruction `seti 9 0 5`
    stores `9` into register `5`. The instruction pointer is
    incremented, causing it to point outside the program, and so the
    program ends.

*What value is left in register `0`* when the background process halts?

\-\-- Part Two \-\-- {#part2}
--------------------

A new background process immediately spins up in its place. It appears
identical, but on closer inspection, you notice that *this time,
register `0` started with the value `1`*.

*What value is left in register `0`* when this new background process
halts?

M src/bin/day19.rs => src/bin/day19.rs +138 -1
@@ 1,1 1,138 @@
fn main() {}
use std::str::FromStr;

static INPUT: &str = "data/day19";

#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
#[repr(u8)]
enum OpCode {
    Addr,
    Addi,
    Mulr,
    Muli,
    Banr,
    Bani,
    Borr,
    Bori,
    Setr,
    Seti,
    Gtir,
    Gtri,
    Gtrr,
    Eqir,
    Eqri,
    Eqrr,
}

impl FromStr for OpCode {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "addr" => OpCode::Addr,
            "addi" => OpCode::Addi,
            "mulr" => OpCode::Mulr,
            "muli" => OpCode::Muli,
            "banr" => OpCode::Banr,
            "bani" => OpCode::Bani,
            "borr" => OpCode::Borr,
            "bori" => OpCode::Bori,
            "setr" => OpCode::Setr,
            "seti" => OpCode::Seti,
            "gtir" => OpCode::Gtir,
            "gtri" => OpCode::Gtri,
            "gtrr" => OpCode::Gtrr,
            "eqir" => OpCode::Eqir,
            "eqri" => OpCode::Eqri,
            "eqrr" => OpCode::Eqrr,
            _ => panic!("invalid"),
        })
    }
}

#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
struct Op {
    code: OpCode,
    a: usize,
    b: usize,
    c: usize,
}

impl std::str::FromStr for Op {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut it = s.split_whitespace();
        let code = it.next().unwrap().parse().unwrap();
        let a = it.next().unwrap().parse().unwrap();
        let b = it.next().unwrap().parse().unwrap();
        let c = it.next().unwrap().parse().unwrap();
        Ok(Op { code, a, b, c })
    }
}

struct Device {
    regs: [usize; 6],
    ip: usize,
    ops: Vec<Op>,
}

impl Device {
    fn raw_op(&mut self, op: OpCode, a: usize, b: usize, c: usize) {
        use self::OpCode::*;
        match op {
            Addr => self.regs[c] = self.regs[a] + self.regs[b],
            Addi => self.regs[c] = self.regs[a] + b,
            Mulr => self.regs[c] = self.regs[a] * self.regs[b],
            Muli => self.regs[c] = self.regs[a] * b,
            Banr => self.regs[c] = self.regs[a] & self.regs[b],
            Bani => self.regs[c] = self.regs[a] & b,
            Borr => self.regs[c] = self.regs[a] | self.regs[b],
            Bori => self.regs[c] = self.regs[a] | b,
            Setr => self.regs[c] = self.regs[a],
            Seti => self.regs[c] = a,
            Gtir => self.regs[c] = (a > self.regs[b]) as usize,
            Gtri => self.regs[c] = (self.regs[a] > b) as usize,
            Gtrr => self.regs[c] = (self.regs[a] > self.regs[b]) as usize,
            Eqir => self.regs[c] = (a == self.regs[b]) as usize,
            Eqri => self.regs[c] = (self.regs[a] == b) as usize,
            Eqrr => self.regs[c] = (self.regs[a] == self.regs[b]) as usize,
        }
    }

    fn op(&mut self) {
        println!("regs: {:?}", self.regs);
        let Op { code, a, b, c } = self.ops[self.regs[self.ip]];
        self.raw_op(code, a, b, c);
        self.regs[self.ip] += 1;
    }
}

fn main() {
    let mut input = aoc::file::to_strings_iter(INPUT);
    let ip = input
        .next()
        .unwrap()
        .trim_start_matches("#ip ")
        .parse()
        .unwrap();
    let ops = input
        .map(|v| match v.parse() {
            Ok(vp) => vp,
            _ => panic!("parse error for input: {}", v),
        })
        .collect();
    let mut dev = Device {
        regs: [0; 6],
        ip,
        ops,
    };
    while dev.regs[dev.ip] < dev.ops.len() {
        dev.op();
    }
    println!("  1: {}", dev.regs[0]);

    // below is determined by examining the results running the input
    let p2 = 10_551_364;
    println!(
        "  2: {}",
        p2 + (1..=p2 / 2).filter(|x| p2 % x == 0).sum::<u32>()
    );
}