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>()
+ );
+}