Skip to content

Commit 8b8bf7b

Browse files
committed
feat: aoc 2022 day 10 solution + tests
1 parent 3c3cfbf commit 8b8bf7b

File tree

5 files changed

+626
-0
lines changed

5 files changed

+626
-0
lines changed

_2022/data/day10/puzzle_input.txt

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
noop
2+
noop
3+
noop
4+
addx 3
5+
addx 20
6+
noop
7+
addx -12
8+
noop
9+
addx 4
10+
noop
11+
noop
12+
noop
13+
addx 1
14+
addx 2
15+
addx 5
16+
addx 16
17+
addx -14
18+
addx -25
19+
addx 30
20+
addx 1
21+
noop
22+
addx 5
23+
noop
24+
addx -38
25+
noop
26+
noop
27+
noop
28+
addx 3
29+
addx 2
30+
noop
31+
noop
32+
noop
33+
addx 5
34+
addx 5
35+
addx 2
36+
addx 13
37+
addx 6
38+
addx -16
39+
addx 2
40+
addx 5
41+
addx -15
42+
addx 16
43+
addx 7
44+
noop
45+
addx -2
46+
addx 2
47+
addx 5
48+
addx -39
49+
addx 4
50+
addx -2
51+
addx 2
52+
addx 7
53+
noop
54+
addx -2
55+
addx 17
56+
addx -10
57+
noop
58+
noop
59+
addx 5
60+
addx -1
61+
addx 6
62+
noop
63+
addx -2
64+
addx 5
65+
addx -8
66+
addx 12
67+
addx 3
68+
addx -2
69+
addx -19
70+
addx -16
71+
addx 2
72+
addx 5
73+
noop
74+
addx 25
75+
addx 7
76+
addx -29
77+
addx 3
78+
addx 4
79+
addx -4
80+
addx 9
81+
noop
82+
addx 2
83+
addx -20
84+
addx 23
85+
addx 1
86+
noop
87+
addx 5
88+
addx -10
89+
addx 14
90+
addx 2
91+
addx -1
92+
addx -38
93+
noop
94+
addx 20
95+
addx -15
96+
noop
97+
addx 7
98+
noop
99+
addx 26
100+
addx -25
101+
addx 2
102+
addx 7
103+
noop
104+
noop
105+
addx 2
106+
addx -5
107+
addx 6
108+
addx 5
109+
addx 2
110+
addx 8
111+
addx -3
112+
noop
113+
addx 3
114+
addx -2
115+
addx -38
116+
addx 13
117+
addx -6
118+
noop
119+
addx 1
120+
addx 5
121+
noop
122+
noop
123+
noop
124+
noop
125+
addx 2
126+
noop
127+
noop
128+
addx 7
129+
addx 3
130+
addx -2
131+
addx 2
132+
addx 5
133+
addx 2
134+
noop
135+
addx 1
136+
addx 5
137+
noop
138+
noop
139+
noop
140+
noop
141+
noop
142+
noop

