Skip to content

Commit 962f141

Browse files
committed
feat: aoc 2022 day 14 solution + tests
1 parent 46c85d4 commit 962f141

File tree

5 files changed

+324
-0
lines changed

5 files changed

+324
-0
lines changed

_2022/data/day14/puzzle_input.txt

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
491,57 -> 506,57 -> 506,56
2+
469,142 -> 473,142
3+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
4+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
5+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
6+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
7+
475,142 -> 479,142
8+
470,154 -> 475,154
9+
506,89 -> 511,89
10+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
11+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
12+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
13+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
14+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
15+
466,151 -> 471,151
16+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
17+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
18+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
19+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
20+
496,91 -> 501,91
21+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
22+
503,91 -> 508,91
23+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
24+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
25+
500,93 -> 505,93
26+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
27+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
28+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
29+
495,14 -> 495,15 -> 508,15 -> 508,14
30+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
31+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
32+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
33+
495,14 -> 495,15 -> 508,15 -> 508,14
34+
454,163 -> 458,163
35+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
36+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
37+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
38+
472,139 -> 476,139
39+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
40+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
41+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
42+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
43+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
44+
514,93 -> 519,93
45+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
46+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
47+
466,145 -> 470,145
48+
457,160 -> 461,160
49+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
50+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
51+
456,154 -> 461,154
52+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
53+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
54+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
55+
502,87 -> 507,87
56+
460,163 -> 464,163
57+
453,157 -> 458,157
58+
478,145 -> 482,145
59+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
60+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
61+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
62+
493,93 -> 498,93
63+
467,157 -> 472,157
64+
463,166 -> 467,166
65+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
66+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
67+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
68+
487,53 -> 487,54 -> 500,54 -> 500,53
69+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
70+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
71+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
72+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
73+
451,166 -> 455,166
74+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
75+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
76+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
77+
472,145 -> 476,145
78+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
79+
507,93 -> 512,93
80+
460,157 -> 465,157
81+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
82+
484,123 -> 484,124 -> 501,124
83+
462,148 -> 467,148
84+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
85+
459,151 -> 464,151
86+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
87+
506,84 -> 506,79 -> 506,84 -> 508,84 -> 508,80 -> 508,84 -> 510,84 -> 510,82 -> 510,84 -> 512,84 -> 512,75 -> 512,84
88+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
89+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
90+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
91+
487,53 -> 487,54 -> 500,54 -> 500,53
92+
487,53 -> 487,54 -> 500,54 -> 500,53
93+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
94+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
95+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
96+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
97+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
98+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
99+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
100+
495,14 -> 495,15 -> 508,15 -> 508,14
101+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
102+
457,166 -> 461,166
103+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
104+
488,51 -> 488,44 -> 488,51 -> 490,51 -> 490,47 -> 490,51 -> 492,51 -> 492,43 -> 492,51 -> 494,51 -> 494,48 -> 494,51
105+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
106+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
107+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
108+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
109+
510,91 -> 515,91
110+
463,154 -> 468,154
111+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
112+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
113+
474,157 -> 479,157
114+
480,127 -> 480,130 -> 475,130 -> 475,136 -> 494,136 -> 494,130 -> 486,130 -> 486,127
115+
491,57 -> 506,57 -> 506,56
116+
499,89 -> 504,89
117+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
118+
484,123 -> 484,124 -> 501,124
119+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
120+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
121+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29
122+
492,18 -> 492,20 -> 489,20 -> 489,26 -> 497,26 -> 497,20 -> 495,20 -> 495,18
123+
493,119 -> 493,114 -> 493,119 -> 495,119 -> 495,114 -> 495,119 -> 497,119 -> 497,115 -> 497,119 -> 499,119 -> 499,109 -> 499,119 -> 501,119 -> 501,109 -> 501,119
124+
505,60 -> 505,63 -> 499,63 -> 499,71 -> 509,71 -> 509,63 -> 508,63 -> 508,60
125+
485,106 -> 485,96 -> 485,106 -> 487,106 -> 487,103 -> 487,106 -> 489,106 -> 489,98 -> 489,106 -> 491,106 -> 491,96 -> 491,106 -> 493,106 -> 493,105 -> 493,106 -> 495,106 -> 495,100 -> 495,106 -> 497,106 -> 497,102 -> 497,106
126+
497,29 -> 497,32 -> 493,32 -> 493,38 -> 507,38 -> 507,32 -> 501,32 -> 501,29

