diff --git a/.aoc_tiles/tiles/2024/17.png b/.aoc_tiles/tiles/2024/17.png index 4bbf39a..353a003 100644 Binary files a/.aoc_tiles/tiles/2024/17.png and b/.aoc_tiles/tiles/2024/17.png differ diff --git a/README.md b/README.md index 96eb4aa..443162b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2024 - 32 ⭐ - Rust + 2024 - 33 ⭐ - Rust

@@ -50,4 +50,7 @@ + + + diff --git a/src/day17.rs b/src/day17.rs new file mode 100644 index 0000000..8fe87a9 --- /dev/null +++ b/src/day17.rs @@ -0,0 +1,271 @@ +use std::{ops::BitXor, str::FromStr}; + +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: usize) -> 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"), + } + } + fn is_literal(self) -> bool { + match self { + Self::Literal(_) => true, + _ => false, + } + } +} + +#[derive(Debug)] +struct RegisterFile { + file: [T; SIZE], +} + +impl 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; + } +} + +#[derive(Debug, Copy, Clone)] +struct Instruction { + opcode: Opcode, + operand: Operand, +} + +impl Instruction { + fn new(opcode: Opcode, operand: Operand) -> Self { + Self { opcode, operand } + } + 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), + + _ => unimplemented!(), + } + } + 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.bitxor(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 a = m.registers.load(Register::B); + let b = m.registers.load(Register::C); + m.registers.store(Register::B, a.bitxor(b)); + 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)] +struct Machine { + registers: RegisterFile<3, i64>, + program: Vec, + ip: usize, + out_file: Vec, +} + +impl Machine { + fn run(&mut self) { + let program = self.program.clone(); + loop { + if let Some(inst) = program.get(self.ip) { + inst.exec(self); + } else { + break; + } + } + } + fn advance(&mut self) { + self.ip += 1; + } + fn jump(&mut self, addr: usize) { + self.ip = addr; + } +} + +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(); + 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: Opcode = inst.parse::().unwrap().into(); + let operand = operand.parse::().unwrap().into(); + program.push(Instruction { + operand: opcode.interp_operand(operand), + opcode, + }); + } + } + } + + Machine { + registers, + program, + 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(",") +} + +#[aoc(day17, part2)] +pub fn part2(input: &str) -> i64 { + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + const EXAMPLE: &str = "Register A: 729 +Register B: 0 +Register C: 0 + +Program: 0,1,5,4,3,0"; + + #[test] + fn part1_example() { + assert_eq!(part1(EXAMPLE), "4,6,3,5,6,3,5,2,1,0"); + } + + #[test] + fn part2_example() { + assert_eq!(part2(EXAMPLE), 0); + } +} diff --git a/src/lib.rs b/src/lib.rs index 345ea54..bbb9f15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod day17; mod day16; use aoc_runner_derive::aoc_lib;