use rpds::{list::List, vector::Vector}; use std::fmt; use std::iter; use std::iter::FromIterator; use std::iter::IntoIterator; pub type Intcode = i32; #[derive(Debug, Clone)] pub struct IntcodeProgram { instruction_pointer: usize, pub error: bool, pub halted: bool, pub awaiting_input: bool, memory: Vector, pub input: List, pub output: Vector, } impl FromIterator for IntcodeProgram { fn from_iter>(iter: I) -> Self { IntcodeProgram { instruction_pointer: 0, error: false, halted: false, awaiting_input: false, memory: iter.into_iter().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, noun).with_memory_set(2, 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, IntcodeProgramError> { 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: usize) -> IntcodeProgram { IntcodeProgram { instruction_pointer, ..self.clone() } } fn with_instruction_pointer_offset(&self, offset: usize) -> IntcodeProgram { IntcodeProgram { instruction_pointer: self.instruction_pointer + offset, ..self.clone() } } fn with_memory_set(&self, address: usize, value: Intcode) -> IntcodeProgram { self.memory .set(address, value) .map(|memory| IntcodeProgram { memory, ..self.clone() }) .unwrap_or(self.error()) } fn with_input_consumed(&self) -> IntcodeProgram { self.input .drop_first() .map(|input| IntcodeProgram { input, ..self.clone() }) .unwrap_or(self.error()) } fn with_output(&self, print: Intcode) -> IntcodeProgram { IntcodeProgram { output: self.output.push_back(print), ..self.clone() } } fn output_into_result(&self) -> Result, IntcodeProgramError> { if self.error { Err(IntcodeProgramError) } else { Ok(self.output.clone()) } } fn memory_0_into_result(&self) -> Result, IntcodeProgramError> { if self.error { Err(IntcodeProgramError) } else { Ok(self.memory.get(0).cloned()) } } fn next(&self) -> IntcodeProgram { //eprintln!("{:?}", self); self.memory .get(self.instruction_pointer) .map(|&opcode| match opcode % 100 { 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(), _ => self.error(), }) .unwrap_or(self.error()) } fn add(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (Some(in1), Some(in2), Some(out)) => self .with_instruction_pointer_offset(4) .with_memory_set(out as usize, in1 + in2), _ => self.error(), } } fn multiply(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (Some(in1), Some(in2), Some(out)) => self .with_instruction_pointer_offset(4) .with_memory_set(out as usize, in1 * in2), _ => self.error(), } } fn input(&self, _mode: Intcode) -> IntcodeProgram { match (self.input.first().cloned(), self.get_immediate(1)) { (Some(input), Some(out)) => self .with_instruction_pointer_offset(2) .with_memory_set(out as usize, input) .with_input_consumed(), (None, Some(_out)) => self.await_input(), _ => self.error(), } } fn output(&self, mode: Intcode) -> IntcodeProgram { match self.get(1, mode) { Some(print) => self.with_instruction_pointer_offset(2).with_output(print), _ => self.error(), } } fn jump_if_true(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode)) { (Some(pred), Some(to)) if pred != 0 => self.with_instruction_pointer(to as usize), (Some(_), Some(_)) => self.with_instruction_pointer_offset(3), _ => self.error(), } } fn jump_if_false(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode)) { (Some(pred), Some(to)) if pred == 0 => self.with_instruction_pointer(to as usize), (Some(_), Some(_)) => self.with_instruction_pointer_offset(3), _ => self.error(), } } fn less_than(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (Some(in1), Some(in2), Some(out)) => self .with_instruction_pointer_offset(4) .with_memory_set(out as usize, if in1 < in2 { 1 } else { 0 }), _ => self.error(), } } fn equals(&self, mode: Intcode) -> IntcodeProgram { match (self.get(1, mode), self.get(2, mode), self.get_immediate(3)) { (Some(in1), Some(in2), Some(out)) => self .with_instruction_pointer_offset(4) .with_memory_set(out as usize, if in1 == in2 { 1 } else { 0 }), _ => self.error(), } } 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) -> IntcodeProgram { IntcodeProgram { halted: true, error: true, ..self.clone() } } fn get(&self, pointer_offset: usize, mode: Intcode) -> Option { match mode / (10 as Intcode).pow(pointer_offset as u32 + 1) % 10 { 0 => self.get_position(pointer_offset), 1 => self.get_immediate(pointer_offset), _ => None, } } fn get_immediate(&self, pointer_offset: usize) -> Option { self.memory .get(self.instruction_pointer + pointer_offset) .cloned() } fn get_position(&self, pointer_offset: usize) -> Option { self.get_immediate(pointer_offset) .and_then(|r| self.memory.get(r as usize)) .cloned() } } #[derive(Debug, PartialEq)] pub struct IntcodeProgramError; impl fmt::Display for IntcodeProgramError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Unknown error") } } impl std::error::Error for IntcodeProgramError {}