Advent of Code 2019 - Day 5 (in Rust!)
Tags: adventofcode, rust
Published: 2020-01-05 20:24:30

# Introduction

The thought of revisiting my Clojure version of the Intcode "computer" horrified me. I will be continuing with the Advent of Code, but personal time is limited, so I'll be taking my sweet time. Since I am not competing on time, and my prior implementation was painful to revise, I wanted to write it again.

Time to break out Rust and only rely on the Rust book (with help from the wider internets for some things like user input).



# Day 5.1

Rehashing my description from the Clojure version:
Part 1 of Day 5 requires that the Intcode computer from Day 2 be updated to accommodate some new features:
1. The Opcode from Day 2 now comprises 5 digits. This is true even if only one digit is provided - you must infer the rest to be 0.
2. The Opcodes from Day 2 continue to apply, but with some additions that break from the [instruction, parameter1, parameter2, parameter3] setup:
    1. Opcode 3 means take a value from human input (or simulate it) and write it to the address provided in the parameter x, where (as we will see in a moment) x is either the address to write it, or a pointer to the address to write it: [3, x]
    2. Opcode 4 means read the value from an address and print it to the screen. It simulates "diagnostic codes" being printed out. This is like the reverse of Opcode 3.
3. The Opcode will provide parameter "modes". The modes are binary. For each Opcode, there are between 1 and 3 parameters, thus there are between 1 and 3 modes. In Day 2 the values in the parameters are always pointers to somewhere else in the Intcode. If a parameter mode is 0, that behavior is unchanged. If the mode is 1, use the literal content of the parameter.
   1. For Opcode of 1 or 2, you either sum or multiply the literal content of par 1 and par 2
   2. For Opcode of 3 or 4 (or any other write to/read from operation), a 0 means read/write to the location defined in the parameter (i.e. a pointer), while a 1 means read/write to the parameter itself. Specifically:
       1. [4, 4, 99, 2, **3**] results in printing 3
       2. [104, **4**, 99, 2, 3] results in printing 4
4. The Opcode will be provided in reverse. Example: 101103 - in this case we can break it into ABCDE:
   1. A is the mode for parameter 3
   2. B is the mode for parameter 2
   3. C is the mode for parameter 1
   4. DE is the Opcode, now provided as two digits
5. The new two-digit Opcode is really a single digit Opcode with an unnecessary prefix, so in 01, only the 1 has value.

### Now, the Rust

So, Rust requires some prep. What types will I need to make? 
For me, I needed two custom types:
1. The Intcode type, containing the Intcode as supplied and maintaining it through edits, the opcode pointer that tells the "computer" where the current opcode is located, and a boolean that tells the `while` loop we'll be using whether we found a `stop` opcode or not.
2. An ExtendedOpcode type that contains the components of the new, longer opcodes.

```rust
#[derive(Debug, Clone)]
pub struct Intcode {
    intcode: Vec<isize>,
    opcode_pointer: usize,
    continue_loop: bool,
}

#[derive(Debug, Clone)]
pub struct ExtendedOpcode {
    opcode: Opcode,
    mode_1: Modes,
    mode_2: Modes,
    mode_3: Modes,
}
```

Now, what the heck are the `Opcode` and `Modes` types?
Those are a pair of enums I used to make my code more readable. Matching integers is entirely possible, but the two enums provide some much more human-friendly labelling:

```rust
#[derive(Debug, Clone, PartialEq)]
enum Opcode {
    Stop,
    Add,
    Multiply,
    Input,
    Output,
    JumpIfTrue,
    JumpIfFalse,
    LessThan,
    Equals,
    OtherValue,
}


#[derive(Debug, Clone, PartialEq)]
enum Modes {
    Position,
    Immediate,
}
```

The remaining setup before we start writing the actual functions that perform the "computer" work are:
1. Implement `PartialEq` for the new `ExtendedOpcode` type (not used in the code itself, but needed for testing. `assert!` needs `PartialEq` for any types it is comparing).
2. A `new` function for the `Intcode` type.
3. A `new` function for the `ExtendedOpcode` type.

```rust
impl PartialEq for ExtendedOpcode {
    fn eq(&self, comparator: &Self) -> bool {
        self.opcode == comparator.opcode
        &&
        self.mode_1 == comparator.mode_1
        &&
        self.mode_2 == comparator.mode_2
        &&
        self.mode_3 == comparator.mode_3
    }
}

impl ExtendedOpcode {
    fn new(opcode: Opcode) -> Self {
        Self {
            opcode: opcode,
            mode_1: Modes::Position,
            mode_2: Modes::Position,
            mode_3: Modes::Position,
        }
    }
}


impl Intcode {
    fn new(intcode_vec: Vec<isize>) -> Self {
        Self {
            intcode: intcode_vec,
            opcode_pointer: 0,
            continue_loop: true,
        }
    }
}
```

