diff --git a/src/bin/day17.rs b/src/bin/day17.rs new file mode 100644 index 0000000..53cb415 --- /dev/null +++ b/src/bin/day17.rs @@ -0,0 +1,248 @@ +use std::time::Instant; + +use aoc_2024::{load, print_res}; +use bstr::BString; +use color_eyre::eyre::{bail, Context, OptionExt}; +use itertools::Itertools; + +#[derive(Debug, Clone, Copy)] +pub struct Computer { + a: u64, + b: u64, + c: u64, + + pc: usize, +} + +type Parsed = (Computer, Vec); + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + let Some((regs, program)) = std::str::from_utf8(input)?.split_once("\n\n") else { + bail!("Malformed input") + }; + + let mut regs = regs.lines(); + let mut extract_reg = |name: char| -> color_eyre::Result { + regs.next() + .ok_or_eyre("Missing register")? + .strip_prefix("Register ") + .ok_or_eyre("Malformed prefix")? + .strip_prefix(name) + .ok_or_eyre("Malformed name")? + .strip_prefix(": ") + .ok_or_eyre("Malformed suffix")? + .parse() + .map_err(Into::into) + }; + + let program = program + .strip_prefix("Program: ") + .ok_or_eyre("Malformed program")? + .trim(); + + Ok(( + Computer { + a: extract_reg('A').with_context(|| "Parsing A")?, + b: extract_reg('B').with_context(|| "Parsing B")?, + c: extract_reg('C').with_context(|| "Parsing C")?, + + pc: 0, + }, + program + .split(',') + .map(|n| { + color_eyre::eyre::ensure!( + n.len() == 1 && (b'0'..=b'7').contains(&n.as_bytes()[0]), + "Instruction: {n}" + ); + Ok(n.as_bytes()[0] - b'0') + }) + .collect::>()?, + )) +} + +impl Computer { + fn combo(&self, v: u8) -> u64 { + match v { + 0..=3 => v as u64, + 4 => self.a, + 5 => self.b, + 6 => self.c, + invalid => unreachable!("Invalid combo: {invalid}"), + } + } + + pub fn run(&mut self, code: &[u8]) -> Vec { + let mut output = Vec::new(); + while self.pc < code.len() { + let operand = code[self.pc + 1]; + match code[self.pc] { + // adv + 0 => { + self.a >>= self.combo(operand); + } + // bxl + 1 => { + self.b ^= operand as u64; + } + // bst + 2 => { + self.b = self.combo(operand) % 8; + } + // jnz + 3 => { + if self.a != 0 { + self.pc = operand as usize; + continue; + } + } + // bxc + 4 => { + self.b ^= self.c; + } + // out + 5 => { + output.push((self.combo(operand) % 8) as u8); + } + // bdv + 6 => { + self.b = self.a >> self.combo(operand); + } + // cdv + 7 => { + self.c = self.a >> self.combo(operand); + } + invalid => unreachable!("Invalid opcode: {invalid}"), + } + + self.pc += 2; + } + + output + } +} + +#[inline(never)] +pub fn part1((mut computer, code): Parsed) { + print_res!( + "Result: {}", + computer.run(&code).iter().map(|s| s.to_string()).join(",") + ); +} + +fn display_code(code: &[u8]) { + fn combo_display(v: u8) { + match v { + 0..=3 => print!("{v}"), + 4 => print!("A"), + 5 => print!("B"), + 6 => print!("C"), + _ => print!(""), + } + } + + println!("Program:"); + for (i, chunk) in code.chunks_exact(2).enumerate() { + print!(" [{:2}] ", i * 2); + match chunk[0] { + 0 => { + print!("A >>= "); + combo_display(chunk[1]); + } + 1 => { + print!("B ^= {}", chunk[1]); + } + 2 => { + print!("B = "); + combo_display(chunk[1]); + print!(" % 8"); + } + 3 => { + print!("A != 0 goto {}", chunk[1]); + } + 4 => { + print!("B ^= C"); + } + 5 => { + print!("out("); + combo_display(chunk[1]); + print!(")"); + } + 6 => { + print!("B = A >> "); + combo_display(chunk[1]); + } + 7 => { + print!("C = A >> "); + combo_display(chunk[1]); + } + _ => { + print!("") + } + } + println!() + } +} + +#[inline(never)] +pub fn part2((computer, code): Parsed) { + display_code(&code); + + let mut possibles = vec![0u64]; + + for i in 0..code.len() { + let mut new = Vec::new(); + + for a in possibles { + for x in 0..=7 { + let tentative = a | x << (i * 3); + let mut computer = computer; + computer.a = tentative; + + let result = computer.run(&code); + if i > 3 && (result.len() < i - 3 || result[0..(i - 3)] != code[0..(i - 3)]) { + continue; + } + + new.push(tentative); + } + } + + possibles = new; + } + + for tentative in possibles { + let mut computer = computer; + computer.a = tentative; + + let result = computer.run(&code); + if result == code { + print_res!("Value: {tentative}"); + return; + } + } + + panic!("No solution found"); +} + +pub fn main() -> color_eyre::Result<()> { + let context = load()?; + + let start = Instant::now(); + let parsed = parsing(&context.input)?; + let elapsed = humantime::format_duration(start.elapsed()); + + let start = Instant::now(); + if context.part == 1 { + part1(parsed); + } else { + part2(parsed); + } + let elapsed_part = humantime::format_duration(start.elapsed()); + + println!(" Parsing: {elapsed}"); + println!(" Solving: {elapsed_part}"); + + Ok(()) +}