DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 5: Sunny with a Chance of Asteroids

Collapse
 
rpalo profile image
Ryan Palo

OK, I'm really, really happy with how my refactoring on Day 2 came out. Even the addition of the input modes fit into place in nice, neat, decoupled chunks. And then it was a matter of letting my interpreter know about all of the new opcodes.

Note: I was very much not expecting my Day 2 setup to hold up this well. I think I got exceptionally lucky at how today's puzzle was structured. I'm fully expecting to run into a future IntCode puzzle in the coming days that blows my architecture out of the water and makes me start over.

My actual Day 5 module is not very impressive:

/// Day 5: Sunny with a Chance of Asteroids
/// 
/// Test out the Thermal Environment Supervision Terminal
/// in the Intcode Interpreter

use std::fs;
use crate::intcode;

/// 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/day5.txt").unwrap();
    let cleaned = text.trim();
    cleaned.split(",").map( |c| c.parse().unwrap()).collect()
}

pub fn run() {
    let ints = parse_input();
    let mut interpreter = intcode::IntcodeInterpreter::new(ints);
    interpreter.run();
}

Here's where all the work gets done. Also, side note, as a happy fallout of how Rust's stdin handles itself, I can either enter values or pipe them in one-per-line, if future input gets more complicated :)

use std::io;
use std::fs;

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

impl IntcodeInterpreter {
    pub fn new(memory: Vec<isize>) -> Self {
        Self { memory, ip: 0}
    }

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

    /// Reads from a memory address
    pub fn get(&self, position: usize) -> isize {
        self.memory[position]
    }

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

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

    /// 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(),
                99 => return,
                _ => 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(&self, offset: usize) -> isize {
        let new_index = (self.ip + offset) % self.memory.len();
        let mode = (self.memory[self.ip] / 10isize.pow(1 + offset as u32)) % 2;
        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 new_index = (self.ip + offset) % self.memory.len();
        self.memory[new_index] as usize
    }

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

    /// 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);

        self.set(out, Self::read_stdin());

        self.step(2);
    }

    /// Read [1] and send it to STDOUT
    fn op4(&mut self) {
        Self::write_stdout(self.arg(1));

        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);
    }
}