-
Notifications
You must be signed in to change notification settings - Fork 0
/
GPT4Tetris.py
502 lines (437 loc) · 20.7 KB
/
GPT4Tetris.py
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
"""
Barnacules Fully GPT4 Code Generated Tetris Clone v1.0
Author: Barnacules
Date: 10/16/2023
Description:
This code is 100% generated by GPT4 using ChatGPT + Vision to upload screenshots to trouble shoot bugs and fix them.
None of the code in this file is generated by me and any corrections or additions to the code were made with ChatGPT
by asking it to add the functionality or describing the bug or uploading a screen shot of a bug to fix it. This
demonstrates just how crazy AI is getting when you can get it to build you an entire game from scratch just by having
a conversation with it and showing it some pictures with no applied coding knowledge needed.
"""
import pygame
import random
import numpy as np
# CONSTANTS
SCREEN_WIDTH = 300
SCREEN_HEIGHT = 600
BLOCK_SIZE = 30
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
MIN_COLOR_VALUE = 50
FLASH_DURATION = 150
FLASH_INTENSITY = [0, 50, 100, 150, 200] # Intensity based on lines cleared: 1 to 4
# Initialize Pygame
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Barnacules GPT4 Tetris Clone v1.0")
clock = pygame.time.Clock()
# Tetromino shapes
SHAPES = [
[[1, 1, 1, 1]],
[[1, 1], [1, 1]],
[[1, 1, 1], [0, 1, 0]],
[[1, 1, 1], [1, 0, 0]],
[[1, 1, 1], [0, 0, 1]],
[[1, 1, 0], [0, 1, 1]],
[[0, 1, 1], [1, 1, 0]]
]
def generate_stereo_sound(frequency, duration, volume=0.5):
"""Generate a simple stereo sine wave sound."""
sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False)
sound_data = np.sin(frequency * t * 2 * np.pi)
sound_data = (volume * sound_data * (2**15 - 1) / np.max(np.abs(sound_data))).astype(np.int16)
stereo_sound_data = np.vstack([sound_data, sound_data]).T
stereo_sound_data = np.ascontiguousarray(stereo_sound_data)
return pygame.sndarray.make_sound(stereo_sound_data)
def generate_ramping_sound(lines_cleared, duration=1.0, volume=0.5):
"""Generate a ramping tone sound based on lines cleared."""
sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False)
start_freq = 220 + lines_cleared * 100
end_freq = start_freq + lines_cleared * 200
frequencies = np.linspace(start_freq, end_freq, int(sample_rate * duration))
sound_data = np.sin(frequencies * t * 2 * np.pi)
sound_data = (volume * sound_data * (2**15 - 1) / np.max(np.abs(sound_data))).astype(np.int16)
stereo_sound_data = np.vstack([sound_data, sound_data]).T
stereo_sound_data = np.ascontiguousarray(stereo_sound_data)
return pygame.sndarray.make_sound(stereo_sound_data)
# Basic sound effects
beep_sound = generate_stereo_sound(440, 0.2)
boop_sound = generate_stereo_sound(220, 0.4)
class GameOverAnimation:
"""Handles the game over animation logic."""
def __init__(self):
self.font = pygame.font.SysFont(None, 100)
self.text = "GAME OVER"
self.color = (255, 255, 255)
self.angle = 0
self.small_font = pygame.font.SysFont(None, 20)
self.instruction_text = "Press ESC to exit or P to play again"
self.instruction_color = (255, 102, 102)
def draw(self, surface):
"""Draw the game over animation on the given surface."""
text_surface = self.font.render(self.text, True, self.color)
shadow_surface = self.font.render(self.text, True, (100, 100, 100))
rotated_surface = pygame.transform.rotate(text_surface, self.angle)
rotated_rect = rotated_surface.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50))
shadow_rotated_surface = pygame.transform.rotate(shadow_surface, self.angle)
shadow_rotated_rect = shadow_rotated_surface.get_rect(center=(SCREEN_WIDTH // 2 + 5, SCREEN_HEIGHT // 2 - 45))
surface.blit(shadow_rotated_surface, shadow_rotated_rect.topleft)
surface.blit(rotated_surface, rotated_rect.topleft)
instruction_surface = self.small_font.render(self.instruction_text, True, self.instruction_color)
instruction_rect = instruction_surface.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 100))
surface.blit(instruction_surface, instruction_rect.topleft)
def update(self):
"""Update the rotation angle of the game over text."""
self.angle += 1
class Board:
"""Represents the Tetris board."""
def __init__(self, width, height):
self.width = width
self.height = height
self.board = [[0 for _ in range(width)] for _ in range(height)]
def place_tetromino(self, tetromino):
"""Place the tetromino on the board."""
for row in range(len(tetromino.shape)):
for col in range(len(tetromino.shape[row])):
if tetromino.shape[row][col]:
self.board[tetromino.y + row][tetromino.x + col] = tetromino.color
def is_collision(self, tetromino, dx=0, dy=0):
"""Check if the tetromino collides with the board or is out of bounds."""
for row in range(len(tetromino.shape)):
for col in range(len(tetromino.shape[row])):
if tetromino.shape[row][col]:
if tetromino.x + col + dx < 0 or tetromino.x + col + dx >= self.width:
return True
if tetromino.y + row + dy < 0 or tetromino.y + row + dy >= self.height:
return True
if self.board[tetromino.y + row + dy][tetromino.x + col + dx] != 0:
return True
return False
def clear_lines(self):
"""Clear filled lines and return the number of lines cleared."""
lines_cleared = 0
for row in range(self.height - 1, -1, -1):
if all(self.board[row]):
del self.board[row]
self.board.insert(0, [0 for _ in range(self.width)])
lines_cleared += 1
return lines_cleared
def draw(self, surface):
"""Draw the board."""
for row in range(self.height):
for col in range(self.width):
if self.board[row][col]:
color = self.board[row][col]
pygame.draw.rect(surface, color,
(col * BLOCK_SIZE, row * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))
border_color = tuple(max(c - 50, 0) for c in color)
pygame.draw.line(surface, border_color,
(col * BLOCK_SIZE, row * BLOCK_SIZE),
((col + 1) * BLOCK_SIZE, row * BLOCK_SIZE), 2)
pygame.draw.line(surface, border_color,
(col * BLOCK_SIZE, row * BLOCK_SIZE),
(col * BLOCK_SIZE, (row + 1) * BLOCK_SIZE), 2)
def is_game_over(self):
"""Check if the game is over."""
for col in range(self.width):
if self.board[0][col] != 0:
return True
return False
class Tetromino:
"""Represents a Tetromino piece."""
def __init__(self, initial_shape=None):
self.shape = initial_shape if initial_shape else random.choice(SHAPES)
self.color = (
random.randint(MIN_COLOR_VALUE, 255),
random.randint(MIN_COLOR_VALUE, 255),
random.randint(MIN_COLOR_VALUE, 255)
)
self.x = SCREEN_WIDTH // BLOCK_SIZE // 2 - len(self.shape[0]) // 2
self.y = 0
def can_move_to(self, dx=0, dy=0):
"""Check if the tetromino can be moved to the specified offset."""
for row in range(len(self.shape)):
for col in range(len(self.shape[row])):
if self.shape[row][col]:
new_x = self.x + col + dx
new_y = self.y + row + dy
if new_x < 0 or new_x >= board.width:
return False
if new_y < 0 or new_y >= board.height:
return False
if board.board[new_y][new_x] != 0:
return False
return True
def can_slide(self, dx=0):
"""Check if the tetromino can slide under another block."""
return self.can_move_to(dx=dx, dy=1)
def can_slide_under(self, dx=0):
"""Check if there is space below the tetromino for it to slide under another block."""
if self.can_move_to(dy=1):
return False
return self.can_move_to(dx=dx, dy=1)
def rotate(self):
"""Rotate the tetromino."""
new_shape = list(zip(*self.shape[::-1]))
old_shape = self.shape
self.shape = new_shape
# Wall-kick logic
if not self.can_move_to():
if self.can_move_to(dx=1):
self.x += 1
elif self.can_move_to(dx=-1):
self.x -= 1
elif self.can_move_to(dx=2):
self.x += 2
elif self.can_move_to(dx=-2):
self.x -= 2
else:
self.shape = old_shape
def place_and_reset(self):
"""Place the tetromino on the board and generate a new one."""
global next_tetromino
board.place_tetromino(self)
self.shape = next_tetromino.shape
self.color = next_tetromino.color
self.x = SCREEN_WIDTH // BLOCK_SIZE // 2 - len(self.shape[0]) // 2
self.y = 0
next_tetromino_shape = random.choice(SHAPES)
next_tetromino = Tetromino(next_tetromino_shape)
beep_sound.play()
def move_down(self):
"""Move the tetromino down."""
if self.can_move_to(dy=1):
self.y += 1
else:
self.place_and_reset()
def drop(self):
"""Drop the tetromino to its final resting place."""
while self.can_move_to(dy=1):
self.y += 1
self.place_and_reset()
def move_left(self):
"""Move the tetromino to the left."""
if self.can_move_to(dx=-1) or self.can_slide_under(dx=-1):
self.x -= 1
def move_right(self):
"""Move the tetromino to the right."""
if self.can_move_to(dx=1) or self.can_slide_under(dx=1):
self.x += 1
def get_drop_position(self):
"""Get the y position where the tetromino would land if dropped."""
drop_y = self.y
temp_tetromino = Tetromino(self.shape)
temp_tetromino.x = self.x
temp_tetromino.y = self.y
while not board.is_collision(temp_tetromino, dy=1):
temp_tetromino.y += 1
drop_y = temp_tetromino.y
return drop_y
def draw(self, surface, ghost=False):
"""Draw the tetromino on the given surface."""
color = self.color
if ghost:
alpha_color = (color[0] // 2, color[1] // 2, color[2] // 2, 127) # Half brightness and 50% transparent
ghost_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA) # Create a transparent surface
target_surface = ghost_surface # Draw on the ghost surface
else:
alpha_color = color
target_surface = surface # Draw on the main surface
for row in range(len(self.shape)):
for col in range(len(self.shape[row])):
if self.shape[row][col]:
pygame.draw.rect(target_surface, alpha_color, (BLOCK_SIZE * (self.x + col), BLOCK_SIZE * (self.y + row), BLOCK_SIZE, BLOCK_SIZE))
if not ghost:
border_color = tuple(max(c - 50, 0) for c in color)
pygame.draw.line(target_surface, border_color, (BLOCK_SIZE * (self.x + col), BLOCK_SIZE * (self.y + row)), (BLOCK_SIZE * (self.x + col + 1), BLOCK_SIZE * (self.y + row)), 2)
pygame.draw.line(target_surface, border_color, (BLOCK_SIZE * (self.x + col), BLOCK_SIZE * (self.y + row)), (BLOCK_SIZE * (self.x + col), BLOCK_SIZE * (self.y + row + 1)), 2)
if ghost:
surface.blit(ghost_surface, (0, 0)) # Blit the ghost surface onto the main surface
def draw_preview(self, surface, scale=0.50):
"""Draw a scaled-down preview of the tetromino."""
block_size_scaled = BLOCK_SIZE * scale
for row in range(len(self.shape)):
for col in range(len(self.shape[row])):
if self.shape[row][col]:
pygame.draw.rect(surface, self.color,
(block_size_scaled * col, block_size_scaled * row, block_size_scaled, block_size_scaled))
border_color = tuple(max(c - 50, 0) for c in self.color)
pygame.draw.line(surface, border_color,
(block_size_scaled * col, block_size_scaled * row),
(block_size_scaled * (col + 1), block_size_scaled * row), 1)
pygame.draw.line(surface, border_color,
(block_size_scaled * col, block_size_scaled * row),
(block_size_scaled * col, block_size_scaled * (row + 1)), 1)
class Scoreboard:
"""Represents a scoreboard for the game."""
def __init__(self, x, y):
self.score = 0
self.x = x
self.y = y
self.font = pygame.font.SysFont(None, 36)
def increase_score(self, lines_cleared):
"""Increase the score based on the number of lines cleared."""
self.score += lines_cleared * 10
def draw(self, surface):
"""Draw the scoreboard on the given surface."""
score_text = self.font.render(f"Score: {self.score}", True, WHITE)
surface.blit(score_text, (self.x, self.y))
class Star:
"""Represents a star for the background animation."""
def __init__(self):
self.x = random.randint(0, SCREEN_WIDTH)
self.y = random.randint(0, SCREEN_HEIGHT)
self.speed = random.uniform(1, 5)
self.size = self.speed / 5 * 3
intensity = int(255 * (self.speed / 5))
self.color = (intensity, intensity, intensity)
def update(self):
"""Update the position of the star."""
self.y += self.speed
if self.y > SCREEN_HEIGHT:
self.y = 0
self.x = random.randint(0, SCREEN_WIDTH)
self.speed = random.uniform(1, 5)
self.size = self.speed / 5 * 3
intensity = int(255 * (self.speed / 5))
self.color = (intensity, intensity, intensity)
def draw(self, surface):
"""Draw the star on the given surface."""
pygame.draw.circle(surface, self.color, (self.x, self.y), self.size)
class GameOverAnimation:
"""Represents the game over animation."""
def __init__(self):
self.font = pygame.font.SysFont(None, 100)
self.text = "GAME OVER"
self.color = WHITE
self.angle = 0
self.small_font = pygame.font.SysFont(None, 20)
self.instruction_text = "Press ESC to exit or P to play again"
self.instruction_color = (255, 102, 102)
def draw(self, surface):
"""Draw the game over text and instruction on the given surface."""
text_surface = self.font.render(self.text, True, self.color)
shadow_surface = self.font.render(self.text, True, (100, 100, 100))
rotated_surface = pygame.transform.rotate(text_surface, self.angle)
rotated_rect = rotated_surface.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50))
shadow_rotated_surface = pygame.transform.rotate(shadow_surface, self.angle)
shadow_rotated_rect = shadow_rotated_surface.get_rect(center=(SCREEN_WIDTH // 2 + 5, SCREEN_HEIGHT // 2 - 45))
surface.blit(shadow_rotated_surface, shadow_rotated_rect.topleft)
surface.blit(rotated_surface, rotated_rect.topleft)
instruction_surface = self.small_font.render(self.instruction_text, True, self.instruction_color)
instruction_rect = instruction_surface.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 100))
surface.blit(instruction_surface, instruction_rect.topleft)
def update(self):
"""Update the animation state."""
self.angle += 1
def game_loop():
"""The main game loop."""
global board, next_tetromino
board = Board(SCREEN_WIDTH // BLOCK_SIZE, SCREEN_HEIGHT // BLOCK_SIZE)
next_tetromino_shape = random.choice(SHAPES)
next_tetromino = Tetromino(next_tetromino_shape)
last_score_check = 0
current_tetromino = Tetromino(next_tetromino_shape)
scoreboard = Scoreboard(10, 10)
game_over = False
fall_timer = 0
fall_speed = 500
game_over_animation = GameOverAnimation()
flash_alpha = 0
flash_timer = 0
stars = [Star() for _ in range(100)]
clock = pygame.time.Clock()
while not game_over:
lines_cleared = board.clear_lines()
fall_timer += clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
restart_game = False
return game_over, restart_game
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_tetromino.move_left()
elif event.key == pygame.K_RIGHT:
current_tetromino.move_right()
elif event.key == pygame.K_DOWN:
current_tetromino.move_down()
elif event.key == pygame.K_UP:
current_tetromino.rotate()
elif event.key == pygame.K_SPACE:
current_tetromino.drop()
if board.is_collision(current_tetromino, dy=1):
board.place_tetromino(current_tetromino)
beep_sound.play()
current_tetromino = next_tetromino
next_tetromino_shape = random.choice(SHAPES)
next_tetromino = Tetromino(next_tetromino_shape)
if board.is_collision(current_tetromino):
game_over = True
if fall_timer >= fall_speed:
current_tetromino.move_down()
fall_timer = 0
screen.fill(BLACK)
for star in stars:
star.update()
star.draw(screen)
board.draw(screen)
current_tetromino.draw(screen)
scoreboard.draw(screen)
# Calculate ghost tetromino's drop position
drop_position = current_tetromino.get_drop_position()
ghost_tetromino = Tetromino(current_tetromino.shape)
ghost_tetromino.x = current_tetromino.x
ghost_tetromino.y = drop_position
ghost_tetromino.color = current_tetromino.color
# Draw the ghost tetromino
ghost_tetromino.draw(screen, ghost=True)
if lines_cleared > 0:
score_increase = lines_cleared ** 2 * 100
scoreboard.increase_score(score_increase)
fall_speed = max(100, fall_speed - 10 * lines_cleared)
flash_alpha = FLASH_INTENSITY[lines_cleared - 1]
generate_ramping_sound(lines_cleared, duration=1.0, volume=0.5).play()
flash_timer = pygame.time.get_ticks()
last_score_check = scoreboard.score
if flash_alpha > 0:
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
overlay.fill((255, 255, 255))
overlay.set_alpha(flash_alpha)
screen.blit(overlay, (0, 0))
if pygame.time.get_ticks() - flash_timer > FLASH_DURATION:
flash_alpha = 0
while game_over:
screen.fill(BLACK)
for star in stars:
star.update()
star.draw(screen)
game_over_animation.update()
game_over_animation.draw(screen)
clock.tick(60)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
restart_game = False
return game_over, restart_game
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
restart_game = False
return game_over, restart_game
elif event.key == pygame.K_p:
game_over = False
restart_game = True
return game_over, restart_game
pygame.display.flip()
return game_over, False
# Main execution
game_over = False
restart_game = False
while not game_over:
game_over, restart_game = game_loop()
if not restart_game:
break
pygame.quit()