1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! # [Day 23: Opening the Turing Lock](https://adventofcode.com/2015/day/23)
//!
//! Little Jane Marie just got her very first computer for Christmas from some unknown benefactor.
//! It comes with instructions and an example program, but the computer itself seems to be
//! malfunctioning. She's curious what the program does, and would like you to help her run it.
//!
//! The manual explains that the computer supports two
//! [registers](https://en.wikipedia.org/wiki/Processor_register) and six
//! [instructions](https://en.wikipedia.org/wiki/Instruction_set) (truly, it goes on to remind the
//! reader, a state-of-the-art technology). The registers are named `a` and `b`, can hold any
//! [non-negative integer](https://en.wikipedia.org/wiki/Natural_number), and begin with a value of
//! `0`. The instructions are as follows:
//!
//! - `hlf r` sets register `r` to half its current value, then continues with the next instruction.
//! - `tpl r` sets register `r` to triple its current value, then continues with the next
//!     instruction.
//! - `inc r` increments register `r`, adding `1` to it, then continues with the next instruction.
//! - `jmp offset` is a jump; it continues with the instruction `offset` away relative to itself.
//! - `jie r, offset` is like `jmp`, but only jumps if register `r` is even ("jump if even").
//! - `jio r, offset` is like `jmp`, but only jumps if register `r` is `1` ("jump if one", not odd).
//!
//! All three jump instructions work with an offset relative to that instruction.
//! The offset is always written with a prefix `+` or `-` to indicate the direction of the jump
//! (forward or backward, respectively). For example, `jmp +1` would simply continue with the next
//! instruction, while `jmp +0` would continuously jump back to itself forever.
//!
//! The program exits when it tries to run an instruction beyond the ones defined.
//!
//! For example, this program sets `a` to `2`, because the `jio` instruction causes it to
//! skip the `tpl` instruction:
//!
//! ```plain
//! inc a
//! jio a, +2
//! tpl a
//! inc a
//! ```
//!
//! **What is the value in register `b` when the program in your puzzle input is finished executing?**
//!
//! # Part Two
//!
//! The unknown benefactor is very thankful for releasi-- er, helping little Jane Marie with her
//! computer. Definitely not to distract you, **what is the value in register b after the program is
//! finished executing if register a starts as 1 instead?**

use std::collections::HashMap;

#[aoc_generator(day23)]
fn parse_input(input: &str) -> Vec<Instruction> {
    input
        .lines()
        .map(|line| {
            let command = &line[0..3];
            let args: Vec<&str> = line[4..line.len()].split(", ").collect();

            match command {
                "hlf" => Instruction::Hlf(args[0].to_owned()),
                "tpl" => Instruction::Tpl(args[0].to_owned()),
                "inc" => Instruction::Inc(args[0].to_owned()),
                "jmp" => Instruction::Jmp(args[0].parse().unwrap()),
                "jie" => Instruction::Jie(args[0].to_owned(), args[1].parse().unwrap()),
                "jio" => Instruction::Jio(args[0].to_owned(), args[1].parse().unwrap()),
                _ => {
                    panic!("unknown instruction: {}", command)
                }
            }
        })
        .collect()
}

enum Instruction {
    Hlf(String),
    Tpl(String),
    Inc(String),
    Jmp(i64),
    Jie(String, i64),
    Jio(String, i64),
}

/// Part 1: What is the value in register `b` when the program in your puzzle input is finished executing?
#[aoc(day23, part1)]
fn part1(input: &[Instruction]) -> i64 {
    let registers = exec(input, 0);
    registers["b"]
}

fn exec(code: &[Instruction], initial_a: i64) -> HashMap<String, i64> {
    let mut registers = HashMap::new();
    registers.insert("a".to_owned(), initial_a);
    registers.insert("b".to_owned(), 0);

    let mut pointer: isize = 0;

    loop {
        if pointer < 0 || pointer >= code.len() as isize {
            break;
        }
        let instr = &code[pointer as usize];
        match instr {
            // `hlf r` sets register `r` to half its current value,
            // then continues with the next instruction.
            Instruction::Hlf(r) => {
                if let Some(r) = registers.get_mut(r) {
                    *r /= 2;
                }
                pointer += 1;
            }
            // `tpl r` sets register `r` to triple its current value,
            // then continues with the next instruction.
            Instruction::Tpl(r) => {
                if let Some(r) = registers.get_mut(r) {
                    *r *= 3;
                }
                pointer += 1;
            }
            // `inc r` increments register `r`, adding `1` to it,
            // then continues with the next instruction.
            Instruction::Inc(r) => {
                if let Some(r) = registers.get_mut(r) {
                    *r += 1;
                }
                pointer += 1;
            }
            // `jmp offset` is a jump; it continues with the instruction `offset` away relative to itself.
            Instruction::Jmp(offset) => {
                pointer += *offset as isize;
            }
            // `jie r, offset` is like `jmp`, but only jumps if register `r` is even ("jump if even").
            Instruction::Jie(r, offset) => {
                if let Some(r) = registers.get_mut(r) {
                    if *r % 2 == 0 {
                        pointer += *offset as isize;
                    } else {
                        pointer += 1;
                    }
                }
            }
            // `jio r, offset` is like `jmp`, but only jumps if register `r` is `1` ("jump if one", not odd).
            Instruction::Jio(r, offset) => {
                if let Some(r) = registers.get_mut(r) {
                    if *r == 1 {
                        pointer += *offset as isize;
                    } else {
                        pointer += 1;
                    }
                }
            }
        }
    }

    registers
}

/// Part 2: what is the value in register b after the program is finished executing if
/// register a starts as 1 instead?
#[aoc(day23, part2)]
fn part2(input: &[Instruction]) -> i64 {
    let registers = exec(input, 1);
    registers["b"]
}

#[cfg(test)]
mod tests {
    use super::*;

    const EXAMPLE: &str = "inc a
jio a, +2
tpl a
inc a";

    #[test]
    fn part1_examples() {
        // For example, this program sets `a` to `2`, because the `jio` instruction causes it to
        // skip the `tpl` instructionlet registers = part1(&parse_input(EXAMPLE));
        let registers = exec(&parse_input(EXAMPLE), 0);
        assert_eq!(2, registers["a"]);
    }
}