-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path__init__.py
More file actions
169 lines (126 loc) · 4.61 KB
/
__init__.py
File metadata and controls
169 lines (126 loc) · 4.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from typing import Dict, List, Tuple
from aocpy import BaseChallenge
LIT = "#"
UNLIT = "."
Point = Tuple[int, int]
Image = Dict[Point, str]
def parse(instr: str) -> Tuple[str, Image]:
algo, image = instr.strip().split("\n\n")
image_dict = {}
for y, line in enumerate(image.splitlines()):
for x, char in enumerate(line):
if char == LIT:
image_dict[(x, y)] = char
return algo, image_dict
def get_adjacent_points(center_point: Point) -> List[Point]:
x, y = center_point
return [
(x - 1, y - 1),
(x, y - 1),
(x + 1, y - 1),
(x - 1, y),
(x, y),
(x + 1, y),
(x - 1, y + 1),
(x, y + 1),
(x + 1, y + 1),
]
def enhance_n(image: Image, algorithm: str, n: int):
# one does not simply "enhance" the image...
for i in range(n):
min_x = min(x for x, _ in image)
max_x = max(x for x, _ in image)
min_y = min(y for _, y in image)
max_y = max(y for _, y in image)
changes = {}
for y in range(min_y - 2, max_y + 3):
for x in range(min_x - 2, max_x + 3):
p = (x, y)
n = 0
for point in get_adjacent_points(p):
px, py = point
is_lit = False
# If the first component of the algorithm (the one that's
# used when no lit pixels are present) is lit, we can
# assume that on every other iteration, the infinite dim
# pixels are going to alternate between being lit and dim,
# hence this weirdness.
if algorithm[0] == LIT and not (
min_x <= px <= max_x and min_y <= py <= max_y
):
is_lit = i % 2 != 0
else:
is_lit = image.get(point, UNLIT) == LIT
n = (n << 1) | (0b1 if is_lit else 0b0)
changes[p] = algorithm[n]
for point in changes:
change = changes[point]
if change == UNLIT and point in image:
del image[point]
elif change == LIT:
image[point] = LIT
class Challenge(BaseChallenge):
@staticmethod
def core(instr: str, n: int) -> int:
algorithm, image = parse(instr)
enhance_n(image, algorithm, n)
return len(image) # only lit pixels are included
@staticmethod
def one(instr: str) -> int:
return Challenge.core(instr, 2)
@staticmethod
def two(instr: str) -> int:
return Challenge.core(instr, 50)
@staticmethod
def vis(instr: str, output_dir: str):
from PIL import Image
import os
import subprocess
import sys
from aocpy.vis import SaveManager
import shutil
import copy
import io
COLOUR_BACKGROUND = tuple(bytes.fromhex("FFFFFF"))
COLOUR_DIM = tuple(bytes.fromhex("05445E"))
COLOUR_LIT = tuple(bytes.fromhex("189AB4"))
print("Generating data", flush=True)
algorithm, image = parse(instr)
images = []
for _ in range(50):
images.append(copy.copy(image))
enhance_n(image, algorithm, 1)
print("Generating frames", flush=True)
min_x = min(x for x, _ in image)
max_x = max(x for x, _ in image)
min_y = min(y for _, y in image)
max_y = max(y for _, y in image)
range_x = max_x - min_x
range_y = max_y - min_y
offset_x = -min_x
offset_y = -min_y
temp_dir = os.path.join(output_dir, "vis-temp")
try:
os.makedirs(temp_dir)
except FileExistsError:
pass
manager = SaveManager(temp_dir)
for frame in images:
img = Image.new("RGB", (range_x + 1, range_y + 1), COLOUR_DIM)
for pixel in frame:
x, y = pixel
img.putpixel((x + offset_x, y + offset_y), COLOUR_LIT)
manager.save(img)
print("Encoding frames", flush=True)
output_file = f"{output_dir}/out.mp4"
try:
os.remove(output_file)
except FileNotFoundError:
pass
subprocess.call(
["ffmpeg", "-framerate", "5", "-i", f"{output_dir}/vis-temp/frame_%04d.png", "-start_number", "0", "-c:v", "libx264", "-vf", "scale=iw*5:ih*5:flags=neighbor", "-r", "30", "-pix_fmt", "yuv420p", output_file],
stdout=sys.stdout,
stderr=subprocess.STDOUT,
)
print("Tidying up", flush=True)
shutil.rmtree(temp_dir)