Skip to content

Commit 38b29c3

Browse files
committed
animated tetris
1 parent 9ebed63 commit 38b29c3

File tree

6 files changed

+246
-135
lines changed

6 files changed

+246
-135
lines changed

2022/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Puzzle | Stars
2525
[Day 14: Regolith Reservoir](https://adventofcode.com/2022/day/14) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day14/day14.rs) [![Python](../scripts/assets/python.png)](../2022/day14/day14.py)
2626
[Day 15: Beacon Exclusion Zone](https://adventofcode.com/2022/day/15) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day15/day15.rs) [![Python](../scripts/assets/python.png)](../2022/day15/day15.py)
2727
[Day 16: Proboscidea Volcanium](https://adventofcode.com/2022/day/16) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day16/day16.rs)
28-
[Day 17: Pyroclastic Flow](https://adventofcode.com/2022/day/17) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day17/day17.rs) [![Python](../scripts/assets/python.png)](../2022/day17/day17.py)
28+
[Day 17: Pyroclastic Flow](https://adventofcode.com/2022/day/17) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day17/day17.rs) [![Python](../scripts/assets/python.png)](../2022/day17/day17.py) [🎁](../2022/day17/README.md)
2929
[Day 18: Boiling Boulders](https://adventofcode.com/2022/day/18) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day18/day18.rs)
3030
[Day 19: Not Enough Minerals](https://adventofcode.com/2022/day/19) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day19/day19.rs)
3131
[Day 20: Grove Positioning System](https://adventofcode.com/2022/day/20) | ⭐⭐ | [![Rust](../scripts/assets/rust.png)](../2022/day20/day20.rs)

2022/day17/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Tetris
2+
3+
![tetris](tetris.gif)

2022/day17/day17.py

Lines changed: 147 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33

44
# Nota: not sure to write it in Rust, nor to make it cleaner and more Pythonic...
55

6-
import sys
6+
import argparse
7+
import time
78
from pathlib import Path
89

9-
filename = ("test.txt" if sys.argv[1] == "-t" else sys.argv[1]) if len(sys.argv) > 1 else "input.txt"
10-
data = Path(filename).read_text()
11-
12-
rocks = [
10+
ROCKS = [
1311
[
1412
[1, 1, 1, 1],
1513
],
@@ -36,167 +34,183 @@
3634
]
3735

3836

39-
jets = data.strip()
37+
class Tetris:
38+
def __init__(self, jets):
4039

41-
cave = [
42-
[1, 1, 1, 1, 1, 1, 1], # the bottom
43-
]
44-
bottom = 0
40+
self.jets = jets.strip()
4541

46-
jet_count = 0
47-
rock_count = 0
42+
self.cave = [
43+
[1, 1, 1, 1, 1, 1, 1], # the bottom
44+
]
45+
self.bottom = 0
4846

47+
self.jet_count = 0
48+
self.rock_count = 0
4949

50-
def cave_height():
51-
"""Returns cave height, including the bottom line."""
52-
global bottom
53-
return len(cave) + bottom
50+
def cave_height(self):
51+
"""Returns cave height, including the bottom line."""
5452

53+
return len(self.cave) + self.bottom
5554

56-
def overlap(x, y, rock):
57-
width = len(rock[0])
58-
height = len(rock)
59-
for i in range(height):
60-
for j in range(width):
61-
if y + i < cave_height():
62-
if cave[y + i - bottom][x + j] != 0 and rock[i][j] == 1:
63-
return True
64-
return False
55+
def overlap(self, x, y, rock):
56+
width = len(rock[0])
57+
height = len(rock)
58+
for i in range(height):
59+
for j in range(width):
60+
if y + i < self.cave_height():
61+
if self.cave[y + i - self.bottom][x + j] != 0 and rock[i][j] == 1:
62+
return True
63+
return False
6564

65+
def fall(self):
6666

67-
def fall():
68-
global jet_count, rock_count, bottom
67+
rock = ROCKS[self.rock_count % len(ROCKS)]
68+
self.rock_count += 1
6969

70-
rock = rocks[rock_count % len(rocks)]
71-
rock_count += 1
70+
width = len(rock[0])
71+
height = len(rock)
7272

73-
width = len(rock[0])
74-
height = len(rock)
73+
y = self.cave_height() + 3
74+
x = 2
7575

76-
y = cave_height() + 3
77-
x = 2
76+
while True:
77+
# current jet of gas
78+
gas = self.jets[self.jet_count % len(self.jets)]
79+
self.jet_count += 1
7880

79-
while True:
80-
# current jet of gas
81-
gas = jets[jet_count % len(jets)]
82-
jet_count += 1
81+
# shift the rock if possible
82+
if gas == ">" and x + width + 1 <= 7 and not self.overlap(x + 1, y, rock):
83+
x += 1
84+
elif gas == "<" and x > 0 and not self.overlap(x - 1, y, rock):
85+
x -= 1
8386

84-
# shift the rock if possible
85-
if gas == ">" and x + width + 1 <= 7 and not overlap(x + 1, y, rock):
86-
x += 1
87-
elif gas == "<" and x > 0 and not overlap(x - 1, y, rock):
88-
x -= 1
87+
# rock falls if possible
88+
if not self.overlap(x, y - 1, rock):
89+
y -= 1
90+
else:
91+
break
8992

90-
# rock falls if possible
91-
if not overlap(x, y - 1, rock):
92-
y -= 1
93-
else:
94-
break
93+
for i in range(height):
94+
if y + i >= self.cave_height():
95+
self.cave.append([0, 0, 0, 0, 0, 0, 0])
96+
for j in range(width):
97+
if rock[i][j] == 1:
98+
self.cave[y + i - self.bottom][x + j] = 65 + (self.rock_count - 1) % len(ROCKS)
9599

96-
for i in range(height):
97-
if y + i >= cave_height():
98-
cave.append([0, 0, 0, 0, 0, 0, 0])
99-
for j in range(width):
100-
if rock[i][j] == 1:
101-
cave[y + i - bottom][x + j] = 65 + (rock_count - 1) % len(rocks)
100+
if len(self.cave) > 200:
101+
del self.cave[0:100]
102+
self.bottom += 100
102103

103-
if len(cave) > 200:
104-
del cave[0:100]
105-
bottom += 100
104+
def show(self, gameover: bool):
106105

106+
max_rows = 25
107+
print(end="\033[H\033[2J")
107108

108-
def show(rx=None, ry=None, rock=None):
109-
global bottom
109+
h = self.cave_height() + 2
110110

111-
h = cave_height() + 2
112-
if rx and ry and rock:
113-
width = len(rock[0])
114-
height = len(rock)
111+
for y in range(h - 1, self.bottom - 1, -1):
112+
s = "|" if y > 0 else "+"
115113

116-
h = max(h, ry + len(rock))
114+
for x in range(0, 7):
117115

118-
for y in range(h - 1, bottom - 1, -1):
119-
s = "|" if y > 0 else "+"
116+
if y < self.cave_height():
117+
c = self.cave[y - self.bottom][x]
118+
else:
119+
c = 0
120120

121-
for x in range(0, 7):
122-
if rx and ry and rock:
123-
if rx <= x < rx + width and ry <= y < ry + height:
124-
if rock[y - ry][x - rx] == 1:
125-
s += "@"
126-
continue
121+
if c == 0:
122+
s += " "
123+
elif c == 1:
124+
s += "--"
125+
else:
126+
s += "\033[" + str(c - 65 + 31) + "m██\033[0m"
127+
# s += chr(c)█
127128

128-
if y < cave_height():
129-
c = cave[y - bottom][x]
130-
else:
131-
c = 0
129+
s += "|" if y > 0 else "+"
132130

133-
if c == 0:
134-
s += "."
135-
elif c == 1:
136-
s += "-"
131+
if y == self.cave_height() - 1:
132+
print(f"{s} <= top ({y})")
137133
else:
138-
s += chr(c)
134+
print(s)
135+
136+
max_rows -= 1
137+
if max_rows == 0:
138+
time.sleep(0.1)
139+
break
140+
141+
if self.bottom != 0:
142+
print(f"({self.bottom} rows suppressed)")
143+
144+
def make_key(self):
145+
top = []
146+
mask = 0
147+
for y in range(len(self.cave) - 1, -1, -1):
148+
for i, c in enumerate(self.cave[y]):
149+
if c != 0:
150+
mask |= 1 << i
151+
top.append(mask)
152+
if mask == 127: # found a rock on all the 7 columns
153+
break
154+
return (self.rock_count % len(ROCKS), self.jet_count % len(self.jets), bytes(top))
155+
156+
def solve(self, show=False):
157+
158+
keys = {}
159+
heights = [0]
160+
start, end = None, None
161+
part1 = None
162+
163+
for n in range(1, 10000):
164+
self.fall()
165+
166+
if show:
167+
self.show(False)
168+
169+
if n == 2022:
170+
part1 = self.cave_height() - 1
171+
172+
if not end:
173+
heights.append(self.cave_height() - 1)
174+
key = self.make_key()
175+
if n > 2000 and key in keys:
176+
start = keys[key]
177+
end = n
178+
keys[key] = n
179+
180+
if part1 and end:
181+
if show:
182+
self.show(True)
183+
break
139184

140-
s += "|" if y > 0 else "+"
141-
142-
if y == cave_height() - 1:
143-
print(f"{s} <= top ({y})")
144185
else:
145-
print(s)
146-
147-
if bottom != 0:
148-
print(f"({bottom} rows suppressed)")
149-
150-
151-
def make_key():
152-
global rock_count, jet_count
153-
top = []
154-
mask = 0
155-
for y in range(len(cave) - 1, -1, -1):
156-
for i, c in enumerate(cave[y]):
157-
if c != 0:
158-
mask |= 1 << i
159-
top.append(mask)
160-
if mask == 127:
161-
break
162-
return (rock_count % len(rocks), jet_count % len(jets), bytes(top))
163-
164-
165-
keys = {}
166-
heights = [0]
167-
start, end = None, None
168-
part1 = None
186+
print("no solution")
187+
exit()
169188

170-
for n in range(1, 10000):
171-
fall()
189+
# part 1
172190

173-
if n == 2022:
174-
part1 = cave_height() - 1
191+
# part 2
192+
q, r = divmod(1_000_000_000_000 - start, end - start)
193+
part2 = heights[start + r] + q * (heights[end] - heights[start])
175194

176-
if not end:
177-
heights.append(cave_height() - 1)
178-
key = make_key()
179-
if n > 2000 and key in keys:
180-
start = keys[key]
181-
end = n
182-
keys[key] = n
195+
return part1, part2
183196

184-
if part1 and end:
185-
break
186197

187-
else:
188-
print("no solution")
189-
exit()
198+
def main():
199+
parser = argparse.ArgumentParser()
200+
parser.add_argument("-v", "--verbose", action="store_true")
201+
parser.add_argument("-t", "--test", action="store_true")
202+
parser.add_argument("filename", nargs="?", type=Path, default="input.txt")
203+
args = parser.parse_args()
204+
if args.test:
205+
args.filename = Path("test.txt")
190206

191-
# part 1
192-
print(part1)
207+
data = args.filename.read_text().strip()
193208

194-
# part 2
195-
q, r = divmod(1_000_000_000_000 - start, end - start)
196-
part2 = heights[start + r] + q * (heights[end] - heights[start])
197-
print(part2)
209+
tetris = Tetris(data)
210+
part1, part2 = tetris.solve(args.verbose)
211+
print(part1)
212+
print(part2)
198213

199214

200-
if filename == "test.txt":
201-
assert part1 == 3068
202-
assert part2 == 1514285714288
215+
if __name__ == "__main__":
216+
main()

2022/day17/tetris.gif

4.76 MB
Loading

0 commit comments

Comments
 (0)