Skip to content

Commit 2640f7d

Browse files
committed
2025: day 9 in Python + visualization
1 parent 5ec509f commit 2640f7d

File tree

5 files changed

+186
-6
lines changed

5 files changed

+186
-6
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![Stars: 518](https://img.shields.io/badge/Stars-518⭐-blue)
44
![Rust: 259](https://img.shields.io/badge/Rust-259-cyan?logo=Rust)
5-
![Python: 127](https://img.shields.io/badge/Python-127-cyan?logo=Python)
5+
![Python: 128](https://img.shields.io/badge/Python-128-cyan?logo=Python)
66

77
<img src="./scripts/assets/christmas_ferris_2015_2024.png" alt="Christmas Ferris" width="164" />
88

@@ -22,13 +22,13 @@ Puzzle | Stars | Lang
2222
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6.rs) [![Rust](./scripts/assets/rust.png)](src/year2025/day6/day6_declarative.rs) [![Python](./scripts/assets/python.png)](src/year2025/day6/day6.py)
2323
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day7/day7.rs) [![Go](./scripts/assets/go.png)](src/year2025/day7/day7.go) [🎁](src/year2025/day7/README.md)
2424
[Day 8: Playground](https://adventofcode.com/2025/day/8) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day8/day8.rs)
25-
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day9/day9.rs)
25+
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](./scripts/assets/rust.png)](src/year2025/day9/day9.rs) [![Python](./scripts/assets/python.png)](src/year2025/day9/day9.py) [🎁](src/year2025/day9/README.md)
2626

2727
## All years
2828

