Last active
March 10, 2025 10:09
-
-
Save ThomasRohde/922f22a5b8abdee758686ead06eb9bdf to your computer and use it in GitHub Desktop.
This script implements a snazzy version of the cla...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# /// script | |
# description = 'A snazzy game of Snake with two AI-controlled snakes competing' | |
# authors = [ | |
# 'Script-Magic AI Generator', | |
# ] | |
# date = '2024-07-14' | |
# requires-python = '>=3.9' | |
# dependencies = [ | |
# 'pygame>=2.1.0', | |
# ] | |
# tags = [ | |
# 'generated', | |
# 'script-magic', | |
# 'game', | |
# 'snake', | |
# 'pygame', | |
# 'ai', | |
# 'competition', | |
# 'edited', | |
# ] | |
# /// | |
# Generated from the prompt: "Create a snazzy game of snake using pygame" | |
# Modified to have two AI-controlled snakes compete against each other | |
import pygame | |
import random | |
import sys | |
import time | |
# Initialize Pygame | |
pygame.init() | |
# Set up the game window | |
WIDTH, HEIGHT = 800, 600 | |
GRID_SIZE = 20 | |
GRID_WIDTH = WIDTH // GRID_SIZE | |
GRID_HEIGHT = HEIGHT // GRID_SIZE | |
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT)) | |
pygame.display.set_caption("AI Snake Competition") | |
# Define colors | |
BLACK = (0, 0, 0) | |
WHITE = (255, 255, 255) | |
RED = (255, 0, 0) | |
GREEN = (0, 255, 0) | |
BLUE = (0, 0, 255) | |
PURPLE = (128, 0, 128) | |
ORANGE = (255, 165, 0) | |
# Define snake properties | |
# Snake 1 (Green) | |
snake1_pos = [(GRID_WIDTH // 4, GRID_HEIGHT // 2)] | |
snake1_dir = (1, 0) | |
snake1_length = 1 | |
# Snake 2 (Purple) | |
snake2_pos = [(3 * GRID_WIDTH // 4, GRID_HEIGHT // 2)] | |
snake2_dir = (-1, 0) | |
snake2_length = 1 | |
# Define food properties | |
food_pos = (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) | |
# Set up the game clock | |
clock = pygame.time.Clock() | |
# Initialize scores | |
snake1_score = 0 | |
snake2_score = 0 | |
# Set game speed (FPS) | |
GAME_SPEED = 10 | |
# Set up fonts | |
font = pygame.font.Font(None, 36) | |
def draw_grid(): | |
for x in range(0, WIDTH, GRID_SIZE): | |
pygame.draw.line(SCREEN, (40, 40, 40), (x, 0), (x, HEIGHT)) | |
for y in range(0, HEIGHT, GRID_SIZE): | |
pygame.draw.line(SCREEN, (40, 40, 40), (0, y), (WIDTH, y)) | |
def draw_snake(snake_pos, base_color): | |
for i, (x, y) in enumerate(snake_pos): | |
color = pygame.Color(*base_color) | |
# Create a gradient effect for each snake | |
color.hsva = (color.hsva[0] + i * 5 % 360, 100, 100, 100) | |
pygame.draw.rect(SCREEN, color, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE)) | |
def draw_food(): | |
x, y = food_pos | |
pygame.draw.circle(SCREEN, RED, (x * GRID_SIZE + GRID_SIZE // 2, y * GRID_SIZE + GRID_SIZE // 2), GRID_SIZE // 2) | |
def ai_decide_direction(snake_pos, snake_dir, other_snake_pos, food_pos): | |
head_x, head_y = snake_pos[0] | |
food_x, food_y = food_pos | |
# Calculate distance to food in each direction | |
distances = { | |
(0, -1): float('inf'), # Up | |
(0, 1): float('inf'), # Down | |
(-1, 0): float('inf'), # Left | |
(1, 0): float('inf') # Right | |
} | |
# Check each possible direction | |
for direction, (dx, dy) in enumerate([(0, -1), (0, 1), (-1, 0), (1, 0)]): | |
# Skip the opposite direction of current direction (can't go backwards) | |
if (dx, dy) == (-snake_dir[0], -snake_dir[1]): | |
continue | |
new_x = (head_x + dx) % GRID_WIDTH | |
new_y = (head_y + dy) % GRID_HEIGHT | |
# Check if moving in this direction would hit itself | |
if (new_x, new_y) in snake_pos: | |
continue | |
# Check if moving in this direction would hit the other snake | |
if (new_x, new_y) in other_snake_pos: | |
continue | |
# Calculate Manhattan distance to food | |
distance = abs(new_x - food_x) + abs(new_y - food_y) | |
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] | |
distances[directions[direction]] = distance | |
# If all directions are blocked, try any valid move | |
if all(d == float('inf') for d in distances.values()): | |
for direction, (dx, dy) in enumerate([(0, -1), (0, 1), (-1, 0), (1, 0)]): | |
if (dx, dy) == (-snake_dir[0], -snake_dir[1]): | |
continue | |
new_x = (head_x + dx) % GRID_WIDTH | |
new_y = (head_y + dy) % GRID_HEIGHT | |
# Only avoid hitting itself, accept other risks | |
if (new_x, new_y) not in snake_pos: | |
return (dx, dy) | |
# Choose the direction with the minimum distance to food | |
if distances: | |
min_distance = min(distances.values()) | |
best_directions = [d for d, dist in distances.items() if dist == min_distance] | |
return random.choice(best_directions) | |
# Default to current direction if nothing else works | |
return snake_dir | |
def move_snakes(): | |
global snake1_pos, snake1_length, snake2_pos, snake2_length, food_pos, snake1_score, snake2_score | |
# Move snake 1 | |
new_head1 = ((snake1_pos[0][0] + snake1_dir[0]) % GRID_WIDTH, | |
(snake1_pos[0][1] + snake1_dir[1]) % GRID_HEIGHT) | |
snake1_pos.insert(0, new_head1) | |
# Move snake 2 | |
new_head2 = ((snake2_pos[0][0] + snake2_dir[0]) % GRID_WIDTH, | |
(snake2_pos[0][1] + snake2_dir[1]) % GRID_HEIGHT) | |
snake2_pos.insert(0, new_head2) | |
# Check if either snake got the food | |
if new_head1 == food_pos: | |
snake1_length += 1 | |
snake1_score += 10 | |
generate_new_food() | |
elif new_head2 == food_pos: | |
snake2_length += 1 | |
snake2_score += 10 | |
generate_new_food() | |
else: | |
# Trim tails if they didn't eat | |
snake1_pos = snake1_pos[:snake1_length] | |
snake2_pos = snake2_pos[:snake2_length] | |
def generate_new_food(): | |
global food_pos | |
# Try to find a position that's not on either snake | |
while True: | |
food_pos = (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) | |
if food_pos not in snake1_pos and food_pos not in snake2_pos: | |
break | |
def check_collisions(): | |
# Check if snake 1 has collided with itself | |
if len(snake1_pos) != len(set(snake1_pos)): | |
return 2 # Snake 2 wins | |
# Check if snake 2 has collided with itself | |
if len(snake2_pos) != len(set(snake2_pos)): | |
return 1 # Snake 1 wins | |
# Check if snake 1's head collided with snake 2's body | |
if snake1_pos[0] in snake2_pos[1:]: | |
return 2 # Snake 2 wins | |
# Check if snake 2's head collided with snake 1's body | |
if snake2_pos[0] in snake1_pos[1:]: | |
return 1 # Snake 1 wins | |
# Check if the snakes' heads collided with each other | |
if snake1_pos[0] == snake2_pos[0]: | |
return 3 # Draw | |
return 0 # No collision | |
def game_over(winner): | |
if winner == 1: | |
winner_text = "Green Snake Wins!" | |
elif winner == 2: | |
winner_text = "Purple Snake Wins!" | |
else: | |
winner_text = "It's a Draw!" | |
game_over_text = font.render("Game Over!", True, WHITE) | |
winner_text = font.render(winner_text, True, WHITE) | |
score_text = font.render(f"Green: {snake1_score} - Purple: {snake2_score}", True, WHITE) | |
restart_text = font.render("Press R to Restart or Q to Quit", True, WHITE) | |
SCREEN.blit(game_over_text, (WIDTH // 2 - game_over_text.get_width() // 2, HEIGHT // 2 - 90)) | |
SCREEN.blit(winner_text, (WIDTH // 2 - winner_text.get_width() // 2, HEIGHT // 2 - 30)) | |
SCREEN.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2 + 30)) | |
SCREEN.blit(restart_text, (WIDTH // 2 - restart_text.get_width() // 2, HEIGHT // 2 + 90)) | |
pygame.display.flip() | |
waiting = True | |
while waiting: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
sys.exit() | |
if event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_r: | |
return True | |
if event.key == pygame.K_q: | |
pygame.quit() | |
sys.exit() | |
return False | |
def reset_game(): | |
global snake1_pos, snake1_dir, snake1_length, snake2_pos, snake2_dir, snake2_length, food_pos, snake1_score, snake2_score | |
snake1_pos = [(GRID_WIDTH // 4, GRID_HEIGHT // 2)] | |
snake1_dir = (1, 0) | |
snake1_length = 1 | |
snake2_pos = [(3 * GRID_WIDTH // 4, GRID_HEIGHT // 2)] | |
snake2_dir = (-1, 0) | |
snake2_length = 1 | |
food_pos = (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1)) | |
snake1_score = 0 | |
snake2_score = 0 | |
def main(): | |
global snake1_dir, snake2_dir | |
while True: | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
pygame.quit() | |
sys.exit() | |
# Allow manual speed adjustment | |
if event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_PLUS or event.key == pygame.K_EQUALS: | |
global GAME_SPEED | |
GAME_SPEED = min(30, GAME_SPEED + 2) | |
if event.key == pygame.K_MINUS: | |
GAME_SPEED = max(1, GAME_SPEED - 2) | |
# AI decision making | |
snake1_dir = ai_decide_direction(snake1_pos, snake1_dir, snake2_pos, food_pos) | |
snake2_dir = ai_decide_direction(snake2_pos, snake2_dir, snake1_pos, food_pos) | |
# Move snakes | |
move_snakes() | |
# Check for collisions | |
collision_result = check_collisions() | |
if collision_result > 0: | |
if game_over(collision_result): | |
reset_game() | |
else: | |
continue | |
# Draw everything | |
SCREEN.fill(BLACK) | |
draw_grid() | |
draw_snake(snake1_pos, GREEN) | |
draw_snake(snake2_pos, PURPLE) | |
draw_food() | |
# Display scores | |
snake1_score_text = font.render(f"Green: {snake1_score}", True, GREEN) | |
snake2_score_text = font.render(f"Purple: {snake2_score}", True, PURPLE) | |
speed_text = font.render(f"Speed: {GAME_SPEED}", True, WHITE) | |
SCREEN.blit(snake1_score_text, (10, 10)) | |
SCREEN.blit(snake2_score_text, (WIDTH - snake2_score_text.get_width() - 10, 10)) | |
SCREEN.blit(speed_text, (WIDTH // 2 - speed_text.get_width() // 2, 10)) | |
# Update display | |
pygame.display.flip() | |
# Control game speed | |
clock.tick(GAME_SPEED) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment