Day 10: Cathode-Ray Tube
https://adventofcode.com/2022/day/10
TL;DR: my solution in Rust
After falling from the bridge yesterday, you want to use the communication device from a few days ago to get in touch with the elves.
The screen broke, but the CPU still works.
The screen and CPU are both driven by a clock circuit, each tick of the clock is called a cycle.
The CPU has a single register (a place that stores a number) named X
, which starts at 1.
It supports 2 instructions:
-
addx N
takes 2 cycles to complete. After two cycles, the numberN
gets added to theX
register. -
noop
takes 1 cycle to complete. It does nothing.
Today's input is a list of instructions for the CPU.
An example input looks like this:
noop
addx 3
addx -5
- The first cycle:
- start:
noop
begins executing.X
is 1 (the starting value). - end:
noop
finishes executing, doing nothing (X
is 1)
- start:
- The second cycle:
- start:
addx 3
begins executing.X
is 1 - end: nothing finishes executing, doing nothing (
X
is 1)
- start:
- The third cycle:
- start:
addx 3
continues executing.X
is still 1 - end:
addx 3
finishes executing, adding 3 toX
(X
is 4)
- start:
- The fourth cycle:
- start:
addx -5
begins executing.X
is 4 - end: nothing finishes executing, doing nothing (
X
is 4)
- start:
- The fifth cycle:
- start:
addx -5
continues executing.X
is still 4 - end:
addx -5
finishes executing, subtracting 5 fromX
(X
is -1)
- start:
Part 1
The signal strength is the cycle number multiplied by the value in X
during a cycle. (not after!)
The question asks to find the sum of the signal strength during the 20th, 60th, 100th, 140th, 180th, and 220th cycles.
That's the 20th cycle and every 40 cycles after that.
That means we have to add the signal strength during a cycle to a total whenever cycle % 40 == 20
(up to 220).
-
X
starts at 1 -
cycle
starts at 1 (I know, an index starting at 1? The humanity!) - the
total
starts at 0 - whenever
cycle % 40 == 20
, addcycle * x
tototal
Every instruction completes before an other one starts.
So for noop
, the cycle
counter gets incremented once.
For addx
, the cycle counter gets incremented twice.
In pseudocode, that would be:
let mut x = 1;
let mut cycle = 1;
let mut total = 0;
for instruction in all_instructions {
// if noop or addx:
// check signal strength
// increment cycle
// if addx:
// check signal strength
// add to x and increment cycle
}
This maps directly to the solution for part1!
Final code
pub fn part_1() -> i32 {
let input = std::fs::read_to_string("src/day10.txt").unwrap();
let mut x = 1;
let mut cycle = 1;
let mut total = 0;
for line in input.lines() {
if cycle % 40 == 20 {
total += cycle * x;
}
cycle += 1;
if let Some(("addx", num)) = line.split_once(' ') {
if cycle % 40 == 20 {
total += cycle * x;
}
let num: i32 = num.parse().unwrap();
x += num;
cycle += 1;
}
}
total
}
Part 2
Part 2 is about figuring out what the broken screen would have displayed given the instructions in your input.
The X
register controls the horizontal middle of a 3 block wide sprite.
There is no concept of a vertical position.
- The screen and the CPU are tied to the same clock cycles.
- The screen draws a pixel every cycle. Its position incrementing by one every cycle.
- If any of the 3 blocks of the sprite is at the position the screen is currently drawing, a lit pixel is drawn, else a dim one.
The screen is 40 pixels wide and 6 high.
Some constants that are mentioned in the question:
const COLS: usize = 40;
const ROWS: usize = 6;
const SPRITE_WIDTH: u32 = 3;
It draws left to right, top to bottom.
Every time it begins drawing a new row, it starts at the left edge again.
The
cycle
starts at 1, and the index on thescreen
starts at 0!
We can write a small helper function to determine if a pixel should be lit or dim.
The function takes in the current value of x
, and the current cycle
.
If the 3 wide sprite is at the location the screen is currently drawing, the pixel is lit.
fn get_pixel(cycle: usize, x: i32) -> char {
let curr_col = (cycle - 1) % COLS;
if (curr_col as i32).abs_diff(x) <= SPRITE_WIDTH / 2 {
'█'
} else {
' '
}
}
We represent the screen as a big array of length ROWS * COLS
.
Each item in that array is a pixel.
Our initial setup for the variables we keep track of in part2:
let mut x = 1;
let mut cycle = 1;
let mut screen = [' '; COLS * ROWS];
Instead of checking the signal strength during each cycle, we draw a pixel to the screen.
At the end, we have a filled screen
array.
As a fun trick, you can end part 2 here, print that array as a string and resize your terminal until you can read it (so until it's 40 characters wide).
Line wrapping will do the trick.
A more universal solution involves splitting the array into chunks of COLS
long,
converted each chunk to a string,
and joining those strings with a newline.
let image = screen
.chunks(COLS)
.map(|row| row.iter().collect())
.collect::<Vec<String>>()
.join("\n");
Put everything together and presto, Advent of Code 2022 day10 part2 is complete!
Final code
const COLS: usize = 40;
const ROWS: usize = 6;
const SPRITE_WIDTH: u32 = 3;
fn get_pixel(cycle: usize, x: i32) -> char {
let curr_col = (cycle - 1) % COLS;
if (curr_col as i32).abs_diff(x) <= SPRITE_WIDTH / 2 {
'█'
} else {
' '
}
}
pub fn part_2() -> String {
let input = std::fs::read_to_string("src/day10.txt").unwrap();
let mut x = 1;
let mut cycle = 1;
let mut screen = [' '; COLS * ROWS];
for line in input.lines() {
screen[cycle - 1] = get_pixel(cycle, x);
cycle += 1;
if let Some(("addx", num)) = line.split_once(' ') {
screen[cycle - 1] = get_pixel(cycle, x);
cycle += 1;
let num: i32 = num.parse().unwrap();
x += num;
}
}
let image = screen
.chunks(COLS)
.map(|row| row.iter().collect())
.collect::<Vec<String>>()
.join('\n');
image
}
Top comments (0)