claude-games 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/command.md +19 -0
- package/games/2048.py +239 -0
- package/games/dino.py +324 -0
- package/games/hangman.py +288 -0
- package/games/hogwarts.py +595 -0
- package/games/invaders.py +380 -0
- package/games/launch.sh +53 -0
- package/games/menu.py +276 -0
- package/games/notify.py +47 -0
- package/games/pacman.py +382 -0
- package/games/patrol.py +348 -0
- package/games/pokemon.py +658 -0
- package/games/snake.py +257 -0
- package/games/tetris.py +347 -0
- package/games/wordle.py +285 -0
- package/install.js +70 -0
- package/package.json +20 -0
package/games/snake.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Claude Snake - Play while Claude Code runs!"""
|
|
3
|
+
import curses
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
from collections import deque
|
|
7
|
+
from notify import init_notify_colors, check_task_done, draw_notification
|
|
8
|
+
|
|
9
|
+
def main(stdscr):
|
|
10
|
+
curses.curs_set(0)
|
|
11
|
+
stdscr.nodelay(True)
|
|
12
|
+
curses.start_color()
|
|
13
|
+
curses.use_default_colors()
|
|
14
|
+
curses.init_pair(1, curses.COLOR_GREEN, -1) # snake
|
|
15
|
+
curses.init_pair(2, curses.COLOR_RED, -1) # food
|
|
16
|
+
curses.init_pair(3, curses.COLOR_YELLOW, -1) # score
|
|
17
|
+
curses.init_pair(4, curses.COLOR_CYAN, -1) # border/title
|
|
18
|
+
curses.init_pair(5, curses.COLOR_WHITE, -1) # text
|
|
19
|
+
init_notify_colors()
|
|
20
|
+
|
|
21
|
+
HEAD_CHARS = {(0, 1): '>', (0, -1): '<', (-1, 0): '^', (1, 0): 'v'}
|
|
22
|
+
FOOD_CHAR = '*'
|
|
23
|
+
BODY_CHAR = '#'
|
|
24
|
+
|
|
25
|
+
high_score = 0
|
|
26
|
+
|
|
27
|
+
def play_game():
|
|
28
|
+
nonlocal high_score
|
|
29
|
+
height, width = stdscr.getmaxyx()
|
|
30
|
+
|
|
31
|
+
# Playfield: inside border
|
|
32
|
+
top, left = 3, 1
|
|
33
|
+
bottom = height - 2
|
|
34
|
+
right = width - 2
|
|
35
|
+
field_h = bottom - top - 1
|
|
36
|
+
field_w = right - left - 1
|
|
37
|
+
|
|
38
|
+
if field_h < 5 or field_w < 10:
|
|
39
|
+
stdscr.erase()
|
|
40
|
+
stdscr.addstr(0, 0, "Terminal too small!", curses.color_pair(2))
|
|
41
|
+
stdscr.refresh()
|
|
42
|
+
time.sleep(2)
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
# Init snake in center
|
|
46
|
+
mid_y = top + field_h // 2
|
|
47
|
+
mid_x = left + field_w // 2
|
|
48
|
+
direction = (0, 1) # moving right
|
|
49
|
+
snake = deque([(mid_y, mid_x - 2), (mid_y, mid_x - 1), (mid_y, mid_x)])
|
|
50
|
+
snake_set = set(snake)
|
|
51
|
+
|
|
52
|
+
score = 0
|
|
53
|
+
base_speed = 0.12
|
|
54
|
+
game_over = False
|
|
55
|
+
paused = False
|
|
56
|
+
notify_timer = 0
|
|
57
|
+
|
|
58
|
+
def place_food():
|
|
59
|
+
while True:
|
|
60
|
+
fy = random.randint(top + 1, bottom - 1)
|
|
61
|
+
fx = random.randint(left + 1, right - 1)
|
|
62
|
+
if (fy, fx) not in snake_set:
|
|
63
|
+
return (fy, fx)
|
|
64
|
+
|
|
65
|
+
food = place_food()
|
|
66
|
+
|
|
67
|
+
def draw_border():
|
|
68
|
+
try:
|
|
69
|
+
stdscr.addstr(top, left, '+' + '-' * field_w + '+', curses.color_pair(4))
|
|
70
|
+
stdscr.addstr(bottom, left, '+' + '-' * field_w + '+', curses.color_pair(4))
|
|
71
|
+
for y in range(top + 1, bottom):
|
|
72
|
+
stdscr.addstr(y, left, '|', curses.color_pair(4))
|
|
73
|
+
stdscr.addstr(y, right, '|', curses.color_pair(4))
|
|
74
|
+
except curses.error:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
while True:
|
|
78
|
+
frame_start = time.time()
|
|
79
|
+
|
|
80
|
+
# Input
|
|
81
|
+
key = stdscr.getch()
|
|
82
|
+
while True:
|
|
83
|
+
k2 = stdscr.getch()
|
|
84
|
+
if k2 == -1:
|
|
85
|
+
break
|
|
86
|
+
key = k2
|
|
87
|
+
|
|
88
|
+
if key in (ord('q'), ord('Q')):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
if key in (ord('p'), ord('P')) and not game_over:
|
|
92
|
+
paused = not paused
|
|
93
|
+
|
|
94
|
+
if game_over:
|
|
95
|
+
if key in (ord(' '), curses.KEY_ENTER, 10, 13):
|
|
96
|
+
return True # restart
|
|
97
|
+
elapsed = time.time() - frame_start
|
|
98
|
+
if elapsed < 0.05:
|
|
99
|
+
time.sleep(0.05 - elapsed)
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if paused:
|
|
103
|
+
stdscr.erase()
|
|
104
|
+
title = "CLAUDE SNAKE"
|
|
105
|
+
try:
|
|
106
|
+
stdscr.addstr(0, max(0, (width - len(title)) // 2), title,
|
|
107
|
+
curses.color_pair(4) | curses.A_BOLD)
|
|
108
|
+
except curses.error:
|
|
109
|
+
pass
|
|
110
|
+
score_str = f"Score: {score} Hi: {high_score}"
|
|
111
|
+
try:
|
|
112
|
+
stdscr.addstr(1, max(0, (width - len(score_str)) // 2), score_str,
|
|
113
|
+
curses.color_pair(3) | curses.A_BOLD)
|
|
114
|
+
except curses.error:
|
|
115
|
+
pass
|
|
116
|
+
draw_border()
|
|
117
|
+
# Food
|
|
118
|
+
try:
|
|
119
|
+
stdscr.addstr(food[0], food[1], FOOD_CHAR, curses.color_pair(2) | curses.A_BOLD)
|
|
120
|
+
except curses.error:
|
|
121
|
+
pass
|
|
122
|
+
# Snake body
|
|
123
|
+
for i, (sy, sx) in enumerate(snake):
|
|
124
|
+
is_head = (i == len(snake) - 1)
|
|
125
|
+
try:
|
|
126
|
+
if is_head:
|
|
127
|
+
ch = HEAD_CHARS.get(direction, '>')
|
|
128
|
+
stdscr.addstr(sy, sx, ch, curses.color_pair(1) | curses.A_BOLD)
|
|
129
|
+
else:
|
|
130
|
+
stdscr.addstr(sy, sx, BODY_CHAR, curses.color_pair(1))
|
|
131
|
+
except curses.error:
|
|
132
|
+
pass
|
|
133
|
+
pause_msg = "PAUSED - P to resume"
|
|
134
|
+
try:
|
|
135
|
+
stdscr.addstr(height // 2, max(0, (width - len(pause_msg)) // 2),
|
|
136
|
+
pause_msg, curses.color_pair(3) | curses.A_BOLD)
|
|
137
|
+
except curses.error:
|
|
138
|
+
pass
|
|
139
|
+
stdscr.refresh()
|
|
140
|
+
time.sleep(0.05)
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
if check_task_done():
|
|
144
|
+
notify_timer = 80
|
|
145
|
+
|
|
146
|
+
# Direction
|
|
147
|
+
dy, dx = direction
|
|
148
|
+
if key == curses.KEY_UP and dy != 1:
|
|
149
|
+
direction = (-1, 0)
|
|
150
|
+
elif key == curses.KEY_DOWN and dy != -1:
|
|
151
|
+
direction = (1, 0)
|
|
152
|
+
elif key == curses.KEY_LEFT and dx != 1:
|
|
153
|
+
direction = (0, -1)
|
|
154
|
+
elif key == curses.KEY_RIGHT and dx != -1:
|
|
155
|
+
direction = (0, 1)
|
|
156
|
+
|
|
157
|
+
# Move
|
|
158
|
+
head_y, head_x = snake[-1]
|
|
159
|
+
ny = head_y + direction[0]
|
|
160
|
+
nx = head_x + direction[1]
|
|
161
|
+
|
|
162
|
+
# Collision check
|
|
163
|
+
if ny <= top or ny >= bottom or nx <= left or nx >= right:
|
|
164
|
+
game_over = True
|
|
165
|
+
elif (ny, nx) in snake_set:
|
|
166
|
+
game_over = True
|
|
167
|
+
else:
|
|
168
|
+
snake.append((ny, nx))
|
|
169
|
+
snake_set.add((ny, nx))
|
|
170
|
+
|
|
171
|
+
if (ny, nx) == food:
|
|
172
|
+
score += 10
|
|
173
|
+
if score > high_score:
|
|
174
|
+
high_score = score
|
|
175
|
+
food = place_food()
|
|
176
|
+
else:
|
|
177
|
+
removed = snake.popleft()
|
|
178
|
+
snake_set.discard(removed)
|
|
179
|
+
|
|
180
|
+
# Draw
|
|
181
|
+
stdscr.erase()
|
|
182
|
+
|
|
183
|
+
# Title
|
|
184
|
+
title = "CLAUDE SNAKE"
|
|
185
|
+
try:
|
|
186
|
+
stdscr.addstr(0, max(0, (width - len(title)) // 2), title,
|
|
187
|
+
curses.color_pair(4) | curses.A_BOLD)
|
|
188
|
+
except curses.error:
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
score_str = f"Score: {score} Hi: {high_score}"
|
|
192
|
+
try:
|
|
193
|
+
stdscr.addstr(1, max(0, (width - len(score_str)) // 2), score_str,
|
|
194
|
+
curses.color_pair(3) | curses.A_BOLD)
|
|
195
|
+
except curses.error:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
draw_border()
|
|
199
|
+
|
|
200
|
+
# Food
|
|
201
|
+
try:
|
|
202
|
+
stdscr.addstr(food[0], food[1], FOOD_CHAR, curses.color_pair(2) | curses.A_BOLD)
|
|
203
|
+
except curses.error:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
# Snake body
|
|
207
|
+
for i, (sy, sx) in enumerate(snake):
|
|
208
|
+
is_head = (i == len(snake) - 1)
|
|
209
|
+
try:
|
|
210
|
+
if is_head:
|
|
211
|
+
ch = HEAD_CHARS.get(direction, '>')
|
|
212
|
+
stdscr.addstr(sy, sx, ch, curses.color_pair(1) | curses.A_BOLD)
|
|
213
|
+
else:
|
|
214
|
+
stdscr.addstr(sy, sx, BODY_CHAR, curses.color_pair(1))
|
|
215
|
+
except curses.error:
|
|
216
|
+
pass
|
|
217
|
+
|
|
218
|
+
# Controls
|
|
219
|
+
ctrl = "Arrow keys: move P: pause Q: quit"
|
|
220
|
+
try:
|
|
221
|
+
stdscr.addstr(height - 1, max(0, (width - len(ctrl)) // 2), ctrl,
|
|
222
|
+
curses.color_pair(5) | curses.A_DIM)
|
|
223
|
+
except curses.error:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
if game_over:
|
|
227
|
+
msgs = ["GAME OVER!", f"Score: {score}", "SPACE to restart Q to quit"]
|
|
228
|
+
cy = height // 2 - 1
|
|
229
|
+
for i, msg in enumerate(msgs):
|
|
230
|
+
attr = curses.color_pair(2) | curses.A_BOLD if i == 0 else curses.color_pair(3)
|
|
231
|
+
try:
|
|
232
|
+
stdscr.addstr(cy + i, max(0, (width - len(msg)) // 2), msg, attr)
|
|
233
|
+
except curses.error:
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
notify_timer = draw_notification(stdscr, height, width, notify_timer)
|
|
237
|
+
|
|
238
|
+
stdscr.refresh()
|
|
239
|
+
|
|
240
|
+
# Speed: faster as snake grows
|
|
241
|
+
speed = max(0.05, base_speed - len(snake) * 0.002)
|
|
242
|
+
elapsed = time.time() - frame_start
|
|
243
|
+
if elapsed < speed:
|
|
244
|
+
time.sleep(speed - elapsed)
|
|
245
|
+
|
|
246
|
+
# Game loop
|
|
247
|
+
while True:
|
|
248
|
+
result = play_game()
|
|
249
|
+
if not result:
|
|
250
|
+
break
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def run():
|
|
254
|
+
curses.wrapper(main)
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
run()
|
package/games/tetris.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Claude Tetris - Play while Claude Code runs!"""
|
|
3
|
+
import curses
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
import copy
|
|
7
|
+
from notify import init_notify_colors, check_task_done, draw_notification
|
|
8
|
+
|
|
9
|
+
# Tetrominoes: each is a list of rotations, each rotation is a list of (row, col) offsets
|
|
10
|
+
PIECES = {
|
|
11
|
+
'I': [[(0,0),(0,1),(0,2),(0,3)], [(0,0),(1,0),(2,0),(3,0)]],
|
|
12
|
+
'O': [[(0,0),(0,1),(1,0),(1,1)]],
|
|
13
|
+
'T': [[(0,0),(0,1),(0,2),(1,1)], [(0,0),(1,0),(2,0),(1,1)],
|
|
14
|
+
[(1,0),(1,1),(1,2),(0,1)], [(0,1),(1,1),(2,1),(1,0)]],
|
|
15
|
+
'S': [[(0,1),(0,2),(1,0),(1,1)], [(0,0),(1,0),(1,1),(2,1)]],
|
|
16
|
+
'Z': [[(0,0),(0,1),(1,1),(1,2)], [(0,1),(1,0),(1,1),(2,0)]],
|
|
17
|
+
'J': [[(0,0),(1,0),(1,1),(1,2)], [(0,0),(0,1),(1,0),(2,0)],
|
|
18
|
+
[(0,0),(0,1),(0,2),(1,2)], [(0,0),(1,0),(2,0),(2,-1)]],
|
|
19
|
+
'L': [[(0,2),(1,0),(1,1),(1,2)], [(0,0),(1,0),(2,0),(2,1)],
|
|
20
|
+
[(0,0),(0,1),(0,2),(1,0)], [(0,0),(0,1),(1,1),(2,1)]],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
PIECE_COLORS = {'I': 1, 'O': 2, 'T': 3, 'S': 4, 'Z': 5, 'J': 6, 'L': 7}
|
|
24
|
+
PIECE_NAMES = list(PIECES.keys())
|
|
25
|
+
|
|
26
|
+
BOARD_W = 10
|
|
27
|
+
BOARD_H = 20
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main(stdscr):
|
|
31
|
+
curses.curs_set(0)
|
|
32
|
+
stdscr.nodelay(True)
|
|
33
|
+
curses.start_color()
|
|
34
|
+
curses.use_default_colors()
|
|
35
|
+
curses.init_pair(1, curses.COLOR_CYAN, -1) # I
|
|
36
|
+
curses.init_pair(2, curses.COLOR_YELLOW, -1) # O
|
|
37
|
+
curses.init_pair(3, curses.COLOR_MAGENTA, -1) # T
|
|
38
|
+
curses.init_pair(4, curses.COLOR_GREEN, -1) # S
|
|
39
|
+
curses.init_pair(5, curses.COLOR_RED, -1) # Z
|
|
40
|
+
curses.init_pair(6, curses.COLOR_BLUE, -1) # J
|
|
41
|
+
curses.init_pair(7, curses.COLOR_WHITE, -1) # L
|
|
42
|
+
curses.init_pair(8, curses.COLOR_WHITE, -1) # border/text
|
|
43
|
+
init_notify_colors()
|
|
44
|
+
|
|
45
|
+
def new_bag():
|
|
46
|
+
bag = list(PIECE_NAMES)
|
|
47
|
+
random.shuffle(bag)
|
|
48
|
+
return bag
|
|
49
|
+
|
|
50
|
+
def play_game():
|
|
51
|
+
board = [[0] * BOARD_W for _ in range(BOARD_H)]
|
|
52
|
+
board_colors = [[0] * BOARD_W for _ in range(BOARD_H)]
|
|
53
|
+
|
|
54
|
+
bag = new_bag()
|
|
55
|
+
next_bag = new_bag()
|
|
56
|
+
score = 0
|
|
57
|
+
lines = 0
|
|
58
|
+
level = 1
|
|
59
|
+
game_over = False
|
|
60
|
+
paused = False
|
|
61
|
+
notify_timer = 0
|
|
62
|
+
|
|
63
|
+
def next_piece_name():
|
|
64
|
+
nonlocal bag, next_bag
|
|
65
|
+
if not bag:
|
|
66
|
+
bag = next_bag
|
|
67
|
+
next_bag = new_bag()
|
|
68
|
+
return bag.pop()
|
|
69
|
+
|
|
70
|
+
cur_name = next_piece_name()
|
|
71
|
+
nxt_name = next_piece_name()
|
|
72
|
+
cur_rot = 0
|
|
73
|
+
cur_r = 0
|
|
74
|
+
cur_c = BOARD_W // 2 - 1
|
|
75
|
+
|
|
76
|
+
drop_timer = 0.0
|
|
77
|
+
last_time = time.time()
|
|
78
|
+
|
|
79
|
+
def get_cells(name, rot, r, c):
|
|
80
|
+
shape = PIECES[name][rot % len(PIECES[name])]
|
|
81
|
+
return [(r + dr, c + dc) for dr, dc in shape]
|
|
82
|
+
|
|
83
|
+
def fits(name, rot, r, c):
|
|
84
|
+
for cr, cc in get_cells(name, rot, r, c):
|
|
85
|
+
if cr < 0 or cr >= BOARD_H or cc < 0 or cc >= BOARD_W:
|
|
86
|
+
return False
|
|
87
|
+
if board[cr][cc]:
|
|
88
|
+
return False
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def lock_piece():
|
|
92
|
+
nonlocal score, lines, level, game_over, cur_name, nxt_name, cur_rot, cur_r, cur_c
|
|
93
|
+
color = PIECE_COLORS[cur_name]
|
|
94
|
+
for cr, cc in get_cells(cur_name, cur_rot, cur_r, cur_c):
|
|
95
|
+
if cr < 0:
|
|
96
|
+
game_over = True
|
|
97
|
+
return
|
|
98
|
+
board[cr][cc] = 1
|
|
99
|
+
board_colors[cr][cc] = color
|
|
100
|
+
|
|
101
|
+
# Clear lines
|
|
102
|
+
cleared = 0
|
|
103
|
+
r = BOARD_H - 1
|
|
104
|
+
while r >= 0:
|
|
105
|
+
if all(board[r]):
|
|
106
|
+
del board[r]
|
|
107
|
+
del board_colors[r]
|
|
108
|
+
board.insert(0, [0] * BOARD_W)
|
|
109
|
+
board_colors.insert(0, [0] * BOARD_W)
|
|
110
|
+
cleared += 1
|
|
111
|
+
else:
|
|
112
|
+
r -= 1
|
|
113
|
+
|
|
114
|
+
if cleared > 0:
|
|
115
|
+
lines += cleared
|
|
116
|
+
points = [0, 100, 300, 500, 800]
|
|
117
|
+
score += points[min(cleared, 4)] * level
|
|
118
|
+
level = lines // 10 + 1
|
|
119
|
+
|
|
120
|
+
# Spawn next
|
|
121
|
+
cur_name = nxt_name
|
|
122
|
+
nxt_name = next_piece_name()
|
|
123
|
+
cur_rot = 0
|
|
124
|
+
cur_r = 0
|
|
125
|
+
cur_c = BOARD_W // 2 - 1
|
|
126
|
+
if not fits(cur_name, cur_rot, cur_r, cur_c):
|
|
127
|
+
game_over = True
|
|
128
|
+
|
|
129
|
+
def ghost_y():
|
|
130
|
+
gy = cur_r
|
|
131
|
+
while fits(cur_name, cur_rot, gy + 1, cur_c):
|
|
132
|
+
gy += 1
|
|
133
|
+
return gy
|
|
134
|
+
|
|
135
|
+
while True:
|
|
136
|
+
now = time.time()
|
|
137
|
+
dt = now - last_time
|
|
138
|
+
last_time = now
|
|
139
|
+
|
|
140
|
+
# Input
|
|
141
|
+
key = stdscr.getch()
|
|
142
|
+
while True:
|
|
143
|
+
k2 = stdscr.getch()
|
|
144
|
+
if k2 == -1:
|
|
145
|
+
break
|
|
146
|
+
key = k2
|
|
147
|
+
|
|
148
|
+
if key in (ord('q'), ord('Q')):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
if game_over:
|
|
152
|
+
if key in (ord(' '), curses.KEY_ENTER, 10, 13):
|
|
153
|
+
return True
|
|
154
|
+
height, width = stdscr.getmaxyx()
|
|
155
|
+
stdscr.erase()
|
|
156
|
+
msgs = ["GAME OVER!", f"Score: {score} Lines: {lines} Level: {level}",
|
|
157
|
+
"SPACE to restart Q to quit"]
|
|
158
|
+
cy = height // 2 - 1
|
|
159
|
+
for i, msg in enumerate(msgs):
|
|
160
|
+
attr = curses.color_pair(5) | curses.A_BOLD if i == 0 else curses.color_pair(8)
|
|
161
|
+
try:
|
|
162
|
+
stdscr.addstr(cy + i, max(0, (width - len(msg)) // 2), msg, attr)
|
|
163
|
+
except curses.error:
|
|
164
|
+
pass
|
|
165
|
+
stdscr.refresh()
|
|
166
|
+
time.sleep(0.05)
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
if key == ord('p') or key == ord('P'):
|
|
170
|
+
paused = not paused
|
|
171
|
+
|
|
172
|
+
if not paused:
|
|
173
|
+
if key == curses.KEY_LEFT and fits(cur_name, cur_rot, cur_r, cur_c - 1):
|
|
174
|
+
cur_c -= 1
|
|
175
|
+
elif key == curses.KEY_RIGHT and fits(cur_name, cur_rot, cur_r, cur_c + 1):
|
|
176
|
+
cur_c += 1
|
|
177
|
+
elif key == curses.KEY_UP:
|
|
178
|
+
new_rot = (cur_rot + 1) % len(PIECES[cur_name])
|
|
179
|
+
if fits(cur_name, new_rot, cur_r, cur_c):
|
|
180
|
+
cur_rot = new_rot
|
|
181
|
+
elif fits(cur_name, new_rot, cur_r, cur_c - 1):
|
|
182
|
+
cur_c -= 1
|
|
183
|
+
cur_rot = new_rot
|
|
184
|
+
elif fits(cur_name, new_rot, cur_r, cur_c + 1):
|
|
185
|
+
cur_c += 1
|
|
186
|
+
cur_rot = new_rot
|
|
187
|
+
elif key == curses.KEY_DOWN:
|
|
188
|
+
if fits(cur_name, cur_rot, cur_r + 1, cur_c):
|
|
189
|
+
cur_r += 1
|
|
190
|
+
score += 1
|
|
191
|
+
elif key == ord(' '):
|
|
192
|
+
while fits(cur_name, cur_rot, cur_r + 1, cur_c):
|
|
193
|
+
cur_r += 1
|
|
194
|
+
score += 2
|
|
195
|
+
lock_piece()
|
|
196
|
+
|
|
197
|
+
if check_task_done():
|
|
198
|
+
notify_timer = 80
|
|
199
|
+
|
|
200
|
+
# Gravity
|
|
201
|
+
drop_speed = max(0.05, 0.5 - (level - 1) * 0.04)
|
|
202
|
+
drop_timer += dt
|
|
203
|
+
if drop_timer >= drop_speed:
|
|
204
|
+
drop_timer = 0
|
|
205
|
+
if fits(cur_name, cur_rot, cur_r + 1, cur_c):
|
|
206
|
+
cur_r += 1
|
|
207
|
+
else:
|
|
208
|
+
lock_piece()
|
|
209
|
+
|
|
210
|
+
# Draw
|
|
211
|
+
height, width = stdscr.getmaxyx()
|
|
212
|
+
stdscr.erase()
|
|
213
|
+
|
|
214
|
+
# Board position
|
|
215
|
+
bx = max(0, (width - BOARD_W * 2 - 2) // 2 - 5)
|
|
216
|
+
by = max(0, (height - BOARD_H - 2) // 2)
|
|
217
|
+
|
|
218
|
+
# Title
|
|
219
|
+
title = "CLAUDE TETRIS"
|
|
220
|
+
try:
|
|
221
|
+
stdscr.addstr(0, max(0, (width - len(title)) // 2), title,
|
|
222
|
+
curses.color_pair(1) | curses.A_BOLD)
|
|
223
|
+
except curses.error:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
# Board border
|
|
227
|
+
for r in range(BOARD_H + 2):
|
|
228
|
+
y = by + r
|
|
229
|
+
if y >= height:
|
|
230
|
+
break
|
|
231
|
+
if r == 0 or r == BOARD_H + 1:
|
|
232
|
+
line = "+" + "--" * BOARD_W + "+"
|
|
233
|
+
try:
|
|
234
|
+
stdscr.addstr(y, bx, line, curses.color_pair(8) | curses.A_DIM)
|
|
235
|
+
except curses.error:
|
|
236
|
+
pass
|
|
237
|
+
else:
|
|
238
|
+
try:
|
|
239
|
+
stdscr.addstr(y, bx, "|", curses.color_pair(8) | curses.A_DIM)
|
|
240
|
+
stdscr.addstr(y, bx + BOARD_W * 2 + 1, "|", curses.color_pair(8) | curses.A_DIM)
|
|
241
|
+
except curses.error:
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
# Board cells
|
|
245
|
+
for r in range(BOARD_H):
|
|
246
|
+
for c in range(BOARD_W):
|
|
247
|
+
if board[r][c]:
|
|
248
|
+
y = by + r + 1
|
|
249
|
+
x = bx + 1 + c * 2
|
|
250
|
+
if 0 <= y < height and 0 <= x < width - 1:
|
|
251
|
+
try:
|
|
252
|
+
stdscr.addstr(y, x, "[]",
|
|
253
|
+
curses.color_pair(board_colors[r][c]) | curses.A_BOLD)
|
|
254
|
+
except curses.error:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
if not game_over:
|
|
258
|
+
# Ghost piece
|
|
259
|
+
gy = ghost_y()
|
|
260
|
+
for gr, gc in get_cells(cur_name, cur_rot, gy, cur_c):
|
|
261
|
+
y = by + gr + 1
|
|
262
|
+
x = bx + 1 + gc * 2
|
|
263
|
+
if 0 <= y < height and 0 <= x < width - 1 and 0 <= gr < BOARD_H:
|
|
264
|
+
try:
|
|
265
|
+
stdscr.addstr(y, x, "..", curses.color_pair(8) | curses.A_DIM)
|
|
266
|
+
except curses.error:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
# Current piece
|
|
270
|
+
color = PIECE_COLORS[cur_name]
|
|
271
|
+
for cr, cc in get_cells(cur_name, cur_rot, cur_r, cur_c):
|
|
272
|
+
y = by + cr + 1
|
|
273
|
+
x = bx + 1 + cc * 2
|
|
274
|
+
if 0 <= y < height and 0 <= x < width - 1 and cr >= 0:
|
|
275
|
+
try:
|
|
276
|
+
stdscr.addstr(y, x, "[]", curses.color_pair(color) | curses.A_BOLD)
|
|
277
|
+
except curses.error:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
# Side panel
|
|
281
|
+
sx = bx + BOARD_W * 2 + 4
|
|
282
|
+
sy = by + 1
|
|
283
|
+
|
|
284
|
+
info = [
|
|
285
|
+
("SCORE", str(score)),
|
|
286
|
+
("LINES", str(lines)),
|
|
287
|
+
("LEVEL", str(level)),
|
|
288
|
+
]
|
|
289
|
+
for i, (label, val) in enumerate(info):
|
|
290
|
+
y = sy + i * 2
|
|
291
|
+
if y < height:
|
|
292
|
+
try:
|
|
293
|
+
stdscr.addstr(y, sx, label, curses.color_pair(8) | curses.A_DIM)
|
|
294
|
+
stdscr.addstr(y + 1, sx, val, curses.color_pair(2) | curses.A_BOLD)
|
|
295
|
+
except curses.error:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# Next piece
|
|
299
|
+
ny = sy + 7
|
|
300
|
+
try:
|
|
301
|
+
stdscr.addstr(ny, sx, "NEXT", curses.color_pair(8) | curses.A_DIM)
|
|
302
|
+
except curses.error:
|
|
303
|
+
pass
|
|
304
|
+
nxt_color = PIECE_COLORS[nxt_name]
|
|
305
|
+
for dr, dc in PIECES[nxt_name][0]:
|
|
306
|
+
y = ny + 1 + dr
|
|
307
|
+
x = sx + dc * 2
|
|
308
|
+
if 0 <= y < height and 0 <= x < width - 1:
|
|
309
|
+
try:
|
|
310
|
+
stdscr.addstr(y, x, "[]", curses.color_pair(nxt_color) | curses.A_BOLD)
|
|
311
|
+
except curses.error:
|
|
312
|
+
pass
|
|
313
|
+
|
|
314
|
+
# Controls
|
|
315
|
+
ctrl_y = ny + 5
|
|
316
|
+
controls = ["<- -> Move", "UP Rotate", "DOWN Soft drop", "SPACE Hard drop",
|
|
317
|
+
"P Pause", "Q Quit"]
|
|
318
|
+
for i, c in enumerate(controls):
|
|
319
|
+
if ctrl_y + i < height:
|
|
320
|
+
try:
|
|
321
|
+
stdscr.addstr(ctrl_y + i, sx, c, curses.color_pair(8) | curses.A_DIM)
|
|
322
|
+
except curses.error:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
if paused:
|
|
326
|
+
msg = " PAUSED - P to resume "
|
|
327
|
+
try:
|
|
328
|
+
stdscr.addstr(height // 2, max(0, (width - len(msg)) // 2),
|
|
329
|
+
msg, curses.color_pair(2) | curses.A_BOLD)
|
|
330
|
+
except curses.error:
|
|
331
|
+
pass
|
|
332
|
+
|
|
333
|
+
notify_timer = draw_notification(stdscr, height, width, notify_timer)
|
|
334
|
+
stdscr.refresh()
|
|
335
|
+
time.sleep(0.05)
|
|
336
|
+
|
|
337
|
+
while True:
|
|
338
|
+
result = play_game()
|
|
339
|
+
if not result:
|
|
340
|
+
break
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def run():
|
|
344
|
+
curses.wrapper(main)
|
|
345
|
+
|
|
346
|
+
if __name__ == "__main__":
|
|
347
|
+
run()
|