简介:
这是一个简单的迷宫小游戏,通过 numpy.ndarray 来存储迷宫的格点信息,通过 turtle 来展示迷宫信息并通过上下左右按键来进行移动。格点信息是通过随机产生的,所以可以做很大规模;另外通过 opencv 的水漫来检测随机产生的迷宫是否是通的。迷宫的起点在左下角,终点是右上角。
效果如下:
整体介绍
0. 导入库
import turtle as tt
import numpy as np
import cv2
1. 通过numpy创建迷宫格点信息
# 设置迷宫
Size = 100
Threshold = 0.6
def set_maze(size=Size, threshold=Threshold) -> np.ndarray:
maze = np.ones(shape=[size, size], dtype=int)*255
a = np.random.random(size=[size, size])
a[0, 0] = 0
a[-1, -1] = 0
maze[np.where(a<threshold)] = 0
return maze
这里,产生的 maze 矩阵即为记录 0 和 1 的格点,Size 即为迷宫的尺寸(正方形)。
threshold 是随机数比较的阈值,这个值不能太小,不然迷宫会堵死。
2. 通过opencv检验迷宫的连通性
# 水漫,检验连通性
def flood(A:np.ndarray, seed=(0,0)) -> np.ndarray:
mat_temp = A.copy()
N = mat_temp.shape[0]
mask = np.zeros(shape=(N+2,N+2), dtype=np.uint8)
cv2.floodFill(mat_temp, mask, seedPoint=seed, newVal=100)
return mask[1:-1,1:-1]
# 设置可用的迷宫
for i in range(1000):
maze = set_maze()
mask = flood(maze)
if mask[-1, -1]>0:
print(i, 'create')
break
if mask[-1, -1]<0:
print('YOUR MAZE DOES NOT WORK!')
我们将左下角 [0,0] 作为起点,右上角 [N-1, N-1] 作为终点。用 opencv 的 floodFill 函数检验连通性,如果随机出来的矩阵不连通,那就继续随机。
如果你的输出里面没有显示 create,那就不要玩下去了,因为是堵死的……
当然你也可以直接手动输入迷宫信息,创建一个固定的迷宫,但那样太费事而且没啥意思了。
3. 画迷宫
# 画图setup
n_show = 10
size_show = 50
tt.tracer(False)
tt.setworldcoordinates(0, 0, n_show*size_show, n_show*size_show)
tt.bgcolor('gray')
tt.penup()
tt.hideturtle()
def draw_rect(size=size_show, color='white') -> None:
tt.setheading(0)
tt.fillcolor(color)
tt.begin_fill()
for _ in range(4):
tt.forward(size)
tt.left(90)
tt.end_fill()
# 画迷宫
Maze = np.array(np.where(maze==0)).T
for xy in Maze:
tt.goto(xy[0]*size_show, xy[1]*size_show)
draw_rect()
tt.goto(Maze[-1][0]*size_show, Maze[-1][1]*size_show)
draw_rect(color='#66ccff')
这里就是很普通的 turtle 画图啦,画出来差不多是这样的:
灰色方块代表封闭的地方,白色方块是能走的,一个方块是一个点。
另外注意,画图需要点时间。对于尺寸为 100,也就是有1万个点,画图大概要1秒钟;如果你的迷宫尺寸很大,可能得稍等个几秒。
4. 开始走迷宫
# 迷宫开始,移动
class Moving():
def __init__(self, size=40) -> None:
self.pos = np.array([0, 0])
self.size = size
self.show_range = np.array([0, 0, n_show*size_show, n_show*size_show])
# 移动
def move(self, direction):
temp = self.pos + np.array(direction)
temp = np.clip(temp, 0, Size-1)
if maze[temp[0], temp[1]] == 0:
tt.dot(self.size, 'white')
self.pos = temp
self.show()
def show(self):
print('x:{}\ty:{}'.format(*self.pos))
tt.goto((self.pos[0]+1/2)*size_show, (self.pos[1]+1/2)*size_show)
tt.dot(self.size, 'green')
self.show_range[:2] = (self.pos-n_show/2)*size_show
self.show_range[2:] = (self.pos+n_show/2)*size_show
tt.setworldcoordinates(*self.show_range)
tt.update()
if (self.pos==Size-1).all():
print('Success!')
# 按键用到的接口
def up(self):
self.move([0, 1])
def down(self):
self.move([0, -1])
def left(self):
self.move([-1, 0])
def right(self):
self.move([1, 0])
# 按键移动
Mov = Moving()
Mov.show()
tt.onkey(Mov.up, 'Up')
tt.onkey(Mov.down, 'Down')
tt.onkey(Mov.left, 'Left')
tt.onkey(Mov.right, 'Right')
tt.listen()
tt.mainloop()
这个就不多说了,要注意的几个点是:
- 窗口的视图要随着你的位置而移动(当然如果能一下子展示完的话也不用这样。
- 每移动到下一个点,要抹去前一步的痕迹……
- 要写函数来对接 turtle 的按键接口。
全部代码:
有问题欢迎指出 ~
import turtle as tt
import numpy as np
import cv2
# 设置迷宫
Size = 10
Threshold = 0.6
def set_maze(size=Size, threshold=Threshold) -> np.ndarray:
maze = np.ones(shape=[size, size], dtype=int)*255
a = np.random.random(size=[size, size])
a[0, 0] = 0
a[-1, -1] = 0
maze[np.where(a<threshold)] = 0
return maze
# 水漫,检验连通性
def flood(A:np.ndarray, seed=(0,0)) -> np.ndarray:
mat_temp = A.copy()
N = mat_temp.shape[0]
mask = np.zeros(shape=(N+2,N+2), dtype=np.uint8)
cv2.floodFill(mat_temp, mask, seedPoint=seed, newVal=100)
return mask[1:-1,1:-1]
# 设置可用的迷宫
for i in range(1000):
maze = set_maze()
mask = flood(maze)
if mask[-1, -1]>0:
print(i, 'create')
break
if mask[-1, -1]<0:
print('YOUR MAZE DOES NOT WORK!')
# 画图setup
n_show = 10
size_show = 50
tt.tracer(False)
tt.setworldcoordinates(0, 0, n_show*size_show, n_show*size_show)
tt.bgcolor('gray')
tt.penup()
tt.hideturtle()
def draw_rect(size=size_show, color='white') -> None:
tt.setheading(0)
tt.fillcolor(color)
tt.begin_fill()
for _ in range(4):
tt.forward(size)
tt.left(90)
tt.end_fill()
# 画迷宫
Maze = np.array(np.where(maze==0)).T
for xy in Maze:
tt.goto(xy[0]*size_show, xy[1]*size_show)
draw_rect()
tt.goto(Maze[-1][0]*size_show, Maze[-1][1]*size_show)
draw_rect(color='#66ccff')
# 迷宫开始,移动
class Moving():
def __init__(self, size=40) -> None:
self.pos = np.array([0, 0])
self.size = size
self.show_range = np.array([0, 0, n_show*size_show, n_show*size_show])
# 移动
def move(self, direction):
temp = self.pos + np.array(direction)
temp = np.clip(temp, 0, Size-1)
if maze[temp[0], temp[1]] == 0:
tt.dot(self.size, 'white')
self.pos = temp
self.show()
def show(self):
print('x:{}\ty:{}'.format(*self.pos))
tt.goto((self.pos[0]+1/2)*size_show, (self.pos[1]+1/2)*size_show)
tt.dot(self.size, 'green')
self.show_range[:2] = (self.pos-n_show/2)*size_show
self.show_range[2:] = (self.pos+n_show/2)*size_show
tt.setworldcoordinates(*self.show_range)
tt.update()
if (self.pos==Size-1).all():
print('Success!')
# 按键用到的接口
def up(self):
self.move([0, 1])
def down(self):
self.move([0, -1])
def left(self):
self.move([-1, 0])
def right(self):
self.move([1, 0])
# 按键移动
Mov = Moving()
Mov.show()
tt.onkey(Mov.up, 'Up')
tt.onkey(Mov.down, 'Down')
tt.onkey(Mov.left, 'Left')
tt.onkey(Mov.right, 'Right')
tt.listen()
tt.mainloop()