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/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()
@@ -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()