use { anyhow::{ anyhow, Context, Result, }, regex::Regex, std::{ env::args, fs::File, io::{ BufRead, BufReader, }, path::Path, }, }; #[derive(Clone,Copy)] enum Instruction { Addx(isize), Noop, } impl Instruction { fn cycles(&self) -> usize { match self { Instruction::Addx(_) => 2, Instruction::Noop => 1, } } } #[derive(Clone)] struct Program { inner: Vec, } impl Program { fn instruction(&self, cycle: usize) -> Option { let mut counter = 0; for instruction in self.inner.iter() { counter += instruction.cycles(); match counter { c if c == cycle => return Some(*instruction), c if c > cycle => return None, _ => { }, } } None } } struct Registers { x: isize, } impl Registers { fn new() -> Self { Self { x: 1, } } } struct Cpu { cycle: usize, program: Program, registers: Registers, } impl Cpu { fn new(program: Program) -> Self { let registers = Registers::new(); Self { cycle: 0, program, registers, } } fn step(&mut self) { if let Some(instruction) = self.program.instruction(self.cycle) { match instruction { Instruction::Addx(argument) => self.registers.x += argument, Instruction::Noop => { }, } } self.cycle += 1; } fn signal_strength(&self) -> isize { self.registers.x * self.cycle as isize } fn pixel(&self) -> String { if ((self.registers.x - 1)..=(self.registers.x + 1)) .contains(&((self.cycle - 1) as isize % 40)) { String::from("#") } else { String::from(".") } } } fn read_input>(filename: T) -> Result { let reader = BufReader::new(File::open(filename)?); let re = Regex::new(r#"(?x)^ (?P(addx)|(noop)) ( \s (?P[0-9-]+) )? $"#)?; let instructions = reader.lines().map( |v| { let s = v?; let caps = re.captures(&s).ok_or_else(|| anyhow!("Could not parse: {s}"))?; let instruction = caps.name("instruction").unwrap_or_else(|| unreachable!()).as_str(); let argument = caps.name("argument").map(|a| a.as_str()).unwrap_or_default(); match instruction { "addx" => Ok(Instruction::Addx(argument.parse()?)), "noop" => Ok(Instruction::Noop), _ => unreachable!(), } } ).collect::>>()?; Ok(Program { inner: instructions }) } fn part1(input: &Program) -> Result { let mut cpu = Cpu::new(input.clone()); let mut sum = 0; for i in 0..=220 { if [20, 60, 100, 140, 180, 220].contains(&i) { sum += cpu.signal_strength(); } cpu.step(); } Ok(sum) } fn part2(input: &Program) -> Result { let mut cpu = Cpu::new(input.clone()); let mut output = String::default(); for i in 0..240 { cpu.step(); if i % 40 == 0 { output += "\n"; } output += &cpu.pixel(); } Ok(output) } fn main() -> Result<()> { let ( do_part_1, do_part_2 ) = aoc::do_parts(); let filename = args().nth(1).ok_or_else(|| anyhow!("Missing input filename"))?; let input = read_input(filename).context("Could not read input")?; if do_part_1 { let solution = part1(&input).context("No solution for part 1")?; println!("Part1, solution found to be: {}", solution); } if do_part_2 { let solution = part2(&input).context("No solution for part 2")?; println!("Part2, solution found to be: {}", solution); } Ok(()) }