/// --- Part Two --- /// /// It seems like the X register controls the horizontal position of a sprite. Specifically, the /// sprite is 3 pixels wide, and the X register sets the horizontal position of the middle of that /// sprite. (In this system, there is no such thing as "vertical position": if the sprite's /// horizontal position puts its pixels where the CRT is currently drawing, then those pixels will /// be drawn.) /// /// You count the pixels on the CRT: 40 wide and 6 high. This CRT screen draws the top row of /// pixels left-to-right, then the row below that, and so on. The left-most pixel in each row is /// in position 0, and the right-most pixel in each row is in position 39. /// /// Like the CPU, the CRT is tied closely to the clock circuit: the CRT draws a single pixel during /// each cycle. Representing each pixel of the screen as a #, here are the cycles during which the /// first and last pixel in each row are drawn: /// /// ``` /// Cycle 1 -> ######################################## <- Cycle 40 /// Cycle 41 -> ######################################## <- Cycle 80 /// Cycle 81 -> ######################################## <- Cycle 120 /// Cycle 121 -> ######################################## <- Cycle 160 /// Cycle 161 -> ######################################## <- Cycle 200 /// Cycle 201 -> ######################################## <- Cycle 240 /// ``` /// /// So, by carefully timing the CPU instructions and the CRT drawing operations, you should be able /// to determine whether the sprite is visible the instant each pixel is drawn. If the sprite is /// positioned such that one of its three pixels is the pixel currently being drawn, the screen /// produces a lit pixel (#); otherwise, the screen leaves the pixel dark (.). /// /// The first few pixels from the larger example above are drawn as follows: /// /// ``` /// Sprite position: ###..................................... /// /// Start cycle 1: begin executing addx 15 /// During cycle 1: CRT draws pixel in position 0 /// Current CRT row: # /// /// During cycle 2: CRT draws pixel in position 1 /// Current CRT row: ## /// End of cycle 2: finish executing addx 15 (Register X is now 16) /// Sprite position: ...............###...................... /// /// Start cycle 3: begin executing addx -11 /// During cycle 3: CRT draws pixel in position 2 /// Current CRT row: ##. /// /// During cycle 4: CRT draws pixel in position 3 /// Current CRT row: ##.. /// End of cycle 4: finish executing addx -11 (Register X is now 5) /// Sprite position: ....###................................. /// /// Start cycle 5: begin executing addx 6 /// During cycle 5: CRT draws pixel in position 4 /// Current CRT row: ##..# /// /// During cycle 6: CRT draws pixel in position 5 /// Current CRT row: ##..## /// End of cycle 6: finish executing addx 6 (Register X is now 11) /// Sprite position: ..........###........................... /// /// Start cycle 7: begin executing addx -3 /// During cycle 7: CRT draws pixel in position 6 /// Current CRT row: ##..##. /// /// During cycle 8: CRT draws pixel in position 7 /// Current CRT row: ##..##.. /// End of cycle 8: finish executing addx -3 (Register X is now 8) /// Sprite position: .......###.............................. /// /// Start cycle 9: begin executing addx 5 /// During cycle 9: CRT draws pixel in position 8 /// Current CRT row: ##..##..# /// /// During cycle 10: CRT draws pixel in position 9 /// Current CRT row: ##..##..## /// End of cycle 10: finish executing addx 5 (Register X is now 13) /// Sprite position: ............###......................... /// /// Start cycle 11: begin executing addx -1 /// During cycle 11: CRT draws pixel in position 10 /// Current CRT row: ##..##..##. /// /// During cycle 12: CRT draws pixel in position 11 /// Current CRT row: ##..##..##.. /// End of cycle 12: finish executing addx -1 (Register X is now 12) /// Sprite position: ...........###.......................... /// /// Start cycle 13: begin executing addx -8 /// During cycle 13: CRT draws pixel in position 12 /// Current CRT row: ##..##..##..# /// /// During cycle 14: CRT draws pixel in position 13 /// Current CRT row: ##..##..##..## /// End of cycle 14: finish executing addx -8 (Register X is now 4) /// Sprite position: ...###.................................. /// /// Start cycle 15: begin executing addx 13 /// During cycle 15: CRT draws pixel in position 14 /// Current CRT row: ##..##..##..##. /// /// During cycle 16: CRT draws pixel in position 15 /// Current CRT row: ##..##..##..##.. /// End of cycle 16: finish executing addx 13 (Register X is now 17) /// Sprite position: ................###..................... /// /// Start cycle 17: begin executing addx 4 /// During cycle 17: CRT draws pixel in position 16 /// Current CRT row: ##..##..##..##..# /// /// During cycle 18: CRT draws pixel in position 17 /// Current CRT row: ##..##..##..##..## /// End of cycle 18: finish executing addx 4 (Register X is now 21) /// Sprite position: ....................###................. /// /// Start cycle 19: begin executing noop /// During cycle 19: CRT draws pixel in position 18 /// Current CRT row: ##..##..##..##..##. /// End of cycle 19: finish executing noop /// /// Start cycle 20: begin executing addx -1 /// During cycle 20: CRT draws pixel in position 19 /// Current CRT row: ##..##..##..##..##.. /// /// During cycle 21: CRT draws pixel in position 20 /// Current CRT row: ##..##..##..##..##..# /// End of cycle 21: finish executing addx -1 (Register X is now 20) /// Sprite position: ...................###.................. /// ``` /// /// Allowing the program to run to completion causes the CRT to produce the following image: /// /// ``` /// ##..##..##..##..##..##..##..##..##..##.. /// ###...###...###...###...###...###...###. /// ####....####....####....####....####.... /// #####.....#####.....#####.....#####..... /// ######......######......######......#### /// #######.......#######.......#######..... /// ``` /// /// Render the image given by your program. What eight capital letters appear on your CRT? use clap::Parser; use std::fmt::Write; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; use std::path::PathBuf; const FILEPATH: &'static str = "examples/input.txt"; const XINIT: i32 = 1; const SPRITE_WIDTH: u8 = 3; const CRT_WIDTH: i32 = 40; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Cli { #[clap(short, long, default_value = FILEPATH)] file: PathBuf, } #[derive(Copy, Clone, Debug)] enum Command { Addx(i32), Noop, } #[derive(Clone, Debug)] struct State { x: i32, cycle: u32, res: String, } impl State { fn new() -> Self { State { x: XINIT, cycle: 0, res: String::new(), } } fn process(&mut self) { self.cycle += 1; let pos = (self.cycle as i32 - 1) % CRT_WIDTH; if (pos + 1) < self.x || pos + 1 >= (self.x + SPRITE_WIDTH as i32) { self.res += " "; } else { self.res += "█"; } if self.cycle as i32 % CRT_WIDTH == 0 { writeln!(&mut self.res).unwrap(); } } } impl Command { fn parse(s: &str) -> Self { let split_s = s.split_whitespace().collect::>(); match split_s.len() { 1 => Command::Noop, 2 => Command::Addx(split_s[1].parse::().unwrap()), _ => panic!(), } } } fn main() { let args = Cli::parse(); let file = File::open(&args.file).unwrap(); let reader = BufReader::new(file); let res = reader .lines() .map(|l| Command::parse(l.unwrap().as_str())) .scan(State::new(), |state, command| { state.process(); match command { Command::Noop => (), Command::Addx(value) => { state.process(); state.x += value; } }; Some(state.res.clone()) }) .last() .unwrap(); // "ZGCJZJFL" println!("{res}"); }