DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 9: Sensor Boost

Collapse
 
rpalo profile image
Ryan Palo

Had a rough time hunting down some issues that caused all of the available test cases to pass but failed my puzzle input. Turns out I should have been using "x % 10" to get the one's digit, not "x % 3" like I had.

Also, I feel like the specification for how opcode 3 (read input) works with this new relative parameter mode wasn't made super clear. But it worked out in the end. Here's my rust solution:

/// Day 9: Sensor Boost
/// 
/// Boost sensors with relative Intcode parameters.

use std::fs;

use crate::intcode::IntcodeInterpreter;

/// Parses the input.  Expects a single line of integers separated by
/// commas
fn parse_input() -> Vec<isize> {
    let text: String = fs::read_to_string("data/day9.txt").unwrap();
    let cleaned = text.trim();
    cleaned.split(",").map( |c| c.parse().unwrap()).collect()
}

pub fn run() {
    let numbers = parse_input();
    let mut a = IntcodeInterpreter::new(numbers);
    a.push_input(2);
    a.run();
    println!("Result: {}", a.shift_output());
}

And my Intcode Interpreter:

use std::io;
use std::collections::HashMap;

/// An Intcode Interpreter is a simple virtual machine that uses opcodes
/// to modify its internal memory
pub struct IntcodeInterpreter {
    memory: HashMap<usize, isize>,
    in_buffer: Vec<isize>,
    out_buffer: Vec<isize>,
    ip: usize,
    relative_base: isize,
}

impl IntcodeInterpreter {
    pub fn new(numbers: Vec<isize>) -> Self {
        let mut memory: HashMap<usize, isize> = HashMap::new();
        for (ind, num) in numbers.iter().enumerate() {
            memory.insert(ind, *num);
        }
        Self {
            memory,
            in_buffer: Vec::new(),
            out_buffer: Vec::new(),
            ip: 0,
            relative_base: 0,
        }
    }

    /// Sets a memory address to a value
    pub fn set(&mut self, position: usize, value: isize) {
        self.memory.insert(position, value);
    }

    /// Reads from a memory address
    pub fn get(&mut self, position: usize) -> isize {
        *self.memory.entry(position).or_default()
    }

    /// Shows the memory for debugging
    pub fn print(&self) {
        println!("{:?}", self.memory);
    }

    /// Get the current instruction
    pub fn current_instruction(&mut self) -> isize {
        self.get(self.ip) % 100
    }

    /// Add a value to the input queue
    pub fn push_input(&mut self, value: isize) {
        self.in_buffer.push(value);
        if self.current_instruction() == 98 {
            self.memory.insert(self.ip, self.memory[&self.ip] - 95);
            // Return the instruction to
            // a 3 while maintaining
            // argtype values
        }
    }

    pub fn count_output(&self) -> usize {
        self.out_buffer.len()
    }

    /// Pop a value off the output queue
    pub fn shift_output(&mut self) -> isize {
        if self.out_buffer.is_empty() {
            panic!("Shift from empty out buffer!");
        }
        self.out_buffer.remove(0)
    }

    /// Runs the program in memory until the stopcode (99) is reached
    /// 
    /// All new ops should have their own method.
    /// They take no rust args, but read in args as needed and
    /// shift the instruction pointer when they're done.
    /// Steps should be the number of args used + 1 for the opcode
    pub fn run(&mut self) {
        loop {
            match self.current_instruction() {
                1 => self.op1(),
                2 => self.op2(),
                3 => self.op3(),
                4 => self.op4(),
                5 => self.op5(),
                6 => self.op6(),
                7 => self.op7(),
                8 => self.op8(),
                9 => self.op9(),
                98 => return,  // Halt until receiving input
                99 => return,  // End of program
                _ => panic!("Unrecognized opcode {}.", self.get(self.ip)),
            };
        }
    }

