rhdang1 commited on
Commit
5625b4c
·
1 Parent(s): 51378ba

Edit pathfinding to improve success rate

Browse files
Files changed (5) hide show
  1. main.py +79 -0
  2. pathfinding.py +173 -0
  3. settings.py +22 -0
  4. snake.py +81 -0
  5. test_snake.py +95 -0
main.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+ from snake import Apple, Snake
3
+ from pathfinding import set_path, is_position_safe
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, apple):
19
+ apple.drawApple()
20
+ path = set_path(snake, apple)
21
+ if path:
22
+ snake.go_to(path[0])
23
+ snake.move()
24
+
25
+ pygame.draw.rect(SCREEN, SNAKE_CLR, snake.head)
26
+
27
+ for square in snake.body[1:]:
28
+ if snake.head.x == square.x and snake.head.y == square.y:
29
+ snake.dead = True
30
+ if snake.head.x <= -1 or snake.head.x >= WIDTH or snake.head.y <= -1 or snake.head.y >= HEIGHT:
31
+ snake.dead = True
32
+ if snake.dead:
33
+ #main()
34
+ pygame.time.wait(30000)
35
+
36
+ if len(snake.body) < 1:
37
+ # If snake's body is empty, add a new block after the head
38
+ new_block = pygame.Rect(snake.head.x, snake.head.y, BLOCK_SIZE, BLOCK_SIZE)
39
+ snake.body.append(new_block)
40
+ else:
41
+ # Draw and update all body segments
42
+ for square in snake.body:
43
+ pygame.draw.rect(SCREEN, SNAKE_CLR, square)
44
+
45
+ apple_pos = (apple.apple.x, apple.apple.y)
46
+ if snake.head.x == apple_pos[0] and snake.head.y == apple_pos[1]:
47
+ apple.spawn() # Respawn apple
48
+ while not is_position_safe(snake.body, (apple.apple.x, apple.apple.y)):
49
+ apple.spawn()
50
+ # Add a new block after the current head position
51
+ snake.grow()
52
+
53
+ def main():
54
+ pygame.init()
55
+ clock = pygame.time.Clock()
56
+ font = pygame.font.SysFont('timesnewroman', BLOCK_SIZE * 2)
57
+ score = font.render("1", True, "white")
58
+ score_rect = score.get_rect(center = (WIDTH / 2, HEIGHT / 20)) # Position score at the top center
59
+
60
+ drawGrid()
61
+
62
+ snake = Snake()
63
+ apple = Apple()
64
+
65
+ running = True
66
+ while running:
67
+ running = handle_events()
68
+ SCREEN.fill(SURFACE_CLR)
69
+ drawGrid()
70
+
71
+ update(snake, apple)
72
+
73
+ clock.tick(FPS)
74
+ pygame.display.update()
75
+
76
+ pygame.quit()
77
+
78
+ if __name__ == "__main__":
79
+ main()
pathfinding.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from settings import *
2
+ from queue import Queue
3
+ from snake import create_duplicate_snake, deepcopy
4
+ from random import randrange
5
+
6
+ def get_neighbours(position):
7
+ '''
8
+ Get neighbouring positions from the current position
9
+
10
+ Args:
11
+ - position: Tuple representing the current position (x, y)
12
+ Returns:
13
+ - List of neighbouring positions
14
+ '''
15
+
16
+ neighbours = [[position[0] + BLOCK_SIZE, position[1]],
17
+ [position[0] - BLOCK_SIZE, position[1]],
18
+ [position[0], position[1] + BLOCK_SIZE],
19
+ [position[0], position[1] - BLOCK_SIZE]]
20
+
21
+ neighbours_within_grid = []
22
+ for pos in neighbours:
23
+ if pos in GRID:
24
+ neighbours_within_grid.append(pos)
25
+
26
+ return neighbours_within_grid
27
+
28
+ def get_available_neighbours(snake, position, apple_pos):
29
+ valid_neighbours = []
30
+ neighbours = get_neighbours(position)
31
+ for neighbour in neighbours:
32
+ if is_position_available(snake, neighbour) and apple_pos != neighbour:
33
+ valid_neighbours.append(neighbour)
34
+ return valid_neighbours
35
+
36
+ def is_position_available(snake, position):
37
+ if (0 <= position[0] <= ROWS or 0 <= position[1] <= ROWS) and position not in snake.body:
38
+ return True
39
+ return False
40
+
41
+ def distance(position1, position2):
42
+ x1, x2 = position1[0], position2[0]
43
+ y1, y2 = position1[1], position2[1]
44
+ return abs(x2 - x1) + abs(y2 - y1)
45
+
46
+
47
+ def longest_path_to_tail(snake, apple_pos):
48
+ neighbours = get_available_neighbours(snake, (snake.head.x, snake.head.y), apple_pos)
49
+ path = []
50
+ if neighbours:
51
+ d = -9999
52
+ for neighbour in neighbours:
53
+ tail_pos = (snake.tail.x, snake.tail.y)
54
+ if distance(neighbour, tail_pos) > d:
55
+ dup_snake = create_duplicate_snake(snake)
56
+ dup_snake.go_to(neighbour)
57
+ dup_snake.move()
58
+ if dup_snake.head.x == apple_pos[0] and dup_snake.head.y == apple_pos[1]:
59
+ dup_snake.grow()
60
+ if path_to_tail(dup_snake):
61
+ path.append(neighbour)
62
+ d = distance(neighbour, tail_pos)
63
+ if path:
64
+ return [path[-1]]
65
+
66
+ def find_safe_move(snake, apple_pos):
67
+ neighbours = get_available_neighbours(snake, (snake.head.x, snake.head.y), apple_pos)
68
+ path = []
69
+ if neighbours:
70
+ path.append(neighbours[randrange(len(neighbours))])
71
+ dup_snake = create_duplicate_snake(snake)
72
+ for move in path:
73
+ dup_snake.go_to(move)
74
+ dup_snake.move()
75
+ if path_to_tail(dup_snake):
76
+ return path
77
+ else:
78
+ return path_to_tail(snake)
79
+
80
+ def BFS(start_pos, target_pos, snake_body):
81
+ '''
82
+ Perform Breadth-First Search (BFS) to find the shortest path from the snake's head to the apple
83
+
84
+ Args:
85
+ - start_pos: Tuple representing the starting position (snake's head)
86
+ - target_pos: Tuple representing the target position (apple)
87
+ - snake_body: List representing the coordinates of the snake's body
88
+ Returns:
89
+ - List representing the sequence of moves to the apple as a list of tuples (coordinates)
90
+ '''
91
+
92
+ queue = Queue()
93
+ queue.put((start_pos, [])) # Initialize queue with snake's head position and empty path
94
+
95
+ visited = set()
96
+ visited.add(start_pos)
97
+
98
+ while not queue.empty():
99
+ current_pos, path = queue.get()
100
+
101
+ # Return path if apple is found
102
+ if current_pos == target_pos:
103
+ return path # Sequence of moves towards the apple
104
+
105
+ # Explore neighbours
106
+ neighbours = get_neighbours(current_pos)
107
+ for neighbour in neighbours:
108
+ if is_position_safe(snake_body, tuple(neighbour)) and tuple(neighbour) not in visited:
109
+ #if tuple(neighbour) not in snake_body and tuple(neighbour) not in visited:
110
+ direction = [neighbour[0] - current_pos[0], neighbour[1] - current_pos[1]]
111
+ visited.add(tuple(neighbour))
112
+ new_path = path + [tuple(direction)]
113
+ queue.put((tuple(neighbour), new_path))
114
+
115
+ return [] # If no path is found
116
+
117
+ def is_position_safe(snake_body, position):
118
+ if position[0] < 0 or position[0] >= WIDTH or position[1] < 0 or position[1] >= HEIGHT:
119
+ return False
120
+ for square in snake_body:
121
+ if position == (square.x, square.y):
122
+ return False
123
+ return True
124
+
125
+ def set_path(snake, apple):
126
+ apple_pos = (apple.apple.x, apple.apple.y)
127
+ snake_head_pos = (snake.head.x, snake.head.y)
128
+ if snake.score == SNAKE_MAX_LEN - 1 and apple_pos in get_neighbours(snake_head_pos):
129
+ winning_path = [apple_pos]
130
+ return winning_path
131
+
132
+ duplicate_snake = create_duplicate_snake(snake)
133
+ dup_head_pos = (duplicate_snake.head.x, duplicate_snake.head.y)
134
+
135
+ initial_path = BFS(dup_head_pos, apple_pos, duplicate_snake.body)
136
+ secondary_path = []
137
+ #print(f"Score: {snake.score}")
138
+ if initial_path:
139
+ for position in initial_path:
140
+ duplicate_snake.go_to(position)
141
+ duplicate_snake.move()
142
+ duplicate_snake.grow()
143
+ secondary_path = path_to_tail(duplicate_snake)
144
+ if secondary_path:
145
+ #print(f"Initial path: {initial_path}")
146
+ return initial_path
147
+ if longest_path_to_tail(snake, apple_pos) and snake.score % 2 == 0:
148
+ #print(f"Longest Path to Tail: {longest_path_to_tail(snake, apple_pos)}")
149
+ return longest_path_to_tail(snake, apple_pos)
150
+ if find_safe_move(snake, apple_pos):
151
+ #print(f"Find safe move: {find_safe_move(snake, apple_pos)}")
152
+ return find_safe_move(snake, apple_pos)
153
+ if path_to_tail(snake):
154
+ #print(f"Path to tail: {path_to_tail(snake)}")
155
+ return path_to_tail(snake)
156
+ print("snake is in danger")
157
+
158
+ def path_to_tail(snake):
159
+ body_len = len(snake.body)
160
+ tail_pos = deepcopy(snake.tail)
161
+ #print(f"Tail Position: {tail_pos}")
162
+ if len(snake.body) > 1:
163
+ snake.tail = snake.body.pop(-1)
164
+ #print(tail_pos)
165
+ head_pos = (snake.head.x, snake.head.y)
166
+ snake_tail_pos = (tail_pos.x, tail_pos.y)
167
+ path = BFS(head_pos, (snake_tail_pos), snake.body)
168
+
169
+ if len(snake.body) < body_len:
170
+ snake.body.append(snake.tail)
171
+ snake.tail = snake.body[-1]
172
+
173
+ return path
settings.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+
3
+ WIDTH = 612
4
+ HEIGHT = 612
5
+ BLOCK_SIZE = 36
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 = 5
19
+ INITIAL_SNAKE_LEN = 1
20
+ SNAKE_MAX_LEN = ROWS * ROWS - INITIAL_SNAKE_LEN
21
+
22
+ GRID = [[x,y] for x in range(0, WIDTH, BLOCK_SIZE) for y in range(0, HEIGHT, BLOCK_SIZE)]
snake.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from settings import *
3
+ from copy import deepcopy
4
+
5
+ def create_duplicate_snake(snake):
6
+ dup_snake = Snake()
7
+ dup_snake.pos = deepcopy(snake.pos)
8
+ dup_snake.head = deepcopy(snake.head)
9
+ dup_snake.body = deepcopy(snake.body)
10
+ dup_snake.dead = deepcopy(snake.dead)
11
+ dup_snake.tail = deepcopy(snake.tail)
12
+ return dup_snake
13
+
14
+ class Snake:
15
+ def __init__(self):
16
+ # Initialize the snake at the center of the screen
17
+ self.x, self.y = 0, 0
18
+ self.pos = [1, 0] # [x, y] direction, initially set to right
19
+ self.head = pygame.Rect(self.x, self.y, BLOCK_SIZE, BLOCK_SIZE)
20
+ self.body = [] # List to keep track of snake's body segments
21
+ self.dead = False
22
+ if self.body:
23
+ self.tail = self.body[-1]
24
+ else:
25
+ self.tail = self.head
26
+ self.score = 0
27
+
28
+ def drawSnake(self):
29
+ pygame.draw.rect(SCREEN, SNAKE_CLR, self.head)
30
+ for segment in self.body:
31
+ pygame.draw.rect(SCREEN, SNAKE_CLR, segment)
32
+
33
+ def go_to(self, position):
34
+ if position[0] > 0 and self.pos != [-1, 0]:
35
+ self.pos = [1, 0]
36
+ elif position[0] < 0 and self.pos != [1, 0]:
37
+ self.pos = [-1, 0]
38
+ elif position[1] > 0 and self.pos != [0, 1]:
39
+ self.pos = [0, 1]
40
+ elif position[1] < 0 and self.pos != [0, -1]:
41
+ self.pos = [0, -1]
42
+
43
+ def move(self):
44
+ new_head = self.head.copy()
45
+ new_head.x += self.pos[0] * BLOCK_SIZE
46
+ new_head.y += self.pos[1] * BLOCK_SIZE
47
+
48
+ if self.body:
49
+ self.body.insert(0, new_head)
50
+ self.head = self.body[0]
51
+ else:
52
+ self.head = new_head
53
+
54
+ if not self.dead and self.body:
55
+ self.body.pop()
56
+
57
+ def grow(self):
58
+ self.score += 1
59
+ new_block = pygame.Rect(self.head.x, self.head.y, BLOCK_SIZE, BLOCK_SIZE)
60
+ self.body.insert(0, new_block)
61
+
62
+ class Apple:
63
+ def __init__(self):
64
+ self.spawn()
65
+
66
+ def spawn(self):
67
+ ''' Spawn a new apple on the screen at a random position '''
68
+ # Randomly spawn apple
69
+ self.x = random.randrange(0, WIDTH, BLOCK_SIZE)
70
+ self.y = random.randrange(0, HEIGHT, BLOCK_SIZE)
71
+ self.apple = pygame.Rect(self.x, self.y, BLOCK_SIZE, BLOCK_SIZE)
72
+
73
+ def drawApple(self):
74
+ pygame.draw.rect(SCREEN, APPLE_CLR, self.apple)
75
+
76
+ def checkSpawn(self, snake):
77
+ ''' Check if the apple collides with the snake. If so, respawn the apple. '''
78
+ for square in snake.body:
79
+ if self.apple.colliderect(square):
80
+ self.spawn() # Respawn apple if it collides with snake
81
+ #snake.grow()
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()