diff --git a/2020/day08_handheld_halting/Cargo.toml b/2020/day08_handheld_halting/Cargo.toml new file mode 100644 index 0000000..86dfd9f --- /dev/null +++ b/2020/day08_handheld_halting/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day08_handheld_halting" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/2020/day08_handheld_halting/challenge.txt b/2020/day08_handheld_halting/challenge.txt new file mode 100644 index 0000000..a222e2f --- /dev/null +++ b/2020/day08_handheld_halting/challenge.txt @@ -0,0 +1,104 @@ +Your flight to the major airline hub reaches cruising altitude without incident. While you consider checking the in-flight menu for one of those drinks that come with a little umbrella, you are interrupted by the kid sitting next to you. + +Their [handheld game console](https://en.wikipedia.org/wiki/Handheld_game_console) won't turn on! They ask if you can take a look. + +You narrow the problem down to a strange *infinite loop* in the boot code (your puzzle input) of the device. You should be able to fix it, but first you need to be able to run the code in isolation. + +The boot code is represented as a text file with one *instruction* per line of text. Each instruction consists of an *operation* (`acc`, `jmp`, or `nop`) and an *argument* (a signed number like `+4` or `-20`). + +* `acc` increases or decreases a single global value called the *accumulator* by the value given in the argument. For example, `acc +7` would increase the accumulator by 7. The accumulator starts at `0`. After an `acc` instruction, the instruction immediately below it is executed next. +* `jmp` *jumps* to a new instruction relative to itself. The next instruction to execute is found using the argument as an *offset* from the `jmp` instruction; for example, `jmp +2` would skip the next instruction, `jmp +1` would continue to the instruction immediately below it, and `jmp -20` would cause the instruction 20 lines above to be executed next. +* `nop` stands for *No OPeration* - it does nothing. The instruction immediately below it is executed next. + +For example, consider the following program: + +``` +nop +0 +acc +1 +jmp +4 +acc +3 +jmp -3 +acc -99 +acc +1 +jmp -4 +acc +6 + +``` + +These instructions are visited in this order: + +``` +nop +0 | 1 +acc +1 | 2, 8(!) +jmp +4 | 3 +acc +3 | 6 +jmp -3 | 7 +acc -99 | +acc +1 | 4 +jmp -4 | 5 +acc +6 | + +``` + +First, the `nop +0` does nothing. Then, the accumulator is increased from 0 to 1 (`acc +1`) and `jmp +4` sets the next instruction to the other `acc +1` near the bottom. After it increases the accumulator from 1 to 2, `jmp -4` executes, setting the next instruction to the only `acc +3`. It sets the accumulator to 5, and `jmp -3` causes the program to continue back at the first `acc +1`. + +This is an *infinite loop*: with this sequence of jumps, the program will run forever. The moment the program tries to run any instruction a second time, you know it will never terminate. + +Immediately *before* the program would run an instruction a second time, the value in the accumulator is *`5`*. + +Run your copy of the boot code. Immediately before any instruction is executed a second time, *what value is in the accumulator?* + +Your puzzle answer was `2080`. + +\--- Part Two --- +---------- + +After some careful analysis, you believe that *exactly one instruction is corrupted*. + +Somewhere in the program, *either* a `jmp` is supposed to be a `nop`, *or* a `nop` is supposed to be a `jmp`. (No `acc` instructions were harmed in the corruption of this boot code.) + +The program is supposed to terminate by *attempting to execute an instruction immediately after the last instruction in the file*. By changing exactly one `jmp` or `nop`, you can repair the boot code and make it terminate correctly. + +For example, consider the same program from above: + +``` +nop +0 +acc +1 +jmp +4 +acc +3 +jmp -3 +acc -99 +acc +1 +jmp -4 +acc +6 + +``` + +If you change the first instruction from `nop +0` to `jmp +0`, it would create a single-instruction infinite loop, never leaving that instruction. If you change almost any of the `jmp` instructions, the program will still eventually find another `jmp` instruction and loop forever. + +However, if you change the second-to-last instruction (from `jmp -4` to `nop -4`), the program terminates! The instructions are visited in this order: + +``` +nop +0 | 1 +acc +1 | 2 +jmp +4 | 3 +acc +3 | +jmp -3 | +acc -99 | +acc +1 | 4 +nop -4 | 5 +acc +6 | 6 + +``` + +After the last instruction (`acc +6`), the program terminates by attempting to run the instruction below the last instruction in the file. With this change, after the program terminates, the accumulator contains the value *`8`* (`acc +1`, `acc +1`, `acc +6`). + +Fix the program so that it terminates normally by changing exactly one `jmp` (to `nop`) or `nop` (to `jmp`). *What is the value of the accumulator after the program terminates?* + +Your puzzle answer was `2477`. + +Both parts of this puzzle are complete! They provide two gold stars: \*\* + +At this point, you should [return to your Advent calendar](/2020) and try another puzzle. + +If you still want to see it, you can [get your puzzle input](8/input). \ No newline at end of file diff --git a/2020/day08_handheld_halting/day08_handheld_halt.core b/2020/day08_handheld_halting/day08_handheld_halt.core new file mode 100644 index 0000000..a2efef3 Binary files /dev/null and b/2020/day08_handheld_halting/day08_handheld_halt.core differ diff --git a/2020/day08_handheld_halting/src/lib.rs b/2020/day08_handheld_halting/src/lib.rs new file mode 100644 index 0000000..a9f0120 --- /dev/null +++ b/2020/day08_handheld_halting/src/lib.rs @@ -0,0 +1,133 @@ +use core::fmt::Display; +use std::{num::ParseIntError, collections::HashSet}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + ParseIntError(std::num::ParseIntError), + LineMalformed(String), +} + +impl From for ParseError { + fn from(value: ParseIntError) -> Self { + Self::ParseIntError(value) + } +} + +impl Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"), + Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"), + } + } +} + +#[derive(Clone)] +enum Instruction { + Acc(isize), + Jmp(isize), + Nop(isize), +} + +impl TryFrom<&str> for Instruction { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + let parts: Vec<_> = value.split_whitespace().collect(); + if parts.len() == 2 { + let argument = if parts[1].starts_with('+') { + parts[1][1..].parse::()? + } else { + parts[1].parse::()? + }; + match parts[0] { + "acc" => Ok(Self::Acc(argument)), + "jmp" => Ok(Self::Jmp(argument)), + "nop" => Ok(Self::Nop(argument)), + _ => Err(Self::Error::LineMalformed(value.to_string())), + } + } else { + Err(Self::Error::LineMalformed(value.to_string())) + } + } +} + +#[derive(Default, Clone)] +struct Cpu { + program: Vec, + next_instr: usize, + accumulator: isize, + visited: HashSet, +} + +impl TryFrom<&str> for Cpu { + type Error = ParseError; + + fn try_from(value: &str) -> Result { + let program: Vec<_> = value.lines().map(Instruction::try_from).collect::, _>>()?; + Ok(Self { program, ..Default::default() }) + } +} + +impl Cpu { + fn run_until_loop(&mut self) -> Result { + match self.program[self.next_instr] { + Instruction::Acc(i) => { + self.accumulator += i; + self.next_instr += 1; + }, + Instruction::Jmp(p) if p >= 0 => self.next_instr += p.unsigned_abs(), + Instruction::Jmp(n) => self.next_instr -= n.unsigned_abs(), + Instruction::Nop(_) => self.next_instr += 1, + } + if self.visited.contains(&self.next_instr) { + Err(self.accumulator) + } else if self.next_instr == self.program.len() { + Ok(self.accumulator) + } else { + self.visited.insert(self.next_instr); + self.run_until_loop() + } + } +} + +pub fn run(input: &str) -> Result<(isize, isize), ParseError> { + let cpu = Cpu::try_from(input)?; + let mut cpu_1 = cpu.clone(); + let first = cpu_1.run_until_loop().err().unwrap(); + for i in 0..cpu.program.len() { + let new = match cpu.program[i] { + Instruction::Acc(_) => continue, + Instruction::Jmp(i) => Instruction::Nop(i), + Instruction::Nop(i) => Instruction::Jmp(i), + }; + let mut cpu_2 = cpu.clone(); + cpu_2.program[i] = new; + if let Ok(second) = cpu_2.run_until_loop() { + return Ok((first, second)); + } + } + panic!("No way found to break the infinite loop"); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::read_to_string; + + fn read_file(name: &str) -> String { + read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string() + } + + #[test] + fn test_sample() { + let sample_input = read_file("tests/sample_input"); + assert_eq!(run(&sample_input), Ok((5, 8))); + } + + #[test] + fn test_challenge() { + let challenge_input = read_file("tests/challenge_input"); + assert_eq!(run(&challenge_input), Ok((2080, 2477))); + } +} diff --git a/2020/day08_handheld_halting/tests/challenge_input b/2020/day08_handheld_halting/tests/challenge_input new file mode 100644 index 0000000..1134cb6 --- /dev/null +++ b/2020/day08_handheld_halting/tests/challenge_input @@ -0,0 +1,641 @@ +acc -8 +acc +19 +nop +178 +jmp +493 +acc +7 +jmp +267 +jmp +604 +acc +30 +acc +11 +acc -17 +nop +402 +jmp +81 +acc +20 +acc +19 +acc +36 +jmp +6 +acc +43 +acc +6 +acc +10 +nop +326 +jmp +228 +nop +371 +acc +49 +nop +140 +acc -11 +jmp +3 +nop +366 +jmp +74 +nop +229 +jmp +554 +acc +12 +acc +6 +jmp +163 +acc +43 +acc +23 +jmp +310 +acc -6 +nop +341 +acc +50 +acc +44 +jmp +378 +acc +28 +acc +29 +nop +76 +jmp +136 +nop +445 +acc +27 +acc -8 +acc +34 +jmp +199 +acc +39 +acc +25 +acc -14 +acc +1 +jmp +189 +acc +2 +acc -9 +jmp -7 +acc +28 +acc +28 +jmp +458 +jmp +1 +nop +299 +jmp +427 +acc +10 +acc +32 +jmp +340 +acc +26 +jmp +563 +jmp +1 +acc +9 +jmp +42 +acc +4 +jmp +468 +acc +1 +acc +3 +jmp +258 +acc +29 +acc +7 +acc -5 +jmp +288 +acc +22 +acc +32 +acc -6 +jmp +173 +acc +48 +acc +42 +acc +26 +jmp +380 +jmp +126 +acc +45 +jmp -27 +acc +50 +jmp +14 +jmp +472 +acc -19 +jmp +363 +acc +19 +acc -8 +acc +25 +jmp +450 +acc -7 +acc +27 +acc +44 +acc +17 +jmp +487 +jmp +89 +nop +216 +nop +345 +acc -1 +acc +37 +jmp +455 +jmp +294 +acc +20 +acc +38 +jmp +419 +acc +17 +acc +17 +jmp +125 +jmp +81 +acc +37 +acc -8 +acc +9 +acc +31 +jmp +218 +acc +24 +acc +28 +acc -4 +acc -12 +jmp -40 +acc +40 +nop +359 +nop +182 +nop +306 +jmp +296 +acc -8 +jmp +1 +nop +43 +acc -14 +jmp +239 +acc +13 +acc +10 +jmp +1 +jmp -36 +acc -16 +acc +2 +jmp +344 +jmp +442 +acc +35 +acc +2 +acc +27 +acc +17 +jmp -27 +nop +478 +acc +6 +acc +7 +jmp +454 +nop -145 +acc +20 +acc -6 +jmp +182 +nop +251 +jmp -37 +acc +26 +jmp +300 +acc +29 +acc +44 +acc +32 +nop +56 +jmp +31 +acc -16 +jmp +53 +acc -9 +jmp +84 +jmp +1 +nop +30 +acc -15 +jmp +262 +acc -19 +jmp +163 +jmp +441 +acc +27 +jmp +449 +acc +42 +acc +45 +acc +21 +acc +22 +jmp +338 +acc +24 +jmp +301 +acc +42 +acc +42 +acc +26 +jmp +348 +jmp +361 +acc -5 +acc -19 +acc +4 +jmp -117 +jmp +254 +jmp +1 +acc +47 +acc -3 +jmp +271 +jmp +388 +acc +2 +acc -17 +acc +37 +jmp -73 +acc +37 +acc +34 +jmp +1 +jmp -148 +jmp -56 +jmp +103 +acc -5 +acc +23 +acc +3 +jmp +405 +nop +255 +acc +14 +nop -41 +acc +12 +jmp +94 +acc +22 +acc +30 +jmp -107 +acc +12 +acc -2 +jmp +65 +acc +35 +acc -4 +jmp -174 +nop -159 +acc +47 +jmp -52 +acc +35 +jmp +73 +acc +1 +acc +19 +acc +35 +acc +15 +jmp -59 +jmp +312 +acc +20 +acc +25 +acc +45 +jmp -4 +acc -4 +nop -160 +acc -8 +acc +31 +jmp +166 +acc +20 +acc +16 +acc -1 +jmp +234 +acc +0 +jmp -45 +acc +47 +acc +17 +nop -187 +nop +206 +jmp +17 +acc +36 +acc +0 +acc +7 +jmp +263 +acc +32 +acc -6 +nop +35 +jmp -101 +acc +49 +nop -60 +jmp +118 +acc -1 +acc -7 +nop -94 +acc +21 +jmp +82 +nop +216 +acc +5 +nop -99 +jmp -47 +acc +31 +acc +2 +acc +26 +acc +27 +jmp -224 +acc +15 +acc +48 +jmp +220 +nop +152 +jmp -69 +acc +4 +acc +24 +jmp +200 +acc +14 +jmp +126 +acc +48 +acc +47 +acc +10 +jmp +26 +acc +16 +jmp -203 +acc +21 +jmp -158 +acc -15 +acc -13 +jmp -94 +jmp -136 +nop -247 +acc +16 +jmp -130 +acc +31 +jmp +115 +jmp -159 +acc +7 +acc +50 +jmp +52 +acc +22 +acc +26 +jmp +249 +acc -18 +jmp +1 +jmp -251 +nop +254 +jmp -127 +acc +37 +jmp -93 +nop +73 +acc +11 +acc +36 +jmp +277 +acc +29 +acc +16 +jmp -88 +nop +8 +acc +18 +acc +47 +acc -9 +jmp +184 +jmp -142 +acc +50 +jmp +287 +jmp -250 +jmp -296 +jmp -83 +acc +13 +acc +29 +acc +28 +acc +16 +jmp +40 +acc +33 +acc -13 +jmp +43 +nop +275 +acc +24 +nop -257 +nop -65 +jmp -112 +acc +4 +acc +38 +jmp -193 +jmp +1 +acc -18 +acc +15 +jmp -223 +acc -18 +jmp -55 +jmp -207 +acc -6 +jmp -215 +acc +16 +acc +44 +jmp +1 +acc +47 +jmp -35 +acc +47 +acc +47 +acc +35 +jmp +144 +jmp +1 +acc +45 +acc +25 +jmp -293 +acc +32 +jmp -381 +nop +65 +jmp +1 +acc +2 +jmp -74 +acc -13 +acc -9 +acc +4 +jmp -251 +jmp +1 +jmp +71 +acc -12 +acc +7 +acc +15 +jmp +11 +jmp -68 +acc +33 +jmp -330 +jmp +48 +acc -15 +acc -11 +jmp +97 +acc -9 +acc -10 +jmp +100 +acc +29 +acc +21 +jmp -134 +acc -18 +acc +38 +jmp +67 +jmp -12 +acc +27 +acc +26 +acc -8 +acc -2 +jmp -124 +jmp +165 +nop -245 +acc -16 +acc +25 +acc -19 +jmp -328 +nop -182 +acc -7 +acc +46 +jmp -250 +acc +45 +acc -7 +nop -256 +acc -2 +jmp +21 +acc +21 +acc +37 +jmp +156 +nop +32 +jmp -195 +nop -355 +acc -14 +nop -302 +acc +48 +jmp -407 +acc +50 +acc -9 +acc +47 +jmp -110 +acc +31 +acc +37 +acc +15 +jmp -162 +acc -14 +jmp -437 +acc +44 +jmp +1 +acc +24 +jmp -139 +jmp -362 +acc +40 +jmp -41 +acc +38 +jmp -231 +acc +31 +acc +23 +jmp +135 +acc -19 +acc +15 +jmp +148 +acc +16 +acc -18 +acc -3 +acc +1 +jmp -189 +acc -12 +acc -6 +acc -18 +nop -454 +jmp +83 +nop -190 +jmp -17 +acc -7 +acc +34 +acc -1 +jmp +94 +acc +42 +jmp +34 +nop -150 +nop +90 +nop -126 +jmp -161 +acc +5 +acc +11 +acc +20 +acc +38 +jmp -97 +acc +49 +acc +29 +acc +26 +jmp -36 +acc +4 +acc -14 +acc +30 +acc +42 +jmp -192 +jmp -336 +acc +34 +acc +31 +acc +2 +acc +33 +jmp +65 +acc +4 +jmp -459 +nop -399 +acc -6 +nop -256 +jmp -420 +acc -12 +acc -17 +jmp -276 +acc +45 +acc +40 +jmp -180 +acc +50 +jmp -501 +acc +17 +jmp -232 +acc +12 +jmp -109 +nop -291 +nop -345 +jmp +100 +acc +36 +acc +2 +acc -2 +jmp +1 +acc +23 +nop -299 +acc +24 +acc +30 +jmp -476 +acc +0 +acc +6 +acc +49 +jmp +6 +nop -461 +jmp -539 +nop -62 +acc +48 +jmp -526 +jmp -365 +acc +47 +acc +10 +acc +32 +jmp -490 +nop -148 +acc +42 +acc +5 +jmp -358 +acc -5 +jmp -101 +jmp -502 +acc +15 +acc +45 +nop -399 +jmp +1 +acc +31 +acc +47 +acc +49 +jmp -269 +acc +6 +acc +45 +acc -8 +acc -6 +jmp +36 +jmp +51 +acc +39 +jmp -64 +acc +47 +jmp +1 +acc -8 +jmp -102 +acc -8 +jmp -202 +jmp -18 +acc +1 +jmp -484 +acc +35 +acc +30 +acc +49 +jmp -562 +jmp -515 +acc -13 +nop -6 +jmp -369 +acc +27 +acc +18 +nop -477 +acc -10 +jmp -430 +jmp +1 +acc +7 +nop -111 +jmp -445 +jmp +12 +jmp -50 +acc +7 +acc +3 +nop -433 +jmp -390 +acc -5 +acc +50 +jmp -67 +acc +45 +acc -10 +jmp -446 +jmp -496 +jmp -17 +acc +14 +acc +33 +jmp -239 +acc +3 +acc -3 +acc +27 +acc -3 +jmp -162 +jmp -16 +acc +23 +acc +26 +acc +25 +jmp -346 +acc +40 +acc +45 +acc +42 +acc -4 +jmp +1 diff --git a/2020/day08_handheld_halting/tests/sample_input b/2020/day08_handheld_halting/tests/sample_input new file mode 100644 index 0000000..178df53 --- /dev/null +++ b/2020/day08_handheld_halting/tests/sample_input @@ -0,0 +1,9 @@ +nop +0 +acc +1 +jmp +4 +acc +3 +jmp -3 +acc -99 +acc +1 +jmp -4 +acc +6