    /// Reads a number from STDIN
    fn read_stdin() -> isize {
        let mut buffer = String::new();
        io::stdin().read_line(&mut buffer).expect("STDIN read failed.");
        buffer.trim().parse::<isize>().unwrap()
    }

    /// Write a number to STDOUT
    fn write_stdout(number: isize) {
        println!("{}", number);
    }

    /// Process the parameter mode and provide the value given
    /// as a parameter
    fn arg(&mut self, offset: usize) -> isize {
        let new_index = self.ip + offset;
        let mode = (self.memory[&self.ip] / 10isize.pow(1 + offset as u32)) % 10;
        if mode == 2 {
            let addr = self.relative_base + self.memory[&new_index];
            let val = self.get(addr as usize);
            self.get(addr as usize)
        } else if mode == 1 {
            self.memory[&new_index]
        } else if mode == 0 {
            self.get(self.memory[&new_index] as usize)
        } else {
            panic!("Unknown parameter mode {}", mode);
        }
    }

    /// Returns the address to write output to
    fn output_address(&self, offset: usize) -> usize {
        let mode = (self.memory[&self.ip] / 10isize.pow(1 + offset as u32)) % 10;
        let new_index = self.ip + offset;
        if mode == 0 {
            self.memory[&new_index] as usize
        } else if mode == 2 {
            (self.relative_base + self.memory[&new_index]) as usize
        } else {
            panic!("Output address with weird mode");
        }
    }

    /// Steps the IP forward "count" steps, wrapping if needed
    fn step(&mut self, count: usize) {
        self.ip = (self.ip + count) % self.memory.len();
    }

    /// Causes the interpreter to block until receiving input
    fn halt_on_input(&mut self) {
        // Update the last two digits from 03 to 98, but maintain
        // the argtype values
        self.memory.insert(self.ip, self.memory[&self.ip] + 95); 
    }

    /// Add [1] + [2], store in [3]
    fn op1(&mut self) {
        let in1 = self.arg(1);
        let in2 = self.arg(2);
        let out = self.output_address(3);

        self.set(out, in1 + in2);

        self.step(4);
    }

    /// Mult [1] * [2], store in [3]
    fn op2(&mut self) {
        let in1 = self.arg(1);
        let in2 = self.arg(2);
        let out = self.output_address(3);

        self.set(out, in1 * in2);

        self.step(4);
    }

    /// Read one value from STDIN and store it in [1]
    fn op3(&mut self) {
        let out = self.output_address(1);

        let val: isize;
        if self.in_buffer.is_empty() {
            self.halt_on_input();
            return;
        } else {
            val = self.in_buffer.remove(0);
        }
        self.set(out, val);

        self.step(2);
    }

    /// Read [1] and send it to STDOUT
    fn op4(&mut self) {
        let arg = self.arg(1);
        self.out_buffer.push(arg);

        self.step(2);
    }

    /// If [1] != 0, set IP -> [2], else nothing
    fn op5(&mut self) {
        if self.arg(1) != 0 {
            self.ip = self.arg(2) as usize;
        } else {
            self.step(3);
        }
    }

    /// if [1] == 0, set IP -> [2], else nothing
    fn op6(&mut self) {
        if self.arg(1) == 0 {
            self.ip = self.arg(2) as usize;
        } else {
            self.step(3);
        }
    }

    /// if [1] < [2], set [3] to 1, else 0
    fn op7(&mut self) {
        let out = self.output_address(3);

        if self.arg(1) < self.arg(2) {
            self.set(out, 1);
        } else {
            self.set(out, 0);
        }

        self.step(4);
    }

    /// if [1] == [2], set [3] to 1, else 0
    fn op8(&mut self) {
        let out = self.output_address(3);

        if self.arg(1) == self.arg(2) {
            self.set(out, 1);
        } else {
            self.set(out, 0);
        }

        self.step(4);
    }

    /// Shift relative base by [1]
    fn op9(&mut self) {
        self.relative_base += self.arg(1);
        self.step(2);
    }
}