use num::bigint::BigInt; use num::traits::identities::Zero; use rpds::RedBlackTreeMap; use rpds::{list::List, vector::Vector}; use std::fmt; use std::iter; use std::iter::FromIterator; use std::iter::IntoIterator; pub type Intcode = BigInt; #[derive(Debug, Clone)] pub struct IntcodeProgram { instruction_pointer: Intcode, pub error: Option, pub halted: bool, pub awaiting_input: bool, memory: RedBlackTreeMap, pub input: List, pub output: Vector, } impl FromIterator for IntcodeProgram { fn from_iter>(iter: I) -> Self { IntcodeProgram { instruction_pointer: Intcode::from(0), error: None, halted: false, awaiting_input: false, memory: iter .into_iter() .enumerate() .map(|(addr, val)| (Intcode::from(addr), val)) .collect(), input: List::new(), output: Vector::new(), } } } impl IntcodeProgram { pub fn with_noun_verb_input(&self, noun: Intcode, verb: Intcode) -> IntcodeProgram { self.with_memory_set(1.into(), noun) .with_memory_set(2.into(), verb) } pub fn with_input(&self, input: List) -> IntcodeProgram { IntcodeProgram { input, ..self.clone() } } pub fn with_additional_input(&self, input: List) -> IntcodeProgram { IntcodeProgram { input: self.input.iter().chain(input.iter()).cloned().collect(), ..self.clone() } } pub fn with_cleared_output(&self) -> IntcodeProgram { IntcodeProgram { output: Vector::new(), ..self.clone() } } pub fn execute(&self) -> Result, IntcodeProgramError> { self.run_to_termination().output_into_result() } pub fn execute_returning_memory_0(&self) -> Result { self.run_to_termination().memory_0_into_result() } pub fn run_to_termination(&self) -> IntcodeProgram { iter::successors(Some(self.clear_await_input()), |p| { Some(IntcodeProgram::next(&p)) }) .find(|p| p.halted) .unwrap() // successors doesn't terminate, so this will never be none. } pub fn run_to_termination_or_input(&self) -> IntcodeProgram { iter::successors(Some(self.clear_await_input()), |p| { Some(IntcodeProgram::next(&p)) }) .find(|p| p.halted || p.awaiting_input) .unwrap() } fn with_instruction_pointer(&self, instruction_pointer: Intcode) -> IntcodeProgram { IntcodeProgram { instruction_pointer, ..self.clone() } } fn with_instruction_pointer_offset(&self, offset: usize) -> IntcodeProgram { IntcodeProgram { instruction_pointer: self.instruction_pointer.clone() + offset, ..self.clone() } } fn with_memory_set(&self, address: Intcode, value: Intcode) -> IntcodeProgram { IntcodeProgram { memory: self.memory.insert(address, value), ..self.clone() } } fn with_input_consumed(&self) -> IntcodeProgram { self.input .drop_first() .map(|input| IntcodeProgram { input, ..self.clone() }) .unwrap_or(self.error(IntcodeProgramError::Unknown)) } fn with_output(&self, print: Intcode) -> IntcodeProgram { IntcodeProgram { output: self.output.push_back(print), ..self.clone() } } fn output_into_result(&self) -> Result, IntcodeProgramError> { match self.error { Some(ref error) => Err(error.clone()), None => Ok(self.output.clone()), } } fn memory_0_into_result(&self) -> Result { match self.error { Some(ref error) => Err(error.clone()), None => Ok(self .memory .get(&Intcode::from(0)) .cloned() .unwrap_or(Intcode::from(0))), } } fn next(&self) -> IntcodeProgram { self.memory .get(&self.instruction_pointer) .map(|opcode| match opcode.to_radix_le(100).1[0] { 1 => self.add(opcode), 2 => self.multiply(opcode), 3 => self.input(opcode), 4 => self.output(opcode), 5 => self.jump_if_true(opcode), 6 => self.jump_if_false(opcode), 7 => self.less_than(opcode), 8 => self.equals(opcode), 99 => self.halt(), unknown => self.error(IntcodeProgramError::InvalidOpcode(unknown.clone())), }) .unwrap_or(self.error(IntcodeProgramError::Unknown)) } fn add(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (in1, in2, out) => self .with_instruction_pointer_offset(4) .with_memory_set(out, in1 + in2), } } fn multiply(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (in1, in2, out) => self .with_instruction_pointer_offset(4) .with_memory_set(out, in1 * in2), } } fn input(&self, _mode: &Intcode) -> IntcodeProgram { match (self.input.first().cloned(), self.get_immediate(1)) { (Some(input), out) => self .with_instruction_pointer_offset(2) .with_memory_set(out, input) .with_input_consumed(), (None, _out) => self.await_input(), } } fn output(&self, mode: &Intcode) -> IntcodeProgram { match self.get(1, mode) { print => self.with_instruction_pointer_offset(2).with_output(print), } } fn jump_if_true(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode)) { (ref pred, ref to) if !pred.is_zero() => self.with_instruction_pointer(to.clone()), (_, _) => self.with_instruction_pointer_offset(3), } } fn jump_if_false(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode)) { (ref pred, ref to) if pred.is_zero() => self.with_instruction_pointer(to.clone()), (_, _) => self.with_instruction_pointer_offset(3), } } fn less_than(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (in1, in2, out) => self.with_instruction_pointer_offset(4).with_memory_set( out, if in1 < in2 { Intcode::from(1) } else { Intcode::from(0) }, ), } } fn equals(&self, mode: &Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (in1, in2, out) => self.with_instruction_pointer_offset(4).with_memory_set( out, if in1 == in2 { Intcode::from(1) } else { Intcode::from(0) }, ), } } fn halt(&self) -> IntcodeProgram { IntcodeProgram { halted: true, ..self.clone() } } fn await_input(&self) -> IntcodeProgram { IntcodeProgram { awaiting_input: true, ..self.clone() } } fn clear_await_input(&self) -> IntcodeProgram { IntcodeProgram { awaiting_input: false, ..self.clone() } } fn error(&self, error: IntcodeProgramError) -> IntcodeProgram { IntcodeProgram { halted: true, error: Some(error), ..self.clone() } } fn get(&self, pointer_offset: usize, mode: &Intcode) -> Intcode { match mode .to_radix_le(10) .1 .get(pointer_offset + 2) .cloned() .unwrap_or(0) { 0 => self.get_position(pointer_offset), 1 => self.get_immediate(pointer_offset), _ => Intcode::from(0), // TODO: Relative mode } } fn get_immediate(&self, pointer_offset: usize) -> Intcode { self.memory .get(&(self.instruction_pointer.clone() + pointer_offset)) .cloned() .unwrap_or(Intcode::from(0)) } fn get_position(&self, pointer_offset: usize) -> Intcode { self.memory .get(&self.get_immediate(pointer_offset)) .cloned() .unwrap_or(Intcode::from(0)) } } #[derive(Debug, PartialEq, Clone)] pub enum IntcodeProgramError { InvalidOpcode(u8), Unknown, } impl fmt::Display for IntcodeProgramError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use IntcodeProgramError::*; match self { InvalidOpcode(i) => write!(f, "{} is not a valid opcode", i), Unknown => write!(f, "Unknown error"), } } } impl std::error::Error for IntcodeProgramError {} #[cfg(test)] mod tests { use super::*; #[test] fn day_2_example_1() { let program: IntcodeProgram = vec![1, 0, 0, 0, 99] .into_iter() .map(Intcode::from) .collect::() .run_to_termination(); assert_eq!(program.error, None); assert_eq!( program.memory, vec![2, 0, 0, 0, 99] .into_iter() .enumerate() .map(|(i, val)| (Intcode::from(i), Intcode::from(val))) .collect::>() ); } }