use aoc_runner_derive::aoc; use itertools::Itertools; use regex::Regex; #[derive(Debug, Clone, Copy)] enum Register { A = 0, B = 1, C = 2, } impl From for Register { fn from(value: usize) -> Self { match value { 0 => Register::A, 1 => Register::B, 2 => Register::C, v => panic!("invalid register address {}", v), } } } #[derive(Debug, Clone, Copy)] enum Opcode { Adv = 0, Bxl = 1, Bst = 2, Jnz = 3, Bxc = 4, Out = 5, Bdv = 6, Cdv = 7, } impl From for Opcode { fn from(value: i64) -> Self { match value { 0 => Opcode::Adv, 1 => Opcode::Bxl, 2 => Opcode::Bst, 3 => Opcode::Jnz, 4 => Opcode::Bxc, 5 => Opcode::Out, 6 => Opcode::Bdv, 7 => Opcode::Cdv, v => panic!("invalid opcode {}", v), } } } impl Opcode { fn interp_operand(&self, value: i64) -> Operand { match self { Self::Adv | Self::Bst | Self::Out | Self::Bdv | Self::Cdv => Operand::new_combo(value), Self::Bxl | Self::Jnz => Operand::Literal(value), Self::Bxc => Operand::Ignore, } } } #[derive(Debug, Clone, Copy)] enum Operand { Literal(i64), Load(Register), Ignore, } impl Operand { fn new_combo(value: i64) -> Self { match value { 0..=3 => Operand::Literal(value), 4 => Operand::Load(Register::A), 5 => Operand::Load(Register::B), 6 => Operand::Load(Register::C), 7 => panic!("reserved combo operand 7"), i => panic!("invalid combo operand {}", i), } } fn value(self, m: &Machine) -> i64 { match self { Self::Literal(i) => i, Self::Load(reg) => *m.registers.load(reg), Self::Ignore => panic!("can't read ignored operand"), } } } #[derive(Debug)] struct RegisterFile { file: [T; SIZE], } impl, const SIZE: usize> RegisterFile { fn load(&self, reg: Register) -> &T { &self.file[reg as usize] } fn store(&mut self, reg: Register, val: T) { self.file[reg as usize] = val; } fn reset(&mut self) { self.file.fill(0.into()); } } #[derive(Debug, Copy, Clone)] struct Instruction { opcode: Opcode, operand: Operand, } impl Instruction { fn exec(&self, m: &mut Machine) { match self.opcode { Opcode::Adv => self.adv(m), Opcode::Bxl => self.bxl(m), Opcode::Bst => self.bst(m), Opcode::Jnz => self.jnz(m), Opcode::Bxc => self.bxc(m), Opcode::Out => self.out(m), Opcode::Bdv => self.bdv(m), Opcode::Cdv => self.cdv(m), } } fn adv(&self, m: &mut Machine) { let num = m.registers.load(Register::A); let denom = 1 << self.operand.value(m); m.registers.store(Register::A, num / denom); m.advance(); } fn bxl(&self, m: &mut Machine) { let lhs = self.operand.value(m); let rhs = m.registers.load(Register::B); m.registers.store(Register::B, lhs ^ rhs); m.advance(); } fn bst(&self, m: &mut Machine) { m.registers.store(Register::B, self.operand.value(m) % 8); m.advance(); } fn jnz(&self, m: &mut Machine) { if *m.registers.load(Register::A) == 0 { m.advance(); } else { m.jump(self.operand.value(m) as usize); } } fn bxc(&self, m: &mut Machine) { let lhs = m.registers.load(Register::B); let rhs = m.registers.load(Register::C); m.registers.store(Register::B, lhs ^ rhs); m.advance(); } fn out(&self, m: &mut Machine) { m.out_file.push(self.operand.value(m) % 8); m.advance(); } fn bdv(&self, m: &mut Machine) { let num = m.registers.load(Register::A); let denom = 1 << self.operand.value(m); m.registers.store(Register::B, num / denom); m.advance(); } fn cdv(&self, m: &mut Machine) { let num = m.registers.load(Register::A); let denom = 1 << self.operand.value(m); m.registers.store(Register::C, num / denom); m.advance(); } } #[derive(Debug)] pub struct Machine { registers: RegisterFile<3, i64>, program: Vec, program_raw: Vec, ip: usize, out_file: Vec, } impl Machine { fn run(&mut self) { let program = self.program.clone(); while let Some(inst) = program.get(self.ip) { inst.exec(self); } } fn advance(&mut self) { self.ip += 1; } fn jump(&mut self, addr: usize) { self.ip = addr; } fn reset(&mut self) { self.registers.reset(); self.ip = 0; self.out_file.clear(); } } fn parse(input: &str) -> Machine { let reg_re = Regex::new(r"Register ([ABC]): (\d+)").unwrap(); let prog_re = Regex::new(r"Program: ((\d+,)*\d+)").unwrap(); let mut registers: RegisterFile<3, i64> = RegisterFile { file: [0; 3] }; let mut program = Vec::new(); let mut program_raw = Vec::new(); for line in input.lines() { if let Some(caps) = reg_re.captures(line) { let address = (caps[1].as_bytes()[0] - b'A') as usize; let value = caps[2].parse().unwrap(); registers.store(address.into(), value); continue; } if let Some(caps) = prog_re.captures(line) { let instructions = caps[1].split(','); for (inst, operand) in instructions.tuples() { let opcode = inst.parse::().unwrap(); let operand = operand.parse::().unwrap(); program_raw.push(opcode); program_raw.push(operand); let opcode: Opcode = opcode.into(); program.push(Instruction { operand: opcode.interp_operand(operand), opcode, }); } } } Machine { registers, program, program_raw, out_file: Vec::new(), ip: 0, } } #[aoc(day17, part1)] pub fn part1(input: &str) -> String { let mut machine = parse(input); machine.run(); machine.out_file.iter().map(|n| n.to_string()).join(",") } // The program must be of the correct form to be solvable pub fn solve(m: &mut Machine, guess: i64, i: usize) -> Option { if i == m.program_raw.len() { return Some(guess); } let program_pos = m.program_raw.len() - 1 - i; let goal_digit = m.program_raw[program_pos]; for digit in 0..8 { let local_guess = (digit << (program_pos * 3)) + guess; m.reset(); m.registers.store(Register::A, local_guess); m.run(); if m.out_file.len() == m.program_raw.len() && m.out_file[program_pos] == goal_digit { if let Some(sol) = solve(m, local_guess, i + 1) { return Some(sol); } } } None } #[aoc(day17, part2)] pub fn part2(input: &str) -> i64 { let mut machine = parse(input); solve(&mut machine, 0, 0).expect("expected a solution") } #[cfg(test)] mod tests { use super::*; const EXAMPLE1: &str = "Register A: 729 Register B: 0 Register C: 0 Program: 0,1,5,4,3,0"; const EXAMPLE2: &str = "Register A: 2024 Register B: 0 Register C: 0 Program: 0,3,5,4,3,0"; #[test] fn part1_example() { assert_eq!(part1(EXAMPLE1), "4,6,3,5,6,3,5,2,1,0"); } #[test] fn part2_example() { assert_eq!(part2(EXAMPLE2), 117440); } }