_2022/solutions/day10.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Day 10: Cathode-Ray Tube
2+
3+
This module provides the solution for Advent of Code 2022 - Day 10.
4+
5+
It simulates a simple CPU with register operations (addx, noop) and tracks
6+
signal strengths at specific cycles. Part 2 renders a CRT display based on
7+
the register value during each cycle.
8+
9+
The module contains a CPU class for simulation and a Solution class that
10+
inherits from SolutionBase.
11+
"""
12+
13+
import re
14+
from typing import ClassVar
15+
16+
from loguru import logger
17+
18+
from aoc.models.base import SolutionBase
19+
20+
21+
class CPU:
22+
"""Simulate a simple CPU with register X and cycle-based operations.
23+
24+
The CPU executes two instructions: addx (takes 2 cycles, adds value to X)
25+
and noop (takes 1 cycle, does nothing). Register X starts at 1 and is used
26+
to control both signal strength calculations and CRT sprite positioning.
27+
"""
28+
29+
REGEX: ClassVar[re.Pattern] = re.compile(r"^(addx) (-?\d+)$|^(noop)$")
30+
31+
def __init__(self) -> None:
32+
"""Initialize CPU with register X at 1 and cycle counter at 1."""
33+
self.register_X = 1
34+
self.cycle = 1
35+
self.waiting_add = 0
36+
self.wait = 0
37+
self.sum_of_registers = 0
38+
self.display: str = ""
39+
40+
def run(self, command: str) -> None:
41+
"""Parse and queue a command for execution.
42+
43+
Args:
44+
command: Either "addx V" or "noop" instruction
45+
"""
46+
match = self.REGEX.match(command)
47+
48+
if match and match.group(1) == "addx":
49+
self.wait = 2
50+
self.waiting_add = int(match.group(2))
51+
52+
else: # noop
53+
self.wait = 1
54+
self.waiting_add = 0
55+
56+
def advance_cycle(self, part_2: bool | None = None) -> None:
57+
"""Advance one CPU cycle, updating signal strength and register.
58+
59+
Signal strength is calculated at cycles 20, 60, 100, 140, 180, 220.
60+
Register X is updated after an instruction completes its wait cycles.
61+
62+
Args:
63+
part_2: If True, also render CRT pixel for this cycle
64+
"""
65+
if self.cycle in [20, 60, 100, 140, 180, 220]:
66+
self.sum_of_registers += self.cycle * self.register_X
67+
68+
if part_2:
69+
self.draw_pixel()
70+
71+
# Advance cycle
72+
self.cycle += 1
73+
self.wait -= 1
74+
75+
# Update register when instruction completes
76+
if self.wait == 0:
77+
self.register_X += self.waiting_add
78+
79+
def draw_pixel(self) -> None:
80+
"""Draw one CRT pixel based on sprite position and current cycle.
81+
82+
The CRT is 40 pixels wide. A 3-pixel wide sprite is centered at
83+
register X position. If the current pixel position overlaps with
84+
the sprite, draw '#', otherwise draw '.'.
85+
"""
86+
sprite_positions = [self.register_X - 1, self.register_X, self.register_X + 1]
87+
pixel_position = (self.cycle - 1) % 40
88+
89+
if pixel_position in sprite_positions:
90+
self.display += "#"
91+
92+
else:
93+
self.display += "."
94+
95+
if len(self.display) == 40:
96+
logger.info(f"{self.display}")
97+
self.display = ""
98+
99+
100+
class Solution(SolutionBase):
101+
"""Simulate CPU operations and render CRT display.
102+
103+
This solution implements a simple CPU simulator that executes addx and noop
104+
instructions over multiple cycles. Part 1 calculates signal strengths at
105+
specific cycles (20, 60, 100, 140, 180, 220). Part 2 renders a 40-pixel
106+
wide CRT display where pixels are lit based on sprite position.
107+
"""
108+
109+
def solve_part(self, data: list[str], *, part_2: bool = False) -> CPU:
110+
"""Run CPU simulation through all instructions.
111+
112+
Processes instructions sequentially, advancing the CPU cycle-by-cycle
113+
until all instructions complete and the CPU becomes idle.
114+
115+
Args:
116+
data: List of CPU instructions (addx or noop)
117+
part_2: If True, enable CRT display rendering
118+
119+
Returns
120+
-------
121+
CPU: CPU instance after all instructions complete with accumulated
122+
signal strengths and display output
123+
"""
124+
cpu = CPU()
125+
instructions = data.copy()
126+
127+
while len(instructions) > 0 or cpu.wait > 0:
128+
# If CPU is idle, load next instruction
129+
if cpu.wait == 0 and len(instructions) > 0:
130+
line = instructions.pop(0)
131+
cpu.run(line)
132+
133+
# Advance one cycle
134+
cpu.advance_cycle(part_2=part_2)
135+
136+
return cpu
137+
138+
def part1(self, data: list[str]) -> int:
139+
"""Calculate sum of signal strengths at cycles 20, 60, 100, 140, 180, 220.
140+
141+
Signal strength is the cycle number multiplied by register X value during
142+
that cycle. The sum provides insight into CPU behavior during execution.
143+
144+
Args:
145+
data: List of CPU instructions
146+
147+
Returns
148+
-------
149+
int: Sum of signal strengths at the six specified cycles
150+
"""
151+
cpu = self.solve_part(data)
152+
return cpu.sum_of_registers
153+
154+
def part2(self, data: list[str]) -> None:
155+
"""Render CRT display output to console.
156+
157+
The CRT draws 40x6 pixels, rendering one pixel per cycle. A 3-pixel
158+
sprite centered at register X determines if each pixel is lit ('#')
159+
or dark ('.'). The output spells an 8-letter message.
160+
161+
Args:
162+
data: List of CPU instructions
163+
164+
Returns
165+
-------
166+
None: Display is printed to console during execution
167+
"""
168+
_ = self.solve_part(data, part_2=True)

0 commit comments

Comments
 (0)