01-贪吃蛇自动寻路
项目背景
利用turtle库编写的贪吃蛇小程序,要求:
- 实现自动吃食物
- 不能将自己绕死。
自动寻路算法
算法核心:A*算法的简化(完全实现应该可以,但耗时太多了,我选择放弃)
算法实现:
首先是基础的贪吃蛇程序的实现,以下为预置代码:
import pathfinding as pf
from turtle import *
from random import *
snake = [[0, 0]] # 蛇的起始位置
aim_x, aim_y = 0, -10 # 蛇的起始方向
def square(x, y, size, sq_color):
"""绘制小正方形, 代表一格"""
color(sq_color)
up()
goto(x, y)
down()
begin_fill()
for i in range(4):
fd(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(x, y):
"""改变蛇的运动方向"""
global aim_x, aim_y
if x != -aim_x or y != -aim_y:
aim_x, aim_y = x, 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
food_x, food_y = new_food() # 食物的起始位置
def move():
global food_x, food_y
pf.pathfinding(food_x, food_y)
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()
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()
# 根据得分调节速度, 每到达一定分数会提高速度
if len(snake) < 10:
ontimer(move, 40)
elif len(snake) < 20:
ontimer(move, 30)
elif len(snake) < 30:
ontimer(move, 20)
elif len(snake) < 40:
ontimer(move, 10)
else:
ontimer(move, 0)
setup(420, 420)
title('贪吃蛇')
hideturtle()
tracer(False)
#listen()
#onkey(lambda: change(0, 10), 'Up')
#onkey(lambda: change(-10, 0), 'Left')
#onkey(lambda: change(0, -10), 'Down')
#onkey(lambda: change(10, 0), 'Right')
move()
done()
出于自动寻路的实现,对接口做了改动
算法:
- 以蛇头作为起点,食物位置为终点,当广度搜索找到食物所在坐标时停止
start = (s.snake[-1][0],s.snake[-1][1])
came_from[start] = None
frontier_put(start)
while frontier_empty():
current = frontier_get()
if (x,y) in came_from.keys():
break
for next in current.values():
if not_in_came_from(next) and not_in_reached(next):
#if rightway(next, start):
# continue
#else:
frontier_put(next)
came_from[next] = frontier[miner()]
reached_add(next)
frontier.pop(miner())
- 将能寻到食物的路径用字典储存
current = (x, y)
path = []
while current != start:
path.append(current)
try:
current = came_from[current]
except:
break
path.reverse() # 使第一步变成首位
came_from.clear()
reached.clear()
frontier.clear()
turn(path[0])
代码不足:
理论上完善的自动寻路应该能处理没有到达食物路径的情况,但是不会码这个,也就是说当身体横跨地图,挡住蛇头通往食物的路时会出bug,主要还是犯懒没有深入研究A*和dfs
下面是完整代码,希望有大佬指出不足,比如说代码风格或者某些部分的实现,理论上来说有些部分应该用类来做,但是类还没学。。。。。。
import snake as s
frontier = dict()
f_count = 0
came_from = dict()# path A->B is stored as came_from[B] == A
reached = dict()
r_count = 0
def frontier_put(ch):#添加
global f_count
frontier[f_count] = ch
f_count += 1
def reached_add(ch):
global r_count
reached[r_count] = ch
r_count += 1
def frontier_empty():#判断是否为控
if frontier:
return True
else: return False
def miner():#获得最小键值
arr = frontier.keys()
ch = []
for i in arr:
ch.append(i)
tep = ch[0]
for i in ch:
if tep >= i:
tep = i
return tep
def frontier_get():
ch = dict()
count = 0
m = miner()
x = frontier[m][0]
y = frontier[m][1]
if s.inside(x+10, y) and [x+10, y] not in s.snake:#判断其周围是否合法
ch[count] = (x+10, y)
count +=1
if s.inside(x-10, y) and [x-10, y] not in s.snake:
ch[count] = (x-10, y)
count +=1
if s.inside(x, y+10) and [x, y+10] not in s.snake:
ch[count] = (x, y+10)
count +=1
if s.inside(x, y-10) and [x, y-10] not in s.snake:
ch[count] = (x, y-10)
count +=1
return ch
def not_in_came_from(tep):
ch = came_from.values()
for i in ch:
if tep == i:
return False
return True
def not_in_reached(tep):
ch = reached.values()
for i in ch:
if tep == i:
return False
return True
def pathfinding(x, y):
start = (s.snake[-1][0],s.snake[-1][1])
came_from[start] = None
frontier_put(start)
while frontier_empty():
current = frontier_get()
if (x,y) in came_from.keys():
break
for next in current.values():
if not_in_came_from(next) and not_in_reached(next):
#if rightway(next, start):
# continue
#else:
frontier_put(next)
came_from[next] = frontier[miner()]
reached_add(next)
frontier.pop(miner())
current = (x, y)
path = []
while current != start:
path.append(current)
try:
current = came_from[current]
except:
break
path.reverse() # 使第一步变成首位
came_from.clear()
reached.clear()
frontier.clear()
turn(path[0])
def turn(path):
# 0 右
# 1 上
# 2 左
# 3 下
x = path[0]
y = path[1]
start_x, start_y = s.snake[-1][0],s.snake[-1][1]
pre_x = x - start_x
pre_y = y - start_y
if pre_x>0:
pre_x = 10
elif pre_x<0:
pre_x = -10
if pre_y>0:
pre_y = 10
elif pre_y<0:
pre_y = -10
s.change(pre_x, pre_y)
#def goto(x, y):
# s.snake[-1][0]
#以下为实验输入端口
#if s.snake[-1][0] > x:
# if s.aim_x != 10:
# turn(2)
# elif s.aim_x == 10:
# if s.snake[-1][1] > y:
# turn(3)
# elif s.snake[-1][1] < y:
# turn(1)
# elif s.snake[-1][1] == y:
# if not s.inside(s.snake[-1][0], s.snake[-1][1]-10) or s.inside(s.snake[-1][0], s.snake[-1][1]-10) in s.snake:
# turn(1)
# else: turn(3)
#elif s.snake[-1][0] < x:
# if s.aim_x != -10:
# turn(0)
# elif s.aim_x == -10:
# if s.snake[-1][1] > y:
# turn(3)
# elif s.snake[-1][1] < y:
# turn(1)
# elif s.snake[-1][1] == y:
# if not s.inside(s.snake[-1][0], s.snake[-1][1]-10) or s.inside(s.snake[-1][0], s.snake[-1][1]-10) in s.snake:
# turn(1)
# else: turn(3)
#elif s.snake[-1][0] == x:
# if s.snake[-1][1] < y:
# if s.aim_y == 10:
# turn(1)
# elif s.aim_y == -10:
# if not s.inside(s.snake[-1][0]+10, s.snake[-1][1]) or s.inside(s.snake[-1][0]+10, s.snake[-1][1]) in s.snake:
# turn(2)
# else: turn(0)
# else:
# turn(1)
# elif s.snake[-1][1] > y:
# if s.aim_y == -10:
# turn(3)
# elif s.aim_y == 10:
# if not s.inside(s.snake[-1][0]+10, s.snake[-1][1]) or s.inside(s.snake[-1][0]+10, s.snake[-1][1]) in s.snake:
# turn(2)
# else: turn(0)
# else:
# turn(3)
# else:
# 1