snake-pathfinding-AI / pathfinding.py
rhdang1
Edit pathfinding to improve success rate
5625b4c
from settings import *
from queue import Queue
from snake import create_duplicate_snake, deepcopy
from random import randrange
def get_neighbours(position):
'''
Get neighbouring positions from the current position
Args:
- position: Tuple representing the current position (x, y)
Returns:
- List of neighbouring positions
'''
neighbours = [[position[0] + BLOCK_SIZE, position[1]],
[position[0] - BLOCK_SIZE, position[1]],
[position[0], position[1] + BLOCK_SIZE],
[position[0], position[1] - BLOCK_SIZE]]
neighbours_within_grid = []
for pos in neighbours:
if pos in GRID:
neighbours_within_grid.append(pos)
return neighbours_within_grid
def get_available_neighbours(snake, position, apple_pos):
valid_neighbours = []
neighbours = get_neighbours(position)
for neighbour in neighbours:
if is_position_available(snake, neighbour) and apple_pos != neighbour:
valid_neighbours.append(neighbour)
return valid_neighbours
def is_position_available(snake, position):
if (0 <= position[0] <= ROWS or 0 <= position[1] <= ROWS) and position not in snake.body:
return True
return False
def distance(position1, position2):
x1, x2 = position1[0], position2[0]
y1, y2 = position1[1], position2[1]
return abs(x2 - x1) + abs(y2 - y1)
def longest_path_to_tail(snake, apple_pos):
neighbours = get_available_neighbours(snake, (snake.head.x, snake.head.y), apple_pos)
path = []
if neighbours:
d = -9999
for neighbour in neighbours:
tail_pos = (snake.tail.x, snake.tail.y)
if distance(neighbour, tail_pos) > d:
dup_snake = create_duplicate_snake(snake)
dup_snake.go_to(neighbour)
dup_snake.move()
if dup_snake.head.x == apple_pos[0] and dup_snake.head.y == apple_pos[1]:
dup_snake.grow()
if path_to_tail(dup_snake):
path.append(neighbour)
d = distance(neighbour, tail_pos)
if path:
return [path[-1]]
def find_safe_move(snake, apple_pos):
neighbours = get_available_neighbours(snake, (snake.head.x, snake.head.y), apple_pos)
path = []
if neighbours:
path.append(neighbours[randrange(len(neighbours))])
dup_snake = create_duplicate_snake(snake)
for move in path:
dup_snake.go_to(move)
dup_snake.move()
if path_to_tail(dup_snake):
return path
else:
return path_to_tail(snake)
def BFS(start_pos, target_pos, snake_body):
'''
Perform Breadth-First Search (BFS) to find the shortest path from the snake's head to the apple
Args:
- start_pos: Tuple representing the starting position (snake's head)
- target_pos: Tuple representing the target position (apple)
- snake_body: List representing the coordinates of the snake's body
Returns:
- List representing the sequence of moves to the apple as a list of tuples (coordinates)
'''
queue = Queue()
queue.put((start_pos, [])) # Initialize queue with snake's head position and empty path
visited = set()
visited.add(start_pos)
while not queue.empty():
current_pos, path = queue.get()
# Return path if apple is found
if current_pos == target_pos:
return path # Sequence of moves towards the apple
# Explore neighbours
neighbours = get_neighbours(current_pos)
for neighbour in neighbours:
if is_position_safe(snake_body, tuple(neighbour)) and tuple(neighbour) not in visited:
#if tuple(neighbour) not in snake_body and tuple(neighbour) not in visited:
direction = [neighbour[0] - current_pos[0], neighbour[1] - current_pos[1]]
visited.add(tuple(neighbour))
new_path = path + [tuple(direction)]
queue.put((tuple(neighbour), new_path))
return [] # If no path is found
def is_position_safe(snake_body, position):
if position[0] < 0 or position[0] >= WIDTH or position[1] < 0 or position[1] >= HEIGHT:
return False
for square in snake_body:
if position == (square.x, square.y):
return False
return True
def set_path(snake, apple):
apple_pos = (apple.apple.x, apple.apple.y)
snake_head_pos = (snake.head.x, snake.head.y)
if snake.score == SNAKE_MAX_LEN - 1 and apple_pos in get_neighbours(snake_head_pos):
winning_path = [apple_pos]
return winning_path
duplicate_snake = create_duplicate_snake(snake)
dup_head_pos = (duplicate_snake.head.x, duplicate_snake.head.y)
initial_path = BFS(dup_head_pos, apple_pos, duplicate_snake.body)
secondary_path = []
#print(f"Score: {snake.score}")
if initial_path:
for position in initial_path:
duplicate_snake.go_to(position)
duplicate_snake.move()
duplicate_snake.grow()
secondary_path = path_to_tail(duplicate_snake)
if secondary_path:
#print(f"Initial path: {initial_path}")
return initial_path
if longest_path_to_tail(snake, apple_pos) and snake.score % 2 == 0:
#print(f"Longest Path to Tail: {longest_path_to_tail(snake, apple_pos)}")
return longest_path_to_tail(snake, apple_pos)
if find_safe_move(snake, apple_pos):
#print(f"Find safe move: {find_safe_move(snake, apple_pos)}")
return find_safe_move(snake, apple_pos)
if path_to_tail(snake):
#print(f"Path to tail: {path_to_tail(snake)}")
return path_to_tail(snake)
print("snake is in danger")
def path_to_tail(snake):
body_len = len(snake.body)
tail_pos = deepcopy(snake.tail)
#print(f"Tail Position: {tail_pos}")
if len(snake.body) > 1:
snake.tail = snake.body.pop(-1)
#print(tail_pos)
head_pos = (snake.head.x, snake.head.y)
snake_tail_pos = (tail_pos.x, tail_pos.y)
path = BFS(head_pos, (snake_tail_pos), snake.body)
if len(snake.body) < body_len:
snake.body.append(snake.tail)
snake.tail = snake.body[-1]
return path