2929
Calendar | Solutions | Stars | Rust | Python | 🎁
3030
-------- | --------- | ----- | ---- | ------ | --
31-
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 18⭐ | 9 | 5 | 1
31+
[Advent of Code 2025](https://adventofcode.com/2025) | [Solutions](src/year2025/README.md) | 18⭐ | 9 | 6 | 2
3232
[Advent of Code 2024](https://adventofcode.com/2024) | [Solutions](src/year2024/README.md) | 50⭐ | 25 | 11 | 3
3333
[Advent of Code 2023](https://adventofcode.com/2023) | [Solutions](src/year2023/README.md) | 50⭐ | 25 | 10 | 2
3434
[Advent of Code 2022](https://adventofcode.com/2022) | [Solutions](src/year2022/README.md) | 50⭐ | 25 | 18 | 1
@@ -44,7 +44,7 @@ Calendar | Solutions | Stars | Rust | Python | 🎁
4444

4545
Year | Count | Days
4646
---- | ----- | --------------------
47-
2025 | 1 | [7](src/year2025/day7/README.md)
47+
2025 | 2 | [7](src/year2025/day7/README.md) [9](src/year2025/day9/README.md)
4848
2024 | 3 | [14](src/year2024/day14/README.md) [15](src/year2024/day15/README.md) [16](src/year2024/day16/README.md)
4949
2023 | 2 | [10](src/year2023/day10/README.md) [14](src/year2023/day14/README.md)
5050
2022 | 1 | [17](src/year2022/day17/README.md)

src/year2025/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
![AoC2025](https://img.shields.io/badge/Advent_of_Code-2025-8A2BE2)
44
![Stars: 18](https://img.shields.io/badge/Stars-18⭐-blue)
55
![Rust: 10](https://img.shields.io/badge/Rust-10-cyan?logo=Rust)
6-
![Python: 5](https://img.shields.io/badge/Python-5-cyan?logo=Python)
6+
![Python: 6](https://img.shields.io/badge/Python-6-cyan?logo=Python)
77

88
## 2025 ([Calendar](https://adventofcode.com/2025)) ([Solutions](./)) : 18⭐
99

@@ -17,4 +17,4 @@ Puzzle | Stars | Lang
1717
[Day 6: Trash Compactor](https://adventofcode.com/2025/day/6) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day6/day6.rs) [![Rust](../../scripts/assets/rust.png)](day6/day6_declarative.rs) [![Python](../../scripts/assets/python.png)](day6/day6.py)
1818
[Day 7: Laboratories](https://adventofcode.com/2025/day/7) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day7/day7.rs) [![Go](../../scripts/assets/go.png)](day7/day7.go) [🎁](day7/README.md)
1919
[Day 8: Playground](https://adventofcode.com/2025/day/8) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day8/day8.rs)
20-
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day9/day9.rs)
20+
[Day 9: Movie Theater](https://adventofcode.com/2025/day/9) | ⭐⭐ | [![Rust](../../scripts/assets/rust.png)](day9/day9.rs) [![Python](../../scripts/assets/python.png)](day9/day9.py) [🎁](day9/README.md)

src/year2025/day9/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Movie Theater
2+
3+
![big tile floor](bigtilefloor.png)

src/year2025/day9/bigtilefloor.png

3.78 KB
Loading

src/year2025/day9/day9.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env python3
2+
# [Day 9: Movie Theater](https://adventofcode.com/2025/day/9)
3+
4+
import atexit
5+
import time
6+
from argparse import ArgumentParser
7+
from operator import itemgetter
8+
from pathlib import Path
9+
10+
parser = ArgumentParser()
11+
parser.add_argument("-v", "--verbose", action="store_true")
12+
parser.add_argument("-t", "--test", action="store_true")
13+
parser.add_argument("--elapsed", action="store_true")
14+
parser.add_argument("--draw", action="store_true")
15+
parser.add_argument("filename", nargs="?", type=Path, default="input.txt")
16+
args = parser.parse_args()
17+
if args.test:
18+
args.filename = Path("test.txt")
19+
20+
data = args.filename.read_text()
21+
22+
if args.elapsed:
23+
start_time_ns = time.time_ns()
24+
atexit.register(lambda: print(f"elapsed: {(time.time_ns() - start_time_ns) / 1_000_000}ms"))
25+
26+
27+
def fill_polygon(vertices, width, height, draw_pixel):
28+
if not vertices:
29+
return
30+
31+
min_y = min(y for x, y in vertices)
32+
max_y = max(y for x, y in vertices)
33+
34+
min_y = max(0, min_y)
35+
max_y = min(height - 1, max_y)
36+
37+
num_vertices = len(vertices)
38+
39+
for y in range(min_y, max_y + 1):
40+
intersections = []
41+
42+
for i in range(num_vertices):
43+
p1 = vertices[i]
44+
p2 = vertices[(i + 1) % num_vertices]
45+
46+
y1, y2 = p1[1], p2[1]
47+
x1, x2 = p1[0], p2[0]
48+
49+
if (y1 <= y < y2) or (y2 <= y < y1):
50+
if y2 != y1:
51+
x_intersect = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
52+
intersections.append(x_intersect)
53+
54+
intersections.sort()
55+
56+
for i in range(0, len(intersections), 2):
57+
if i + 1 < len(intersections):
58+
start_x = int(intersections[i])
59+
end_x = int(intersections[i + 1])
60+
61+
for x in range(start_x, end_x):
62+
if 0 <= x < width and 0 <= y < height:
63+
draw_pixel(x, y)
64+
65+
66+
points = list(tuple(map(int, line.split(","))) for line in data.splitlines())
67+
68+
# coordinate compression
69+
coord_x = sorted(map(itemgetter(0), points))
70+
coord_y = sorted(map(itemgetter(1), points))
71+
72+
comp_x = {}
73+
for i, x in enumerate(coord_x, 1):
74+
comp_x[x] = i
75+
inv_x = dict((i, x) for x, i in comp_x.items())
76+
77+
comp_y = {}
78+
for i, y in enumerate(coord_y, 1):
79+
comp_y[y] = i
80+
inv_y = dict((i, y) for y, i in comp_y.items())
81+
82+
comp = list((comp_x[x], comp_y[y]) for x, y in points)
83+
84+
85+
grid = [["." for x in range(500)] for y in range(500)]
86+
87+
88+
def fill_grid(x, y):
89+
grid[y][x] = "X"
90+
91+
92+
fill_polygon(comp, 500, 500, fill_grid)
93+
94+
95+
for i in range(len(comp)):
96+
p1 = comp[i]
97+
p2 = comp[(i + 1) % len(comp)]
98+
if p1[0] == p2[0]:
99+
x = p2[0]
100+
y1 = min(p1[1], p2[1])
101+
y2 = max(p1[1], p2[1])
102+
for y in range(y1, y2 + 1):
103+
grid[y][x] = "#"
104+
elif p1[1] == p2[1]:
105+
y = p2[1]
106+
x1 = min(p1[0], p2[0])
107+
x2 = max(p1[0], p2[0])
108+
for x in range(x1, x2 + 1):
109+
grid[y][x] = "#"
110+
else:
111+
exit()
112+
113+
114+
max_area1 = 0
115+
max_area2 = 0
116+
rect = None
117+
for i in range(len(comp) - 1):
118+
for j in range(i + 1, len(comp)):
119+
p1 = comp[i]
120+
p2 = comp[j]
121+
122+
x1 = min(p1[0], p2[0])
123+
x2 = max(p1[0], p2[0])
124+
y1 = min(p1[1], p2[1])
125+
y2 = max(p1[1], p2[1])
126+
127+
area = (inv_x[x2] - inv_x[x1] + 1) * (inv_y[y2] - inv_y[y1] + 1)
128+
129+
# part one
130+
if area > max_area1:
131+
max_area1 = area
132+
133+
# part two
134+
if area > max_area2:
135+
contained = True
136+
for x in range(x1, x2 + 1):
137+
for y in range(y1, y2 + 1):
138+
if grid[y][x] == ".":
139+
contained = False
140+
break
141+
if not contained:
142+
break
143+
144+
if contained:
145+
max_area2 = area
146+
rect = (x1, x2, y1, y2)
147+
148+
149+
print(max_area1)
150+
print(max_area2)
151+
152+
153+
if args.draw:
154+
from PIL import Image
155+
156+
im = Image.new("RGB", (500, 500))
157+
158+
for y in range(500):
159+
for x in range(500):
160+
if grid[y][x] == "X":
161+
im.putpixel((x, y), (30, 220, 30))
162+
elif grid[y][x] == "#":
163+
im.putpixel((x, y), (220, 10, 10))
164+
165+
for x in range(rect[0], rect[1] + 1):
166+
for y in range(rect[2], rect[3] + 1):
167+
im.putpixel((x, y), (100, 100, 100))
168+
169+
for x, y in comp:
170+
im.putpixel((x, y), (220, 10, 10))
171+
172+
for i in range(1, 50):
173+
f = Path(f"im{i:02d}.png")
174+
if not f.is_file():
175+
break
176+
177+
im.save(f)

0 commit comments

Comments
 (0)