本帖最後由 javacomhk 於 2025-4-1 11:10 編輯
編碼能力領先 OpenAI 、 Claude 及 Grok3
測試 Google Gemini Programming task 的 tetrasnake.py
Comparing it with Grok 3 code at https://x.ai/news/grok-3
Prompt:
Create a game that is a mixture of two classic games. Make it in pygame with sound effects and make it look pretty.- import pygame
- import random
- import sys # Needed for sys.exit()
- # --- Constants ---
- SCREEN_WIDTH = 400
- SCREEN_HEIGHT = 600
- GRID_SIZE = 30
- GRID_WIDTH = SCREEN_WIDTH // GRID_SIZE # Grid dimensions in cells
- GRID_HEIGHT = SCREEN_HEIGHT // GRID_SIZE
- # Center the grid horizontally
- BOARD_WIDTH = 10 # Width of the playable Tetris area
- BOARD_HEIGHT = 20 # Height of the playable Tetris area (visible)
- BOARD_X_OFFSET = (GRID_WIDTH - BOARD_WIDTH) // 2
- BOARD_Y_OFFSET = (GRID_HEIGHT - BOARD_HEIGHT) # Keep board aligned to bottom for snake
- # Colors (Can be customized for a prettier look)
- WHITE = (255, 255, 255)
- BLACK = (0, 0, 0)
- GRAY = (128, 128, 128)
- CYAN = (0, 255, 255)
- BLUE = (0, 0, 255)
- ORANGE = (255, 165, 0)
- YELLOW = (255, 255, 0)
- GREEN = (0, 255, 0)
- PURPLE = (128, 0, 128)
- RED = (255, 0, 0)
- SNAKE_HEAD_COLOR = (200, 200, 0) # Distinct color for snake head
- SNAKE_BODY_COLOR = YELLOW # Use Tetris yellow for consistency
- # Tetromino Shapes (coordinates relative to top-left pivot)
- SHAPES = [
- [[1, 1, 1, 1]], # I
- [[1, 1], [1, 1]], # O
- [[0, 1, 0], [1, 1, 1]], # T
- [[1, 0, 0], [1, 1, 1]], # L
- [[0, 0, 1], [1, 1, 1]], # J
- [[0, 1, 1], [1, 1, 0]], # S
- [[1, 1, 0], [0, 1, 1]] # Z
- ]
- SHAPE_COLORS = [CYAN, YELLOW, PURPLE, ORANGE, BLUE, GREEN, RED] # Colors corresponding to SHAPES
- # Game Settings
- INITIAL_FALL_SPEED = 500 # Milliseconds per grid step down
- SPEED_INCREMENT = 20 # Speed increase per line clear
- MIN_FALL_SPEED = 100 # Fastest speed
- INITIAL_SNAKE_LENGTH = 3
- # --- Sound Effect Placeholders ---
- # Replace 'None' with loaded sound objects using pygame.mixer.Sound('path/to/sound.wav')
- # You'll need to find or create these sound files (.wav or .ogg recommended)
- try:
- pygame.mixer.init()
- # move_sound = pygame.mixer.Sound('sounds/move.wav') # Example
- # rotate_sound = pygame.mixer.Sound('sounds/rotate.wav') # Example
- lock_sound = pygame.mixer.Sound('sounds/lock.wav') if pygame.mixer.get_init() else None
- clear_sound = pygame.mixer.Sound('sounds/clear.wav') if pygame.mixer.get_init() else None
- grow_sound = pygame.mixer.Sound('sounds/grow.wav') if pygame.mixer.get_init() else None
- game_over_sound = pygame.mixer.Sound('sounds/game_over.wav') if pygame.mixer.get_init() else None
- # Background music?
- # pygame.mixer.music.load('sounds/background.ogg')
- # pygame.mixer.music.play(-1) # Loop indefinitely
- except pygame.error as e:
- print(f"Warning: Could not initialize mixer or load sounds: {e}")
- lock_sound, clear_sound, grow_sound, game_over_sound = None, None, None, None
- def play_sound(sound):
- """Safely plays a sound effect if it's loaded."""
- if sound:
- sound.play()
- # --- Classes ---
- class Tetromino:
- def __init__(self, x, y, shape_index):
- self.x = x
- self.y = y
- self.shape_index = shape_index
- self.shape = SHAPES[shape_index]
- self.color = SHAPE_COLORS[shape_index]
- self.rotation = 0 # Not implementing rotation for falling pieces in this version yet
- def draw(self, surface, grid_x_offset, grid_y_offset):
- for r_idx, row in enumerate(self.shape):
- for c_idx, cell in enumerate(row):
- if cell:
- pygame.draw.rect(
- surface,
- self.color,
- (
- (self.x + c_idx + grid_x_offset) * GRID_SIZE,
- (self.y + r_idx + grid_y_offset) * GRID_SIZE,
- GRID_SIZE - 1, # -1 for grid lines
- GRID_SIZE - 1
- )
- )
- # Add a slightly darker border for definition
- pygame.draw.rect(
- surface,
- tuple(max(0, c-50) for c in self.color), # Darker color
- (
- (self.x + c_idx + grid_x_offset) * GRID_SIZE,
- (self.y + r_idx + grid_y_offset) * GRID_SIZE,
- GRID_SIZE - 1,
- GRID_SIZE - 1
- ), 1 # Border width
- )
- def move(self, dx, dy):
- self.x += dx
- self.y += dy
- def get_block_positions(self):
- """Returns list of (x, y) grid coordinates for each block in the piece."""
- positions = []
- for r_idx, row in enumerate(self.shape):
- for c_idx, cell in enumerate(row):
- if cell:
- positions.append((self.x + c_idx, self.y + r_idx))
- return positions
- class Snake:
- def __init__(self, initial_x, initial_y, length):
- self.segments = [(initial_x - i, initial_y) for i in range(length)] # Head is at index 0
- self.direction = (1, 0) # Initial movement (not used for player control here)
- self.head_color = SNAKE_HEAD_COLOR
- self.body_color = SNAKE_BODY_COLOR
- def move(self, dx):
- """Moves the entire snake horizontally."""
- new_segments = []
- can_move = True
- for i, (sx, sy) in enumerate(self.segments):
- new_x = sx + dx
- # Basic boundary check (adjust if needed for board offset)
- if not (0 <= new_x < BOARD_WIDTH):
- can_move = False
- break
- new_segments.append((new_x, sy))
- if can_move:
- self.segments = new_segments
- # play_sound(move_sound) # Sound for snake move
- def grow(self, amount=1):
- """Adds segments to the tail."""
- if not self.segments: return # Cannot grow if snake doesn't exist
- # Determine tail direction to add new segment logically
- # (Simplification: always add to the 'left' based on current list order)
- last_x, last_y = self.segments[-1]
- # We'll just add it conceptually off-screen left, it'll correct position on next move?
- # Or better: find the leftmost segment and add left of that.
- min_x = min(x for x, y in self.segments)
- new_segment_pos = (min_x - 1, last_y) # Add left of the leftmost block
-
- # Check if new segment position is valid (within bounds) - It might initially be outside!
- # We rely on the next player move to bring it potentially back into bounds.
- # A better approach might be needed if snake wraps or complex growth is desired.
-
- for _ in range(amount):
- self.segments.append(new_segment_pos) # Add multiple segments if needed
- new_segment_pos = (new_segment_pos[0] - 1, new_segment_pos[1]) # Continue left
- play_sound(grow_sound)
- def draw(self, surface, grid_x_offset, grid_y_offset):
- for i, (x, y) in enumerate(self.segments):
- # Only draw segments within the visible board area
- if 0 <= x < BOARD_WIDTH and 0 <= y < BOARD_HEIGHT :
- color = self.head_color if i == 0 else self.body_color
- # Draw slightly smaller rects for grid effect
- pygame.draw.rect(
- surface,
- color,
- (
- (x + grid_x_offset) * GRID_SIZE,
- (y + grid_y_offset) * GRID_SIZE,
- GRID_SIZE - 1,
- GRID_SIZE - 1
- )
- )
- # Border
- pygame.draw.rect(
- surface,
- tuple(max(0, c-50) for c in color),
- (
- (x + grid_x_offset) * GRID_SIZE,
- (y + grid_y_offset) * GRID_SIZE,
- GRID_SIZE - 1,
- GRID_SIZE - 1
- ), 1
- )
- def get_head_pos(self):
- return self.segments[0] if self.segments else None
- def get_body_positions(self):
- return self.segments[1:] if len(self.segments) > 1 else []
-
- def get_all_positions(self):
- return self.segments
- # --- Game Functions ---
- def create_grid(locked_positions={}):
- """Create the board grid structure."""
- grid = [[BLACK for _ in range(BOARD_WIDTH)] for _ in range(BOARD_HEIGHT)] # Visible grid
- # Fill grid with colors of locked pieces
- for y in range(BOARD_HEIGHT):
- for x in range(BOARD_WIDTH):
- if (x, y) in locked_positions:
- color = locked_positions[(x, y)]
- grid[y][x] = color
- return grid
- def draw_grid(surface, grid, grid_x_offset, grid_y_offset):
- """Draw the grid lines."""
- for y in range(BOARD_HEIGHT):
- for x in range(BOARD_WIDTH):
- # Draw cell background (already done by filling 'grid' in create_grid)
- pygame.draw.rect(
- surface,
- grid[y][x],
- (
- (x + grid_x_offset) * GRID_SIZE,
- (y + grid_y_offset) * GRID_SIZE,
- GRID_SIZE,
- GRID_SIZE
- ),
- 0 # Fill the cell
- )
- # Draw border for the cell
- pygame.draw.rect(
- surface,
- GRAY, # Grid line color
- (
- (x + grid_x_offset) * GRID_SIZE,
- (y + grid_y_offset) * GRID_SIZE,
- GRID_SIZE,
- GRID_SIZE
- ),
- 1 # Border width
- )
- def draw_window(surface, grid, score=0, snake=None, current_piece=None, next_piece=None):
- """Draws all elements onto the screen."""
- surface.fill(BLACK) # Background
- # Title
- font = pygame.font.SysFont('comicsans', 40)
- label = font.render('TetraSnake', 1, WHITE)
- surface.blit(label, (SCREEN_WIDTH / 2 - label.get_width() / 2, 15))
- # Draw the Play Area Grid and locked pieces
- draw_grid(surface, grid, BOARD_X_OFFSET, BOARD_Y_OFFSET)
- # Draw the falling piece
- if current_piece:
- current_piece.draw(surface, BOARD_X_OFFSET, BOARD_Y_OFFSET)
- # Draw the snake
- if snake:
- snake.draw(surface, BOARD_X_OFFSET, BOARD_Y_OFFSET)
- # Draw Score
- score_label = font.render(f'Score: {score}', 1, WHITE)
- sx = SCREEN_WIDTH - score_label.get_width() - 20
- sy = 50 # Position score top right
- surface.blit(score_label, (sx, sy))
- # Draw Next Piece (optional - needs UI space)
- if next_piece:
- nx_label = pygame.font.SysFont('comicsans', 25).render('Next:', 1, WHITE)
- nx_x = SCREEN_WIDTH - nx_label.get_width() - 40
- nx_y = 100
- surface.blit(nx_label, (nx_x, nx_y))
- # Adjust position for drawing the small preview piece
- preview_piece = Tetromino(0, 0, next_piece.shape_index) # Temp piece for drawing
- preview_piece.draw(surface, (nx_x // GRID_SIZE) + 1, (nx_y // GRID_SIZE) + 1)
- # Update the display
- # pygame.display.update() # Use flip() at end of main loop instead for full screen update
- def get_new_piece():
- """Returns a new random Tetromino at the top center."""
- shape_idx = random.randint(0, len(SHAPES) - 1)
- # Start piece above the visible grid
- start_x = BOARD_WIDTH // 2 - len(SHAPES[shape_idx][0]) // 2
- start_y = 0 # Start at the very top row
- return Tetromino(start_x, start_y, shape_idx)
- def is_valid_position(piece, grid, locked_positions, snake):
- """Checks if the piece is in a valid position (within bounds, not overlapping)."""
- accepted_pos = [[(x, y) for x in range(BOARD_WIDTH) if y >= 0] for y in range(BOARD_HEIGHT)]
- accepted_pos = [item for sublist in accepted_pos for item in sublist] # Flatten
- formatted = piece.get_block_positions()
- for pos in formatted:
- x, y = pos
- if x < 0 or x >= BOARD_WIDTH or y >= BOARD_HEIGHT: # Check bounds (allow y<0 initially)
- return False
- if y >= 0: # Only check grid/locked if within visible area
- # Check collision with locked pieces
- if (x, y) in locked_positions:
- return False
- # Check collision with snake's body (not head)
- if snake and pos in snake.get_body_positions():
- return False # Cannot land on snake body yet (becomes locked)
- return True
- def check_lost(locked_positions, snake):
- """Checks if the game is over."""
- # 1. Block reached the top
- for x, y in locked_positions:
- if y < 1: # Piece locked in the topmost row (or above)
- return True
-
- # 2. Falling piece collided with snake head (handled during locking) - Implicitly checked below
- # 3. Snake collided with itself (not implemented)
- return False
- def clear_lines(grid, locked_positions):
- """Checks for and clears completed lines, returns number of lines cleared."""
- lines_cleared = 0
- full_rows = []
- # Check from bottom up
- for y in range(BOARD_HEIGHT - 1, -1, -1):
- row_full = True
- for x in range(BOARD_WIDTH):
- if (x, y) not in locked_positions:
- row_full = False
- break
- if row_full:
- lines_cleared += 1
- full_rows.append(y)
- play_sound(clear_sound)
- # Mark row for clearing visually (optional flashing effect could go here)
- for x in range(BOARD_WIDTH):
- # Temporarily change color? Or handle in draw loop
- pass
- if lines_cleared > 0:
- # Remove cleared rows from locked_positions
- new_locked = {}
- rows_to_shift = sorted(full_rows) # Sort ascending for correct shifting
- for (x, y), color in locked_positions.items():
- if y not in rows_to_shift:
- # Calculate how many cleared rows were below this position
- shift_down = sum(1 for cleared_y in rows_to_shift if cleared_y > y)
- new_locked[(x, y + shift_down)] = color # Add shifted block to new dict
-
- return lines_cleared, new_locked
- return 0, locked_positions # No lines cleared, return original
- def draw_text_middle(surface, text, size, color):
- font = pygame.font.SysFont('comicsans', size, bold=True)
- label = font.render(text, 1, color)
- surface.blit(label, (SCREEN_WIDTH / 2 - label.get_width() / 2, SCREEN_HEIGHT / 2 - label.get_height() / 2))
- # --- Main Game Loop ---
- def main():
- pygame.init()
- screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
- pygame.display.set_caption("TetraSnake")
- locked_positions = {} # (x, y): color
- grid = create_grid(locked_positions)
- change_piece = False
- run = True
- current_piece = get_new_piece()
- next_piece = get_new_piece()
- clock = pygame.time.Clock()
- fall_time = 0
- fall_speed = INITIAL_FALL_SPEED
- level_time = 0 # Could be used for speed increase over time too
- score = 0
- # Initialize Snake at the bottom center
- snake_start_y = BOARD_HEIGHT - 1 # Last row
- snake_start_x = BOARD_WIDTH // 2
- player_snake = Snake(snake_start_x, snake_start_y, INITIAL_SNAKE_LENGTH)
- game_over = False
- while run:
- grid = create_grid(locked_positions) # Update grid based on locked pieces
- fall_time += clock.get_rawtime() # Time since last frame
- level_time += clock.get_rawtime()
- clock.tick() # Control FPS (let Pygame manage it)
- # --- Automatic Falling ---
- if not game_over and fall_time / 1000 * (1000 / fall_speed) >= 1: # Time based fall
- fall_time = 0
- current_piece.move(0, 1) # Move down one step
-
- # Check if landed
- if not is_valid_position(current_piece, grid, locked_positions, player_snake):
- current_piece.move(0, -1) # Move back up
-
- # Check for collision with Snake Head -> Game Over
- piece_blocks = current_piece.get_block_positions()
- snake_head = player_snake.get_head_pos()
- if snake_head and any(block == snake_head for block in piece_blocks):
- game_over = True
- play_sound(game_over_sound)
- # Optional: Show piece overlapping head briefly
- draw_window(screen, grid, score, player_snake, current_piece, next_piece)
- pygame.display.flip()
- pygame.time.wait(500) # Short pause
-
- if not game_over:
- # Lock the piece
- for pos in piece_blocks:
- x, y = pos
- if y >= 0: # Only lock blocks within the visible grid
- locked_positions[(x, y)] = current_piece.color
- play_sound(lock_sound)
- change_piece = True # Signal to get a new piece
- # --- Event Handling ---
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- run = False
- pygame.quit()
- sys.exit()
- if event.type == pygame.KEYDOWN:
- if not game_over:
- if event.key == pygame.K_LEFT:
- player_snake.move(-1)
- # Check snake collision with locked pieces after move (optional for difficulty)
- # If head hits a locked piece? Maybe game over?
- elif event.key == pygame.K_RIGHT:
- player_snake.move(1)
- # Check snake collision...
- # Tetris Controls (Apply to falling piece) - Can be complex with snake
- # For simplicity, let's *not* allow player control of falling piece for now
- # Focus on snake movement first.
- # elif event.key == pygame.K_DOWN: # Soft drop
- # current_piece.move(0, 1)
- # if not is_valid_position(current_piece, grid, locked_positions, player_snake):
- # current_piece.move(0, -1) # Undo if invalid
-
- # elif event.key == pygame.K_UP: # Rotate (Harder to implement correctly)
- # # Add rotation logic here
- # pass # play_sound(rotate_sound)
- # elif event.key == pygame.K_SPACE: # Hard drop
- # while is_valid_position(current_piece, grid, locked_positions, player_snake):
- # current_piece.move(0, 1)
- # current_piece.move(0, -1) # Move back one step
- # # Lock piece immediately after hard drop (similar logic to landing)
- # piece_blocks = current_piece.get_block_positions()
- # if snake_head and any(block == snake_head for block in piece_blocks):
- # game_over = True
- # play_sound(game_over_sound)
- # else:
- # for pos in piece_blocks:
- # x,y = pos
- # if y >=0: locked_positions[(x,y)] = current_piece.color
- # play_sound(lock_sound)
- # change_piece = True
- if game_over and event.key == pygame.K_RETURN: # Restart game
- main() # Call main again to reset everything
- # --- Piece Locking and New Piece ---
- if change_piece and not game_over:
- # Check for cleared lines BEFORE getting the new piece
- lines_cleared, locked_positions = clear_lines(grid, locked_positions)
-
- if lines_cleared > 0:
- score += lines_cleared * 10 # Simple scoring
- player_snake.grow(lines_cleared * 4) # Grow snake by blocks cleared (4 per line avg?)
- # Increase speed
- fall_speed = max(MIN_FALL_SPEED, fall_speed - (SPEED_INCREMENT * lines_cleared))
- # Get the next piece
- current_piece = next_piece
- next_piece = get_new_piece()
- change_piece = False
- # Check if the new piece immediately causes a loss (spawn overlap)
- if not is_valid_position(current_piece, grid, locked_positions, player_snake):
- game_over = True
- play_sound(game_over_sound)
- # --- Game Over Check ---
- if not game_over:
- # Check loss condition based on locked pieces height (redundant with spawn check?)
- if check_lost(locked_positions, player_snake):
- game_over = True
- play_sound(game_over_sound)
- # --- Drawing ---
- draw_window(screen, grid, score, player_snake, current_piece, next_piece)
- if game_over:
- draw_text_middle(screen, "GAME OVER!", 80, RED)
- draw_text_middle(screen, f"Score: {score}", 50, WHITE, y_offset=50) # Offset score display
- draw_text_middle(screen, "Press ENTER to Play Again", 30, WHITE, y_offset=100)
- pygame.display.flip() # Update the full screen
- # Function to draw text with offset easily
- def draw_text_middle(surface, text, size, color, y_offset=0):
- font = pygame.font.SysFont('comicsans', size, bold=True)
- label = font.render(text, 1, color)
- text_rect = label.get_rect(center=(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + y_offset))
- surface.blit(label, text_rect)
- if __name__ == "__main__":
- main()
複製代碼 |