Upload 9 files
Browse files- snake-pathfinding-AI/README.md +29 -0
- snake-pathfinding-AI/__pycache__/pathfinding.cpython-312.pyc +0 -0
- snake-pathfinding-AI/__pycache__/settings.cpython-312.pyc +0 -0
- snake-pathfinding-AI/__pycache__/snake.cpython-312.pyc +0 -0
- snake-pathfinding-AI/main.py +85 -0
- snake-pathfinding-AI/pathfinding.py +62 -0
- snake-pathfinding-AI/settings.py +21 -0
- snake-pathfinding-AI/snake.py +53 -0
- snake-pathfinding-AI/test_snake.py +95 -0
snake-pathfinding-AI/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Snake Game with Pathfinding
|
2 |
+
|
3 |
+
This project implements the Snake game using Python and Pygame library. The Snake moves around the screen, eating apples to grow longer. The game ends if the Snake collides with itself or with the screen's boundaries.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
- Snake movement: The Snake moves around the screen based on a predefined pathfinding algorithm (BFS).
|
7 |
+
- Apple spawning: Apples spawn randomly on the screen for the Snake to eat.
|
8 |
+
- Collision detection: Detects when the Snake collides with itself or the screen's boundaries.
|
9 |
+
- Pathfinding: Implements Breadth-First Search (BFS) algorithm to find the shortest path from the Snake to the nearest Apple.
|
10 |
+
|
11 |
+
## Installation
|
12 |
+
|
13 |
+
1. Clone the repository:
|
14 |
+
```bash
|
15 |
+
git clone https://github.com/your-username/snake-game.git
|
16 |
+
2. Install dependencies:
|
17 |
+
```bash
|
18 |
+
pip install pygame
|
19 |
+
4. Run the game:
|
20 |
+
```bash
|
21 |
+
python main.py
|
22 |
+
|
23 |
+
## Usage
|
24 |
+
- The snake will automatically find the shortest path to the nearest apple using the BFS algorithm.
|
25 |
+
|
26 |
+
## Tests
|
27 |
+
- To run unit tests:
|
28 |
+
- ```bash
|
29 |
+
python -m unittest test_snake.py
|
snake-pathfinding-AI/__pycache__/pathfinding.cpython-312.pyc
ADDED
Binary file (2.62 kB). View file
|
|
snake-pathfinding-AI/__pycache__/settings.cpython-312.pyc
ADDED
Binary file (866 Bytes). View file
|
|
snake-pathfinding-AI/__pycache__/snake.cpython-312.pyc
ADDED
Binary file (3.94 kB). View file
|
|
snake-pathfinding-AI/main.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pygame
|
2 |
+
from snake import Apple, Snake
|
3 |
+
from pathfinding import BFS
|
4 |
+
from settings import *
|
5 |
+
|
6 |
+
def drawGrid():
|
7 |
+
for x in range(0, WIDTH, BLOCK_SIZE):
|
8 |
+
for y in range(0, HEIGHT, BLOCK_SIZE):
|
9 |
+
rect = pygame.Rect(x, y, BLOCK_SIZE, BLOCK_SIZE)
|
10 |
+
pygame.draw.rect(SCREEN, GRID_CLR, rect, 1)
|
11 |
+
|
12 |
+
def handle_events():
|
13 |
+
for event in pygame.event.get():
|
14 |
+
if event.type == pygame.QUIT:
|
15 |
+
return False
|
16 |
+
return True
|
17 |
+
|
18 |
+
def update_snake_direction(snake, path):
|
19 |
+
''' Extract moves from the path and update snake's direction '''
|
20 |
+
if path:
|
21 |
+
next_x, next_y = path[0], path[1]
|
22 |
+
if next_x > 0 and snake.pos != [-1, 0]:
|
23 |
+
snake.pos = [1, 0]
|
24 |
+
elif next_x < 0 and snake.pos != [1, 0]:
|
25 |
+
snake.pos = [-1, 0]
|
26 |
+
elif next_y > 0 and snake.pos != [0, 1]:
|
27 |
+
snake.pos = [0, 1]
|
28 |
+
elif next_y < 0 and snake.pos != [0, -1]:
|
29 |
+
snake.pos = [0, -1]
|
30 |
+
|
31 |
+
# Remove the current move pair from the path
|
32 |
+
path = path[2:]
|
33 |
+
return path
|
34 |
+
|
35 |
+
|
36 |
+
def main():
|
37 |
+
pygame.init()
|
38 |
+
running = True
|
39 |
+
clock = pygame.time.Clock()
|
40 |
+
font = pygame.font.SysFont('timesnewroman', BLOCK_SIZE * 2)
|
41 |
+
score = font.render("1", True, "white")
|
42 |
+
score_rect = score.get_rect(center = (WIDTH / 2, HEIGHT / 20)) # Position score at the top center
|
43 |
+
|
44 |
+
drawGrid()
|
45 |
+
|
46 |
+
snake = Snake()
|
47 |
+
apple = Apple()
|
48 |
+
|
49 |
+
while running:
|
50 |
+
running = handle_events()
|
51 |
+
|
52 |
+
path = BFS(snake, apple, snake.pos)
|
53 |
+
path = update_snake_direction(snake, path)
|
54 |
+
|
55 |
+
snake.drawSnake()
|
56 |
+
SCREEN.fill(SURFACE_CLR)
|
57 |
+
drawGrid()
|
58 |
+
apple.checkSpawn(snake.body)
|
59 |
+
apple.drawApple()
|
60 |
+
|
61 |
+
score = font.render(f"{len(snake.body)}", True, GRID_CLR)
|
62 |
+
|
63 |
+
pygame.draw.rect(SCREEN, SNAKE_CLR, snake.head)
|
64 |
+
for square in snake.body:
|
65 |
+
pygame.draw.rect(SCREEN, SNAKE_CLR, square)
|
66 |
+
|
67 |
+
SCREEN.blit(score, score_rect)
|
68 |
+
|
69 |
+
# Grow the snake
|
70 |
+
if len(snake.body) < 1:
|
71 |
+
if(snake.head.x == apple.x and snake.head.y == apple.y):
|
72 |
+
snake.body.append(pygame.Rect(snake.x, snake.y, BLOCK_SIZE, BLOCK_SIZE))
|
73 |
+
else:
|
74 |
+
# Differentiates apple from the snake's body
|
75 |
+
if(snake.head.x == apple.x and snake.head.y == apple.y):
|
76 |
+
snake.body.append(pygame.Rect(square.x, square.y, BLOCK_SIZE, BLOCK_SIZE))
|
77 |
+
|
78 |
+
|
79 |
+
pygame.display.update()
|
80 |
+
clock.tick(FPS)
|
81 |
+
|
82 |
+
pygame.quit()
|
83 |
+
|
84 |
+
if __name__ == "__main__":
|
85 |
+
main()
|
snake-pathfinding-AI/pathfinding.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from settings import *
|
2 |
+
from queue import Queue
|
3 |
+
|
4 |
+
def get_neighbours(position):
|
5 |
+
'''
|
6 |
+
Get neighbouring positions from the current position
|
7 |
+
|
8 |
+
Args:
|
9 |
+
- position: Tuple representing the current position (x, y)
|
10 |
+
Returns:
|
11 |
+
- List of neighbouring positions
|
12 |
+
'''
|
13 |
+
|
14 |
+
neighbours = [[position[0] + BLOCK_SIZE, position[1]],
|
15 |
+
[position[0] - BLOCK_SIZE, position[1]],
|
16 |
+
[position[0], position[1] + BLOCK_SIZE],
|
17 |
+
[position[0], position[1] - BLOCK_SIZE]]
|
18 |
+
|
19 |
+
neighbours_within_grid = []
|
20 |
+
for pos in neighbours:
|
21 |
+
if pos in GRID:
|
22 |
+
neighbours_within_grid.append(pos)
|
23 |
+
|
24 |
+
return neighbours_within_grid
|
25 |
+
|
26 |
+
def BFS(snake, apple, current_direction):
|
27 |
+
'''
|
28 |
+
Perform Breadth-First Search (BFS) to find the shortest path from the snake's head to the apple
|
29 |
+
|
30 |
+
Args:
|
31 |
+
- snake: Snake object
|
32 |
+
- apple: Apple object
|
33 |
+
- current direction: Tuple representing current direction of the snake (x, y)
|
34 |
+
Returns:
|
35 |
+
- List representing the sequence of moves to the apple'''
|
36 |
+
|
37 |
+
queue = Queue()
|
38 |
+
head_pos = (snake.head.x, snake.head.y)
|
39 |
+
apple_pos = (apple.apple.x, apple.apple.y)
|
40 |
+
queue.put((head_pos, [])) # Initialize queue with snake's head position and empty path
|
41 |
+
|
42 |
+
visited = set()
|
43 |
+
visited.add(head_pos)
|
44 |
+
|
45 |
+
while not queue.empty():
|
46 |
+
current_pos, path = queue.get()
|
47 |
+
|
48 |
+
# Return path if apple is found
|
49 |
+
if current_pos == apple_pos:
|
50 |
+
return path # Sequence of moves towards the apple
|
51 |
+
|
52 |
+
# Explore neighbours
|
53 |
+
neighbours = get_neighbours(current_pos)
|
54 |
+
for neighbour in neighbours:
|
55 |
+
if neighbour not in snake.body:
|
56 |
+
direction = [neighbour[0] - current_pos[0], neighbour[1] - current_pos[1]]
|
57 |
+
if direction != [-current_direction[0], -current_direction[1]]:
|
58 |
+
if tuple(neighbour) not in visited:
|
59 |
+
visited.add(tuple(neighbour))
|
60 |
+
new_path = path + direction
|
61 |
+
queue.put((tuple(neighbour), new_path))
|
62 |
+
return [] # If no path is found
|
snake-pathfinding-AI/settings.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pygame
|
2 |
+
|
3 |
+
WIDTH = 800
|
4 |
+
HEIGHT = 800
|
5 |
+
BLOCK_SIZE = 50
|
6 |
+
ROWS = WIDTH // BLOCK_SIZE
|
7 |
+
GAP_SIZE = 2
|
8 |
+
|
9 |
+
# Set up display
|
10 |
+
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
|
11 |
+
pygame.display.set_caption("Snake")
|
12 |
+
|
13 |
+
SURFACE_CLR = (0, 0, 0)
|
14 |
+
GRID_CLR = (255, 255, 255)
|
15 |
+
SNAKE_CLR = (0, 255, 0)
|
16 |
+
APPLE_CLR = (255, 0, 0)
|
17 |
+
|
18 |
+
FPS = 10
|
19 |
+
INITIAL_SNAKE_LEN = 1
|
20 |
+
|
21 |
+
GRID = [[x,y] for x in range(0, WIDTH, BLOCK_SIZE) for y in range(0, HEIGHT, BLOCK_SIZE)]
|
snake-pathfinding-AI/snake.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import random
|
2 |
+
from settings import *
|
3 |
+
|
4 |
+
class Snake:
|
5 |
+
def __init__(self):
|
6 |
+
# Initialize the snake at the center of the screen
|
7 |
+
self.x, self.y = 0, 0
|
8 |
+
self.pos = [1, 0] # [x, y] direction, initially set to right
|
9 |
+
self.head = pygame.Rect(self.x, self.y, BLOCK_SIZE, BLOCK_SIZE)
|
10 |
+
self.body = [] # List to keep track of snake's body segments
|
11 |
+
self.dead = False
|
12 |
+
|
13 |
+
def drawSnake(self):
|
14 |
+
# Check if snake has gone out of bounds
|
15 |
+
if self.head.x not in range(0, WIDTH) or self.head.y not in range(0, HEIGHT):
|
16 |
+
self.dead = True
|
17 |
+
print('game over')
|
18 |
+
# Check if snake has hit itself
|
19 |
+
for square in self.body:
|
20 |
+
if self.head.x == square.x and self.head.y == square.y:
|
21 |
+
self.dead = True
|
22 |
+
print('game over')
|
23 |
+
|
24 |
+
self.body.append(self.head)
|
25 |
+
|
26 |
+
# Move body segments to follow head
|
27 |
+
for i in range(len(self.body) - 1):
|
28 |
+
self.body[i].x, self.body[i].y = self.body[i + 1].x, self.body[i + 1].y
|
29 |
+
|
30 |
+
# Update head position based on current direction
|
31 |
+
self.head.x += self.pos[0] * BLOCK_SIZE
|
32 |
+
self.head.y += self.pos[1] * BLOCK_SIZE
|
33 |
+
self.body.remove(self.head)
|
34 |
+
|
35 |
+
class Apple:
|
36 |
+
def __init__(self):
|
37 |
+
self.spawn()
|
38 |
+
|
39 |
+
def spawn(self):
|
40 |
+
''' Spawn a new apple ont he screen at a random position '''
|
41 |
+
# Randomly spawn apple
|
42 |
+
self.x = random.randrange(0, WIDTH, BLOCK_SIZE)
|
43 |
+
self.y = random.randrange(0, HEIGHT, BLOCK_SIZE)
|
44 |
+
self.apple = pygame.Rect(self.x, self.y, BLOCK_SIZE, BLOCK_SIZE)
|
45 |
+
|
46 |
+
def drawApple(self):
|
47 |
+
pygame.draw.rect(SCREEN, APPLE_CLR, self.apple)
|
48 |
+
|
49 |
+
def checkSpawn(self, snake: list):
|
50 |
+
''' Check if the apple collides with the snake. If so, respawn the apple. '''
|
51 |
+
for square in snake:
|
52 |
+
if self.apple.colliderect(square):
|
53 |
+
self.spawn() # Respawn apple if it collides with snake
|
snake-pathfinding-AI/test_snake.py
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import unittest
|
2 |
+
import pygame
|
3 |
+
from snake import Snake, Apple # Make sure the Snake and Apple classes are imported correctly
|
4 |
+
from settings import *
|
5 |
+
|
6 |
+
class TestSnakeGame(unittest.TestCase):
|
7 |
+
|
8 |
+
def setUp(self):
|
9 |
+
pygame.init()
|
10 |
+
self.snake = Snake()
|
11 |
+
self.apple = Apple()
|
12 |
+
|
13 |
+
def tearDown(self):
|
14 |
+
pygame.quit()
|
15 |
+
|
16 |
+
def test_initial_state(self):
|
17 |
+
# Test initial snake position
|
18 |
+
self.assertEqual(self.snake.head.x, 0)
|
19 |
+
self.assertEqual(self.snake.head.y, 0)
|
20 |
+
# Test initial snake length
|
21 |
+
self.assertEqual(len(self.snake.body), 0)
|
22 |
+
|
23 |
+
def test_movement(self):
|
24 |
+
# Test snake movement to the right
|
25 |
+
self.snake.pos = [1, 0]
|
26 |
+
self.snake.drawSnake()
|
27 |
+
self.assertEqual(self.snake.head.x, BLOCK_SIZE)
|
28 |
+
self.assertEqual(self.snake.head.y, 0)
|
29 |
+
|
30 |
+
# Test snake movement left
|
31 |
+
self.snake.pos = [-1, 0]
|
32 |
+
self.snake.drawSnake()
|
33 |
+
self.assertEqual(self.snake.head.x, 0)
|
34 |
+
self.assertEqual(self.snake.head.y, 0)
|
35 |
+
|
36 |
+
# Test snake movement downwards
|
37 |
+
self.snake.pos = [0, -1]
|
38 |
+
self.snake.drawSnake()
|
39 |
+
self.assertEqual(self.snake.head.x, 0)
|
40 |
+
self.assertEqual(self.snake.head.y, -50)
|
41 |
+
|
42 |
+
# Test snake movement upwards
|
43 |
+
self.snake.pos = [0, 1]
|
44 |
+
self.snake.drawSnake()
|
45 |
+
self.assertEqual(self.snake.head.x, 0)
|
46 |
+
self.assertEqual(self.snake.head.y, 0)
|
47 |
+
|
48 |
+
def test_apple_spawn(self):
|
49 |
+
# Test apple spawning within the grid
|
50 |
+
self.apple.spawn()
|
51 |
+
self.assertTrue(0 <= self.apple.apple.x < WIDTH)
|
52 |
+
self.assertTrue(0 <= self.apple.apple.y < HEIGHT)
|
53 |
+
self.assertEqual(self.apple.apple.width, BLOCK_SIZE)
|
54 |
+
self.assertEqual(self.apple.apple.height, BLOCK_SIZE)
|
55 |
+
|
56 |
+
def test_eating_apple(self):
|
57 |
+
# Place apple directly in front of the snake
|
58 |
+
self.apple.apple.x = self.snake.head.x + BLOCK_SIZE
|
59 |
+
self.apple.apple.y = self.snake.head.y
|
60 |
+
self.snake.pos = [1, 0]
|
61 |
+
self.snake.drawSnake()
|
62 |
+
|
63 |
+
# Simulate snake eating the apple
|
64 |
+
if self.snake.head.colliderect(self.apple.apple):
|
65 |
+
self.snake.body.append(pygame.Rect(self.snake.head.x, self.snake.head.y, BLOCK_SIZE, BLOCK_SIZE))
|
66 |
+
self.apple.spawn()
|
67 |
+
|
68 |
+
self.assertEqual(len(self.snake.body), 1)
|
69 |
+
self.assertNotEqual((self.apple.apple.x, self.apple.apple.y), (self.snake.head.x, self.snake.head.y))
|
70 |
+
|
71 |
+
def test_collision_with_wall(self):
|
72 |
+
# Move snake to the right until it hits the wall
|
73 |
+
self.snake.pos = [-1, 0]
|
74 |
+
self.snake.drawSnake()
|
75 |
+
self.snake.pos = [-1, 0]
|
76 |
+
self.snake.drawSnake()
|
77 |
+
self.assertTrue(self.snake.dead)
|
78 |
+
|
79 |
+
def test_collision_with_self(self):
|
80 |
+
# Create a situation where the snake will collide with itself
|
81 |
+
self.snake.body = [
|
82 |
+
pygame.Rect(WIDTH / 2, HEIGHT / 2, BLOCK_SIZE, BLOCK_SIZE),
|
83 |
+
pygame.Rect(WIDTH / 2 - BLOCK_SIZE, HEIGHT / 2, BLOCK_SIZE, BLOCK_SIZE),
|
84 |
+
pygame.Rect(WIDTH / 2 - 2 * BLOCK_SIZE, HEIGHT / 2, BLOCK_SIZE, BLOCK_SIZE)
|
85 |
+
]
|
86 |
+
self.snake.pos = [1, 0]
|
87 |
+
self.snake.drawSnake() # Move right
|
88 |
+
self.snake.pos = [0, -1]
|
89 |
+
self.snake.drawSnake() # Move up
|
90 |
+
self.snake.pos = [-1, 0]
|
91 |
+
self.snake.drawSnake() # Move left (collide with body)
|
92 |
+
self.assertTrue(self.snake.dead)
|
93 |
+
|
94 |
+
if __name__ == '__main__':
|
95 |
+
unittest.main()
|