|
| 1 | +use std::collections::VecDeque; |
| 2 | +use utils::prelude::*; |
| 3 | + |
| 4 | +/// Interpreting assembly, with message passing between instances. |
| 5 | +#[derive(Clone, Debug)] |
| 6 | +pub struct Day18 { |
| 7 | + instructions: Vec<Instruction>, |
| 8 | +} |
| 9 | + |
| 10 | +// Use an enum for registers to avoid bounds checks when accessing the register array, slightly |
| 11 | +// improving performance |
| 12 | +#[derive(Copy, Clone, Debug, PartialEq)] |
| 13 | +enum Register { |
| 14 | + A, |
| 15 | + B, |
| 16 | + C, |
| 17 | + D, |
| 18 | + F, |
| 19 | + I, |
| 20 | + P, |
| 21 | +} |
| 22 | + |
| 23 | +// Use separate enum variants for register and constant operands to minimize branching in the |
| 24 | +// interpreter loop, again slightly improving performance |
| 25 | +#[derive(Copy, Clone, Debug, PartialEq)] |
| 26 | +enum Instruction { |
| 27 | + Snd(Register), |
| 28 | + SndN(i32), // Used in second example |
| 29 | + Set(Register, Register), |
| 30 | + SetN(Register, i32), |
| 31 | + Add(Register, Register), |
| 32 | + AddN(Register, i32), |
| 33 | + Mul(Register, Register), // Used in first example |
| 34 | + MulN(Register, i32), |
| 35 | + Mod(Register, Register), |
| 36 | + ModN(Register, i32), |
| 37 | + Rcv(Register), |
| 38 | + Jgz(Register, Register), |
| 39 | + JgzN(Register, i32), |
| 40 | + Jmp(i32), // Used for jgz $n where $n > 0 |
| 41 | + Noop(), // Used for jgz $n where $n <= 0 |
| 42 | +} |
| 43 | + |
| 44 | +#[derive(Debug)] |
| 45 | +struct Program<'a> { |
| 46 | + instructions: &'a [Instruction], |
| 47 | + pc: usize, |
| 48 | + reg: [i64; 7], |
| 49 | +} |
| 50 | + |
| 51 | +impl Day18 { |
| 52 | + pub fn new(input: &str, _: InputType) -> Result<Self, InputError> { |
| 53 | + let register = parser::literal_map!( |
| 54 | + "a" => Register::A, |
| 55 | + "b" => Register::B, |
| 56 | + "c" => Register::C, |
| 57 | + "d" => Register::D, |
| 58 | + "f" => Register::F, |
| 59 | + "i" => Register::I, |
| 60 | + "p" => Register::P, |
| 61 | + ); |
| 62 | + |
| 63 | + let instruction = parser::parse_tree!( |
| 64 | + ("snd ") =>> { |
| 65 | + (r @ register) => Instruction::Snd(r), |
| 66 | + (v @ parser::i32()) => Instruction::SndN(v), |
| 67 | + }, |
| 68 | + ("set ", r @ register, b' ') =>> { |
| 69 | + (r2 @ register) => Instruction::Set(r, r2), |
| 70 | + (v @ parser::i32()) => Instruction::SetN(r, v), |
| 71 | + }, |
| 72 | + ("add ", r @ register, b' ') =>> { |
| 73 | + (r2 @ register) => Instruction::Add(r, r2), |
| 74 | + (v @ parser::i32()) => Instruction::AddN(r, v), |
| 75 | + }, |
| 76 | + ("mul ", r @ register, b' ') =>> { |
| 77 | + (r2 @ register) => Instruction::Mul(r, r2), |
| 78 | + (v @ parser::i32()) => Instruction::MulN(r, v), |
| 79 | + }, |
| 80 | + ("mod ", r @ register, b' ') =>> { |
| 81 | + (r2 @ register) => Instruction::Mod(r, r2), |
| 82 | + (v @ parser::i32()) => Instruction::ModN(r, v), |
| 83 | + }, |
| 84 | + ("rcv ", r @ register) => Instruction::Rcv(r), |
| 85 | + ("jgz ") =>> { |
| 86 | + (r @ register, b' ') =>> { |
| 87 | + (r2 @ register) => Instruction::Jgz(r, r2), |
| 88 | + (v @ parser::i32()) => Instruction::JgzN(r, v), |
| 89 | + }, |
| 90 | + (x @ parser::i32(), b' ', v @ parser::i32()) => { |
| 91 | + if x > 0 { |
| 92 | + Instruction::Jmp(v) |
| 93 | + } else { |
| 94 | + Instruction::Noop() |
| 95 | + } |
| 96 | + }, |
| 97 | + }, |
| 98 | + ); |
| 99 | + |
| 100 | + Ok(Self { |
| 101 | + instructions: instruction.parse_lines(input)?, |
| 102 | + }) |
| 103 | + } |
| 104 | + |
| 105 | + #[must_use] |
| 106 | + pub fn part1(&self) -> i64 { |
| 107 | + let mut program = Program::from_instructions(&self.instructions); |
| 108 | + let mut last = None; |
| 109 | + |
| 110 | + program.run( |
| 111 | + |v| last = Some(v), |
| 112 | + |v| { |
| 113 | + if v == 0 { |
| 114 | + // If the register is zero, do nothing/set the register back to zero |
| 115 | + Some(0) |
| 116 | + } else { |
| 117 | + // If the register is not zero, this is the first recovered frequency, so return |
| 118 | + // None to halt program execution |
| 119 | + None |
| 120 | + } |
| 121 | + }, |
| 122 | + ); |
| 123 | + |
| 124 | + last.unwrap() |
| 125 | + } |
| 126 | + |
| 127 | + #[must_use] |
| 128 | + pub fn part2(&self) -> u32 { |
| 129 | + let mut program0 = Program::from_instructions(&self.instructions); |
| 130 | + let mut inbound0 = VecDeque::new(); |
| 131 | + |
| 132 | + let mut program1 = Program::from_instructions(&self.instructions); |
| 133 | + let mut inbound1 = VecDeque::new(); |
| 134 | + let mut sent = 0; |
| 135 | + program1.reg[Register::P as usize] = 1; |
| 136 | + |
| 137 | + // Run both programs until they both fail to make progress. Use | instead of || so each |
| 138 | + // iteration always attempts to run both |
| 139 | + while program0.run( |
| 140 | + |v| { |
| 141 | + // Send to program 1 |
| 142 | + inbound1.push_back(v) |
| 143 | + }, |
| 144 | + |_| { |
| 145 | + // Receive from program 1 |
| 146 | + inbound0.pop_front() |
| 147 | + }, |
| 148 | + ) | program1.run( |
| 149 | + |v| { |
| 150 | + // Send to program 0, keeping track of how many values are sent |
| 151 | + sent += 1; |
| 152 | + inbound0.push_back(v) |
| 153 | + }, |
| 154 | + |_| { |
| 155 | + // Receive from program 0 |
| 156 | + inbound1.pop_front() |
| 157 | + }, |
| 158 | + ) {} |
| 159 | + |
| 160 | + sent |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +impl<'a> Program<'a> { |
| 165 | + fn from_instructions(instructions: &'a [Instruction]) -> Self { |
| 166 | + Program { |
| 167 | + instructions, |
| 168 | + pc: 0, |
| 169 | + reg: [0; 7], |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + fn run(&mut self, mut snd: impl FnMut(i64), mut rcv: impl FnMut(i64) -> Option<i64>) -> bool { |
| 174 | + let mut progress = false; |
| 175 | + |
| 176 | + while let Some(&instruction) = self.instructions.get(self.pc) { |
| 177 | + match instruction { |
| 178 | + Instruction::Snd(r) => snd(self.reg[r as usize]), |
| 179 | + Instruction::SndN(v) => snd(v as i64), |
| 180 | + Instruction::Set(r, r2) => self.reg[r as usize] = self.reg[r2 as usize], |
| 181 | + Instruction::SetN(r, v) => self.reg[r as usize] = v as i64, |
| 182 | + Instruction::Add(r, r2) => self.reg[r as usize] += self.reg[r2 as usize], |
| 183 | + Instruction::AddN(r, v) => self.reg[r as usize] += v as i64, |
| 184 | + Instruction::Mul(r, r2) => self.reg[r as usize] *= self.reg[r2 as usize], |
| 185 | + Instruction::MulN(r, v) => self.reg[r as usize] *= v as i64, |
| 186 | + Instruction::Mod(r, r2) => { |
| 187 | + self.reg[r as usize] = self.reg[r as usize].rem_euclid(self.reg[r2 as usize]) |
| 188 | + } |
| 189 | + Instruction::ModN(r, v) => { |
| 190 | + self.reg[r as usize] = self.reg[r as usize].rem_euclid(v as i64) |
| 191 | + } |
| 192 | + Instruction::Rcv(r) => { |
| 193 | + if let Some(value) = rcv(self.reg[r as usize]) { |
| 194 | + self.reg[r as usize] = value; |
| 195 | + } else { |
| 196 | + break; |
| 197 | + } |
| 198 | + } |
| 199 | + Instruction::Jgz(r, ro) => { |
| 200 | + if self.reg[r as usize] > 0 { |
| 201 | + self.pc = self.pc.wrapping_add_signed(self.reg[ro as usize] as isize) - 1; |
| 202 | + } |
| 203 | + } |
| 204 | + Instruction::JgzN(r, v) => { |
| 205 | + if self.reg[r as usize] > 0 { |
| 206 | + self.pc = self.pc.wrapping_add_signed(v as isize) - 1; |
| 207 | + } |
| 208 | + } |
| 209 | + Instruction::Jmp(v) => { |
| 210 | + self.pc = self.pc.wrapping_add(v as usize) - 1; |
| 211 | + } |
| 212 | + Instruction::Noop() => {} |
| 213 | + } |
| 214 | + |
| 215 | + self.pc += 1; |
| 216 | + progress = true; |
| 217 | + } |
| 218 | + |
| 219 | + progress |
| 220 | + } |
| 221 | +} |
| 222 | + |
| 223 | +examples!(Day18 -> (i64, u32) [ |
| 224 | + { |
| 225 | + input: "set a 1\n\ |
| 226 | + add a 2\n\ |
| 227 | + mul a a\n\ |
| 228 | + mod a 5\n\ |
| 229 | + snd a\n\ |
| 230 | + set a 0\n\ |
| 231 | + rcv a\n\ |
| 232 | + jgz a -1\n\ |
| 233 | + set a 1\n\ |
| 234 | + jgz a -2", |
| 235 | + part1: 4, |
| 236 | + }, |
| 237 | + { |
| 238 | + input: "snd 1\n\ |
| 239 | + snd 2\n\ |
| 240 | + snd p\n\ |
| 241 | + rcv a\n\ |
| 242 | + rcv b\n\ |
| 243 | + rcv c\n\ |
| 244 | + rcv d", |
| 245 | + part2: 3, |
| 246 | + }, |
| 247 | +]); |
0 commit comments