游戏逻辑
snake.py
from turtle import *
from random import *
from pathfinding import GameGraph
def square(x, y, size, sq_color):
'''绘制小正方形, 代表一格'''
penup()
goto(x, y)
color(sq_color)
pendown()
begin_fill()
for i in range(4):
forward(size)
left(90)
end_fill()
def frame():
'''绘制边框'''
for i in range(-210, 200, 10):
square(i, -200, 10, 'gray')
square(i, 200, 10, 'gray')
for i in range(-200, 200, 10):
square(-210, i, 10, 'gray')
square(190, i, 10, 'gray')
def change(direction):
'''改变蛇的运动方向'''
global aim_x, aim_y
x, y = direction
if x != -aim_x or y != -aim_y:
aim_x = x
aim_y = y
def inside(head_x, head_y):
'''判断蛇是否在边框内'''
if -210 < head_x < 190 and -200 < head_y < 200:
return True
else:
return False
all_food = [] # 所有食物的位置
for x in range(-200, 190, 10):
for y in range(-190, 200, 10):
all_food.append((x, y))
def new_food():
food = all_food.copy()
for i in snake: # 去掉蛇所在的位置
food.remove(i)
new_food_x, new_food_y = food.pop(randint(0, len(food) - 1))
return new_food_x, new_food_y
snake = [(0, 0)] # 蛇的起始位置
food_x, food_y = new_food() # 食物的起始位置
aim_x, aim_y = 0, 0 # 蛇的起始方向
def move():
global food_x, food_y, game_graph
direction = game_graph.safe_move()
if direction is not None:
change(direction)
head_move_x = snake[-1][0] + aim_x
head_move_y = snake[-1][1] + aim_y
# 判断是否撞到边框或者撞到自己
if not inside(head_move_x, head_move_y) or [head_move_x, head_move_y] in snake:
square(head_move_x, head_move_y, 10, 'red')
update()
print('得分: ', len(snake))
return
snake.append((head_move_x, head_move_y))
# 判断是否吃到食物以及是否胜利
if head_move_x == food_x and head_move_y == food_y:
if(len(snake) == len(all_food)):
print('YOU WIN!')
return
else:
food_x, food_y = new_food()
game_graph.set_food((food_x, food_y))
else:
snake.pop(0)
clear()
# 绘制边框, 蛇和食物
frame()
for body in snake:
square(body[0], body[1], 10, 'black')
square(food_x, food_y, 10, 'green')
update()
game_graph = GameGraph(snake.copy(), (food_x, food_y), {'xmin': -200, 'xmax': 180, 'ymin': -190, 'ymax': 190})
ontimer(move)
game_graph = GameGraph(snake.copy(), (food_x, food_y), {'xmin': -200, 'xmax': 180, 'ymin': -190, 'ymax': 190})
寻路算法
pathfinding.py
from turtle import *
from random import *
from queue import PriorityQueue
import random
class GameGraph:
def __init__(self, snake, food, boundaries):
self.snake = snake
self.food = food
self.boundaries = boundaries
def set_food(self, food): #更新食物位置
self.food = food
def heuristic(self, a, b): #计算曼哈顿距离
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def get_neighbors(self, node): #获取相邻位置
dirs = [(0, 10), (0, -10), (10, 0), (-10, 0)]
result = []
for dir in dirs:
neighbor = (node[0] + dir[0], node[1] + dir[1])
if self.boundaries['xmin'] <= neighbor[0] <= self.boundaries['xmax'] and self.boundaries['ymin'] <= neighbor[1] <= self.boundaries['ymax'] and neighbor not in self.snake:
result.append(neighbor)
return result
def pathfinding_to_tail(self): #判断能否找到尾巴
path = self._pathfinding(self.snake[0])
if path is None or len(path) < 2:
return None
return (path[1][0] - path[0][0], path[1][1] - path[0][1])
def pathfinding(self): #判断能否找到食物,找不到则寻找自己的尾巴
path = self._pathfinding(self.food)
if path is None or len(path) < 2:
return self.pathfinding_to_tail()
return (path[1][0] - path[0][0], path[1][1] - path[0][1])
def _pathfinding(self,end): # A*算法
start = self.snake[-1] # 获取蛇的当前位置作为起始点
frontier = PriorityQueue() # 创建一个优先队列,用于存储待探索的节点
frontier.put((0, start)) # 将起始点放入队列,优先级为0
came_from = {} # 创建一个字典,用于存储每个节点的前驱节点
cost_so_far = {} # 创建一个字典,用于存储从起始点到每个节点的已知最小代价
came_from[start] = None # 起始点的前驱节点为None
cost_so_far[start] = 0 # 起始点的代价为0
while not frontier.empty(): # 当队列不为空时,继续循环
current = frontier.get()[1] # 从队列中取出优先级最高(代价最小)的节点
if current == end: # 如果当前节点就是目标节点,结束循环
break
for next in self.get_neighbors(current): # 遍历当前节点的所有邻居
new_cost = cost_so_far[current] + 1 # 计算从起始点到邻居节点的新代价
if next not in cost_so_far or new_cost < cost_so_far[next]: # 如果邻居节点还未被探索过,或者新代价小于已知代价
cost_so_far[next] = new_cost # 更新代价
priority = new_cost + self.heuristic(end, next) # 计算优先级,等于新代价加上邻居节点到目标节点的估计代价(启发式函数)
frontier.put((priority, next)) # 将邻居节点放入队列
came_from[next] = current # 记录邻居节点的前驱节点
if end not in came_from: # 如果目标节点没有被探索过,返回None
return None
current = end # 从目标节点开始
path = [] # 创建一个列表,用于存储从起始点到目标节点的路径
while current != start: # 当前节点不是起始点时,继续循环
path.append(current) # 将当前节点添加到路径中
current = came_from[current] # 移动到前驱节点
path.append(start) # 将起始点添加到路径中
path.reverse() # 反转路径,使其从起始点开始
return path # 返回路径
def getACellThatIsFarthestToGoal(self): # 获取离目标最远的格子
max_distance = -1
farthest_cell = None
for neighbor in self.get_neighbors(self.snake[-1]):
distance = self.heuristic(neighbor, self.food)
self.snake.append(neighbor)
if self.pathfinding_to_tail() is not None and distance > max_distance:
max_distance = distance
farthest_cell = neighbor
self.snake.pop()
if farthest_cell is None: # 如果所有的邻居格子都不能到达尾巴,选择一个默认的格子
farthest_cell = self.get_neighbors(self.snake[-1])[0]
return farthest_cell
def safe_move(self):
direction = self.pathfinding()
if direction is None:
direction = self.pathfinding_to_tail() # 如果找不到吃苹果的路线,尝试找到尾巴的路线
if direction is None:
farthest_cell = self.getACellThatIsFarthestToGoal()
if farthest_cell is not None:
direction = (farthest_cell[0] - self.snake[-1][0], farthest_cell[1] - self.snake[-1][1])
else:
direction = random.choice([(-10,0),(10,0),(0,10),(0,-10)]) # 如果找不到安全的移动方向,选择一个默认的方向
return direction
运行
main.py
from snake import *
from pathfinding import *
from turtle import *
from random import *
setup(420, 420)
title('贪吃蛇')
hideturtle()
tracer(False)
game_graph = GameGraph(snake.copy(), (food_x, food_y), {'xmin': -200, 'xmax': 180, 'ymin': -190, 'ymax': 190})
move()
done()