In this next section you will find the "computer" logic. Here's the breakdown:

1. `work_on_intcode` is the "command and control" function, maintaining a `while` loop until a `99` opcode is found (which sets the `self.continue_loop` to `false`, exiting the loop). It calls the `get_opcode_method` function and uses the output of that function to decide which subsequent action to take.
2. `get_opcode_method` extracts the long-form opcode, and calls the `split_opcodes` function which splits the opcode into pieces, reverses the order, and converts back to integers. Then, adjusting the approach based on the length of the opcode, a new `ExtendedOpcode` is created and the actual opcode value is put into a `match` to apply the correct `Opcode` enum type.
    1. The splitting of the long-form opcode means a `99` appears as `[9, 9]`, so to protect me against future `9`-prefixed opcodes, I put a sub-`match` to check that the second value is also a `9`, ensuring I have found a `99` opcode.
3. Now, with the opcode known, I have the day 5.1 functions in place (all functions are named, but they are placeholders for now):
    1. `add_and_place` first determines the values to work with, depending on whether the mode for each is `Position` or `Immediate`, then adds the two integers and writes them to the location of the third integer. It moves the `opcode_pointer` forward 4 places.
    2. `multiply_and_place` is the same as the `add` function, but it multiplies.
    3. `take_input` makes use of the [text_io](https://crates.io/crates/text_io) crate. Add `#[macro_use] extern crate text_io;` to the `main.rs` or `lib.rs` file for your project and then use `let user_input: isize = read!();` or similar. The macro will attempt to coerce the type. The rest of the function, similar to the previous two, determines where it is writing to and puts the user input in that location. It increments the `opcode_pointer` by two as there are only two values associated.
    4. `print_output` effectively does the opposite of three; it reads from a location defined by the opcode modes, and then prints to screen. It then moves the `opcode_pointer` 2 places.


```rust
impl Intcode {
    fn split_opcodes(&self, opcode_full: isize) -> Vec<isize> {
        let unsplit_opcode = opcode_full.to_string();
        let split_opcode = unsplit_opcode.trim().split_terminator("").skip(1).collect::<Vec<&str>>(); //split_terminator("").skip(1) splits the string, but prevents the resulting vector having "" at start and end
        //println!("{:?}", split_opcode);
        let rev_split_opcode = split_opcode.iter().rev().map(|o| o.parse::<isize>().unwrap()).collect::<Vec<isize>>();

        rev_split_opcode
    }


    fn get_opcode_method(&self) -> ExtendedOpcode {
        let opcode_full = self.intcode[self.opcode_pointer];
        let opcode_split = self.split_opcodes(opcode_full);
        //println!("{:?}", opcode_split);
        let mut extended_opcode: ExtendedOpcode = ExtendedOpcode::new(Opcode::OtherValue);

        if opcode_split.len() > 2 && opcode_split[2] == 1 {
            extended_opcode.mode_1 = Modes::Immediate;   
        }

        if opcode_split.len() > 3 && opcode_split[3] == 1 {
            extended_opcode.mode_2 = Modes::Immediate;
        }

        if opcode_split.len() > 4 && opcode_split[4] == 1 {
            extended_opcode.mode_3 = Modes::Immediate;
        }

        match opcode_split[0] {
            9 => match opcode_split[1] {
                9 => extended_opcode.opcode = Opcode::Stop,
                _ => extended_opcode.opcode = Opcode::OtherValue,
            }
            1 => extended_opcode.opcode = Opcode::Add,
            2 => extended_opcode.opcode = Opcode::Multiply,
            3 => extended_opcode.opcode = Opcode::Input,
            4 => extended_opcode.opcode = Opcode::Output,
            5 => extended_opcode.opcode = Opcode::JumpIfTrue,
            6 => extended_opcode.opcode = Opcode::JumpIfFalse,
            7 => extended_opcode.opcode = Opcode::LessThan,
            8 => extended_opcode.opcode = Opcode::Equals,
            _ => extended_opcode.opcode = Opcode::OtherValue,
        }
        //println!("{:?}", self.intcode);
        extended_opcode
    }


    fn add_and_place(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        let write_to = match extended_opcode.mode_3 {
            Modes::Position => self.intcode[ self.opcode_pointer + 3 ] as usize,
            Modes::Immediate => self.opcode_pointer + 3 as usize,
        };

        self.intcode[write_to] = value_1 + value_2;
        self.opcode_pointer += 4;
    }


    fn multiply_and_place(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        let write_to = match extended_opcode.mode_3 {
            Modes::Position => self.intcode[ self.opcode_pointer + 3 ] as usize,
            Modes::Immediate => self.opcode_pointer + 3 as usize,
        };

        self.intcode[write_to] = value_1 * value_2;
        self.opcode_pointer += 4;
    }


    fn take_input(&mut self, extended_opcode: ExtendedOpcode) {
        let user_input: isize = read!();

        let write_to = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.opcode_pointer + 1 ] as usize,
            Modes::Immediate => self.opcode_pointer + 1 as usize,
        };
        self.intcode[write_to] = user_input;
        self.opcode_pointer += 2;
    }

    fn print_output(&mut self, extended_opcode: ExtendedOpcode) {
        let read_from = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.opcode_pointer + 1 ] as usize,
            Modes::Immediate => self.opcode_pointer + 1 as usize,
        };
        let val_to_output = self.intcode[read_from];
        println!("Diagnostic code: {:?}", val_to_output);
        self.opcode_pointer += 2;
    }

    fn jump_if_true(&self, _extended_opcode: ExtendedOpcode) {
        println!("I do nothing, yet.");
    }

    fn jump_if_false(&self, _extended_opcode: ExtendedOpcode) {
        println!("I do nothing, yet.");
    }

    fn less_than(&self, _extended_opcode: ExtendedOpcode) {
        println!("I do nothing, yet.");
    }

    fn equals(&self, _extended_opcode: ExtendedOpcode) {
        println!("I do nothing, yet.");
    }

    fn work_on_intcode(&mut self) -> Result<(), &str> {
        while self.continue_loop {

            let extended_opcode = self.get_opcode_method();

            match extended_opcode.opcode {
                Opcode::Stop => { 
                    println!("Finished.");
                    self.continue_loop = false;
                },
                Opcode::Add => self.add_and_place(extended_opcode),
                Opcode::Multiply => self.multiply_and_place(extended_opcode),
                Opcode::Input => self.take_input(extended_opcode),
                Opcode::Output => self.print_output(extended_opcode),
                Opcode::JumpIfTrue => self.jump_if_true(extended_opcode),
                Opcode::JumpIfFalse => self.jump_if_false(extended_opcode),
                Opcode::LessThan => self.less_than(extended_opcode),
                Opcode::Equals => self.equals(extended_opcode),
                Opcode::OtherValue => return Err("A value outside Opcode range was found."),
                _ => return Err("No idea where this came from..."),
            }
        }

        Ok(())
    }

}
```



With all of this in place, running it is pretty simple. My convenience `file_open` function just pulls in the contents of a file as a string and returns a `Result` containing (hopefully) the string, hence the `unwrap` to extract it.

The splitting of the string is a little long because it must be split, the result collected into a vector, that vector then iterated over to parse each `&str` into an integer of type `isize`. The parse returns a result, so it is `unwrap`ped and the integers are then collected into another vector.

```rust
pub fn day_five() {
    let file_input = file_open();
    let input_string = file_input.unwrap();
    let int_vec = input_string.split(",").collect::<Vec<&str>>().iter().map(|o| o.parse::<isize>().unwrap()).collect::<Vec<isize>>();
    let mut intcode_obj = Intcode::new(int_vec);

    let opcode_results = intcode_obj.work_on_intcode();
}
```

# Day 5.2

Adding the functions was simple due to the way I set up my types. Just add the functions and it just works - almost.
The `opcode_pointer` in `jump_if_true` and `jump_if_false` has to be coerced to a `usize` value because it is being used to access the vector by index. Rust does not allow negative index access, so an `isize` type would be problematic. A simple `as usize` makes the change.

```rust
fn jump_if_true(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        if value_1 != 0 {
            self.opcode_pointer = value_2 as usize;
        } else {
            self.opcode_pointer += 3;
        }
    }

    fn jump_if_false(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        if value_1 == 0 {
            self.opcode_pointer = value_2 as usize;
        } else {
            self.opcode_pointer += 3;
        }
    }

    fn less_than(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        let write_to = match extended_opcode.mode_3 {
            Modes::Position => self.intcode[ self.opcode_pointer + 3 ] as usize,
            Modes::Immediate => self.opcode_pointer + 3 as usize,
        };

        if value_1 < value_2 {
            self.intcode[write_to] = 1;
        } else {
            self.intcode[write_to] = 0;
        }

        self.opcode_pointer += 4;
    }

    fn equals(&mut self, extended_opcode: ExtendedOpcode) {
        let value_1 = match extended_opcode.mode_1 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 1 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 1 ],
        };

        let value_2 = match extended_opcode.mode_2 {
            Modes::Position => self.intcode[ self.intcode[ self.opcode_pointer + 2 ] as usize ],
            Modes::Immediate => self.intcode[ self.opcode_pointer + 2 ],
        };

        let write_to = match extended_opcode.mode_3 {
            Modes::Position => self.intcode[ self.opcode_pointer + 3 ] as usize,
            Modes::Immediate => self.opcode_pointer + 3 as usize,
        };

        if value_1 == value_2 {
            self.intcode[write_to] = 1;
        } else {
            self.intcode[write_to] = 0;
        }

        self.opcode_pointer += 4;
    }
```