_2022/solutions/day14.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""Day 14: Regolith Reservoir
2+
3+
This module provides the solution for Advent of Code 2022 - Day 14.
4+
5+
It simulates falling sand in a cave system with rock formations, tracking
6+
how sand accumulates as it falls from a source point following gravity rules.
7+
8+
The module contains a Solution class that inherits from SolutionBase for
9+
parsing rock formations and simulating sand physics.
10+
"""
11+
12+
from itertools import pairwise
13+
import re
14+
from typing import ClassVar
15+
16+
from aoc.models.base import SolutionBase
17+
18+
19+
class Solution(SolutionBase):
20+
"""Simulate falling sand in cave system with rock formations.
21+
22+
This solution models sand falling from point (500, 0) following physics rules:
23+
sand tries to move down, then down-left, then down-right. Part 1 counts sand
24+
units that settle before falling into the abyss. Part 2 adds an infinite floor
25+
and counts units until the source is blocked.
26+
27+
The simulation uses set-based collision detection for efficient position tracking.
28+
"""
29+
30+
REGEX: ClassVar[re.Pattern[str]] = re.compile(r"(\d+,\d+)")
31+
SAND_SOURCE: ClassVar[tuple[int, int]] = (500, 0)
32+
33+
def parse_data(self, data: list[str]) -> set[tuple[int, int]]:
34+
"""Parse rock formation coordinates into occupied positions set.
35+
36+
Converts line segment notation (e.g., "498,4 -> 498,6 -> 496,6") into
37+
individual coordinate points by drawing lines between each pair of points.
38+
39+
Args:
40+
data: List of strings describing rock paths as coordinate pairs
41+
separated by " -> "
42+
43+
Returns
44+
-------
45+
set[tuple[int, int]]: Set of (x, y) coordinates occupied by rocks
46+
"""
47+
rocks = set()
48+
49+
for line in data:
50+
matches = re.findall(self.REGEX, line)
51+
coords = [(int(x), int(y)) for x, y in [match.split(",") for match in matches]]
52+
53+
for (x1, y1), (x2, y2) in pairwise(coords):
54+
# Vertical line
55+
if x1 == x2:
56+
for y in range(min(y1, y2), max(y1, y2) + 1):
57+
rocks.add((x1, y))
58+
59+
# Horizontal line
60+
else:
61+
for x in range(min(x1, x2), max(x1, x2) + 1):
62+
rocks.add((x, y1))
63+
64+
return rocks
65+
66+
def simulate(
67+
self, rocks: set[tuple[int, int]], max_depth: int, *, has_floor: bool = False
68+
) -> int:
69+
"""Simulate sand falling and settling until termination condition met.
70+
71+
Each sand unit falls from source (500, 0) following movement rules:
72+
1. Try to move down (0, +1)
73+
2. Try to move down-left (-1, +1)
74+
3. Try to move down-right (+1, +1)
75+
4. If all blocked, settle at current position
76+
77+
Args:
78+
rocks: Set of rock positions that block sand
79+
max_depth: Maximum y-coordinate of rocks (for abyss detection)
80+
has_floor: If True, simulate infinite floor at max_depth + 2
81+
82+
Returns
83+
-------
84+
int: Number of sand units that settled before termination condition
85+
"""
86+
settled_sand = set()
87+
fall_offsets = [(0, 1), (-1, 1), (1, 1)] # down, down-left, down-right
88+
floor_level = max_depth + 2 if has_floor else None
89+
90+
while True:
91+
sand_x, sand_y = self.SAND_SOURCE
92+
93+
while True:
94+
if has_floor and sand_y + 1 == floor_level:
95+
settled_sand.add((sand_x, sand_y))
96+
break
97+
98+
if not has_floor and sand_y >= max_depth:
99+
return len(settled_sand)
100+
101+
moved = False
102+
for dx, dy in fall_offsets:
103+
next_pos = (sand_x + dx, sand_y + dy)
104+
105+
if next_pos not in rocks and next_pos not in settled_sand:
106+
sand_x, sand_y = next_pos
107+
moved = True
108+
break
109+
110+
if not moved:
111+
settled_sand.add((sand_x, sand_y))
112+
113+
if has_floor and (sand_x, sand_y) == self.SAND_SOURCE:
114+
return len(settled_sand)
115+
116+
break
117+
118+
def part1(self, data: list[str]) -> int:
119+
"""Count sand units that settle before falling into abyss.
120+
121+
Simulates sand falling until a unit falls past the lowest rock formation,
122+
indicating it would fall forever into the endless void below.
123+
124+
Args:
125+
data: List of strings describing rock formations
126+
127+
Returns
128+
-------
129+
int: Number of sand units that came to rest before sand starts
130+
flowing into the abyss
131+
"""
132+
rocks = self.parse_data(data)
133+
max_depth = max(y for _, y in rocks)
134+
135+
return self.simulate(rocks, max_depth, has_floor=False)
136+
137+
def part2(self, data: list[str]) -> int:
138+
"""Count sand units that settle before blocking the source.
139+
140+
Adds an infinite horizontal floor 2 units below the lowest rock.
141+
Simulates until sand accumulates to block the source point at (500, 0).
142+
143+
Args:
144+
data: List of strings describing rock formations
145+
146+
Returns
147+
-------
148+
int: Number of sand units that came to rest before the source
149+
becomes blocked (including the unit that blocks it)
150+
"""
151+
rocks = self.parse_data(data)
152+
max_depth = max(y for _, y in rocks)
153+
154+
return self.simulate(rocks, max_depth, has_floor=True)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
498,4 -> 498,6 -> 496,6
2+
503,4 -> 502,4 -> 502,9 -> 494,9
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
498,4 -> 498,6 -> 496,6
2+
503,4 -> 502,4 -> 502,9 -> 494,9

_2022/tests/test_14.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Test suite for Day 14: Regolith Reservoir
2+
3+
This module contains tests for the Day 14 solution, which simulates falling sand
4+
in a cave system with rock formations. The tests verify:
5+
6+
1. Part 1: Counting sand units that settle before falling into abyss
7+
2. Part 2: Counting sand units that settle before blocking the source (with floor)
8+
"""
9+
10+
from aoc.models.tester import TestSolutionUtility
11+
12+
13+
def test_day14_part1() -> None:
14+
"""Test counting sand units before falling into abyss.
15+
16+
This test runs the solution for Part 1 of the puzzle against the
17+
provided test input and compares the result with the expected output.
18+
"""
19+
TestSolutionUtility.run_test(
20+
year=2022,
21+
day=14,
22+
is_raw=False,
23+
part_num=1,
24+
expected=24,
25+
)
26+
27+
28+
def test_day14_part2() -> None:
29+
"""Test counting sand units before blocking source with floor.
30+
31+
This test runs the solution for Part 2 of the puzzle against the
32+
provided test input and compares the result with the expected output.
33+
"""
34+
TestSolutionUtility.run_test(
35+
year=2022,
36+
day=14,
37+
is_raw=False,
38+
part_num=2,
39+
expected=93,
40+
)

0 commit comments

Comments
 (0)