详解如何利用Python绘制迷宫小游戏

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

详解如何利⽤Python绘制迷宫⼩游戏
⽬录
构思
绘制迷宫
⾛出迷宫
完整代码
更⼤的挑战
关于坐标系设置
周末在家,⼉⼦闹着要玩游戏,让玩吧,不利于健康,不让玩吧,扛不住他折腾,于是想,不如⼀起搞个⼩游戏玩玩!
之前给他编过猜数字和掷骰⼦游戏,现在已经没有吸引⼒了,就对他说:“我们来玩个迷宫游戏吧。


果不其然,有了兴趣,于是和他⼀起设计实现起来,现在⼀起看看我们是怎么做的吧,说不定也能成为⼀个陪娃神器~
先⼀睹为快:
构思
迷宫游戏,相对⽐较简单,设置好地图,然后⽤递归算法来寻找出⼝,并将过程显⽰出来,增强趣味性。

不如想到需要让孩⼦⼀起参与,选择了绘图程序 Turtle作为实现⼯具。

这样就可以先在纸上绘制⼀个迷宫,然后编写成代码,让 Turtle 去绘制,因为孩⼦⽤笔画过,所以在实现代码时,他可以充分参与,不仅是为了得到最终的游戏,⽽且更是享受制作过程,开发编程思维,说不定省了⼀笔不⼩的少⼉编程费⽤哈哈哈~
⾸先和孩⼦⼀起制作迷宫,在纸上画出 5 X 5 的⼩格⼦,然后,让他在格⼦中画⼀条通路,像这样:
绘制迷宫
然后,将这幅图转化为⼀个迷宫矩阵,⽤ 1 表⽰墙,⽤空格表⽰通路,需要注意的是⽹格每条边线都是墙,连通部分的墙需要打通,成为路。

这时可以和他⼀起来实现,⽐如让他⽤⾃⼰的积⽊等摆设⼀个迷宫,⽽我们来做数字化转化,最后转化成的结果是:
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
如果孩⼦看不清楚,可以将路径表⽰出来哈哈哈:
1 1 1 1 1 1 1 1 1 1 1
->_____ 1 _____ 1 1 1
1 1 1 | 1 | 1 | 1 1 1
1 ____| 1 | 1 |___ 1
1 | 1 1 1 | 1 1 1 | 1
1 |____ 1 | 1 ____| 1
1 1 1 | 1 | 1 | 1 1 1
1 ____| 1 | 1 |____ 1
1 | 1 1 1 | 1 1 1 | 1
1 |_______| 1 1 1 | 1
1 1 1 1 1 1 1 1 1\|/1
做完了迷宫数字化,就需要将迷宫在电脑上表⽰出来了。

之所以选择 Turtle,就是因为它会像⽤笔做图画⼀样,可以让孩⼦充分参与。

找出⼀张纸,⽤刚才整理的迷宫数字化结果作为指导绘图,遇到 1 就画⼀个⼩⽅格,遇到空格就跳过,可以和孩⼦⼀起画,主要是让他体会过程中的规律。

好了,趁他绘制的时候,我们来实现绘制代码吧。

⾸先需要知道 Turtle 的⼀些特点:
Turtle 的初始坐标在屏幕中⼼,可以将屏幕分成平⾯坐标系的四个象限
Turtle 画笔默认的移动最⼩单位是⼀个像素,因此需要做坐标点的初始化
Turtle 画笔移动都是相对于笔尖的朝向的,因此需要特别注意笔尖朝向
实现的⽅式和孩⼦⽤笔画是⼀样的,从第⼀个格⼦画起:
效果
下⾯看看代码:
def drawCenteredBox(self, x, y, color):
self.t.up()
self.t.goto(x - 0.5, y - 0.5)
self.t.color('black', color)
self.t.setheading(90)
self.t.down()
self.t.begin_fill()
for _ in range(4):
self.t.forward(1)
self.t.right(90)
self.t.end_fill()
update()
1. drawCenteredBox 是迷宫类 Maze 的成员⽅法,self 指的就是迷宫类本⾝,可以暂时将其理解为全局变量
2. self.t 是⼀个 Turtle 模块实例,可以理解成画笔
3. up ⽅法表⽰抬起笔尖
4. goto ⽅法的作⽤是移动到指定的位置,这⾥需要移动到指定位置的左下⾓,所以各⾃减去了 0.5(这⾥做了坐标值转
化,后⾯会有说明)
5. color 表⽰设置颜⾊,两个参数分别是笔的颜⾊和填充颜⾊
6. setheading 表⽰让笔尖朝上,即将笔尖朝向 90 度
7. down 表⽰落下笔尖,意思是随后的移动相当于绘制
8. begin_fill 表⽰准备填充,也就是它会把从调⽤起到调⽤ end_fill 为⽌所绘制的区域做填充
9. 然后是循环四次,⽤来绘制⽅格,循环内,每次向前(笔尖朝向)绘制⼀个单位,向右转 90 度,这样就绘制好了⼀个
⽅格
10. end_fill 即为填充当前绘制的⽅格
11. update 表⽰更新⼀下绘图区域
看看这个过程,是不是和孩⼦⼿⼯绘制⼀模⼀样!
现在遍历整个迷宫矩阵,不断调⽤ drawCenteredBox 就可以绘制出迷宫了:
效果
代码如下:
def drawMaze(self):
for y in range(self.rowsInMaze):
for x in range(self.columnsInMaze):
if self.mazelist[y][x] == 1:
self.drawCenteredBox(x + self.xTranslate, -y + self.yTranslate, 'tan')
rowsInMaze、columnsInMaze 表⽰迷宫矩阵的⾏和列
tan 为沙漠迷彩⾊的颜⾊名称
⾛出迷宫
可以先问问孩⼦,让他想想办法。

实现思路也很简单,就是超⼀个⽅向⾛,如果是墙,就换⼀个⽅向,如果不是墙,就继续⾛下去,如此往复……
但是,这⾥可以和孩⼦做个预演,⽐如迷宫很⼤的时候,记不住⾛过哪些路怎么办?
探索了⼀条路,⾛不通,返回后,不记得⾛过哪些路,这是⾮常危险的事情,如果有种⽅法可以记住⾛过的路,就好了。

这⾥我给⼉⼦讲了⼀下忒修斯⼤战⽜头怪[3]的古希腊神话传说,启发他想出好的⽅法。

如何⽤代码实现呢,只要在迷宫矩阵种,标记⼀下⾛过的路就可以了:
PART_OF_PATH = 0
OBSTACLE = 1
TRIED = 3
DEAD_END = 4
def search(maze, startRow, startColumn): # 从指定的点开始搜索
if maze[startRow][startColumn] == OBSTACLE:
return False
if maze[startRow][startColumn] == TRIED:
return False
if maze.isExit(startRow, startColumn):
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
return True
maze.updatePosition(startRow, startColumn, TRIED)
found = search(maze, startRow-1, startColumn) or \
search(maze, startRow, startColumn-1) or \
search(maze, startRow+1, startColumn) or \
search(maze, startRow, startColumn+1)
if found:
maze.updatePosition(startRow, startColumn, PART_OF_PATH)
else:
maze.updatePosition(startRow, startColumn, DEAD_END)
return found
因为使⽤了递归⽅式,所以代码⽐较简短,我们来看看:
1. PART_OF_PATH、OBSTACLE、TRIED、DEAD_END 是四个全局变量,分别表⽰迷宫矩阵中的通路,墙,探索过的
路和死路
2. search ⽅法⽤于探索迷宫,接受⼀个迷宫对象,和起始位置
3. 然后看看指定的位置是否为墙、或者是⾛过的,以及是否是出⼝
4. 然后继续探索,讲指定的位置标记为已⾛过
5. 接下来朝四个⽅向探索,分别是像西、向东、向南、向北
6. 每个⽅向的探索都是递归的调⽤ search ⽅法
7. 如果探索的结果是找到了出⼝,就将当前的位置标记为路线,否则标记为死路
这⾥还需要看看 updatePosition ⽅法的实现:
def updatePosition(self, row, col, val=None):
if val:
self.mazelist[row][col] = val
self.moveTurtle(col, row)
if val == PART_OF_PATH:
color = 'green'
elif val == OBSTACLE:
color = 'red'
elif val == TRIED:
color = 'black'
elif val == DEAD_END:
color = 'red'
else:
color = None
if color:
self.dropBreadcrumb(color)
def moveTurtle(self, x, y):
self.t.setheading(self.t.towards(x+self.xTranslate, -y+self.yTranslate))
self.t.goto(x+self.xTranslate, -y+self.yTranslate)
def dropBreadcrumb(self, color):
self.t.dot(color)
updatePosition ⽅法本⾝不复杂,⾸先对迷宫矩阵做标记,然后将笔尖移动到指定的点,之后判断标记的值,在指定的点上画点
移动的⽅法是 moveTurtle,⾸先抬起笔尖,然后将笔尖转向将要移动过去的点
Turtle 的 towards ⽅法会计算⼀个笔尖当前点到指定点之间的⼀个夹⾓,作⽤是让笔尖转向要移动过去的点,其中xTranslate 和 yTranslate 是在坐标系中像素点的偏移量(后⾯会有说明)
Turtle 的 dot ⽅法作⽤是绘制⼀个点
看⼀下效果:
⾛出迷宫
完整代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: 闲欢
"""
import pygame, random, sys, time
pygame.init()
screen = pygame.display.set_mode([600, 400])
screen.fill((255, 255, 255))
# 圆的半径
radius = [0] * 10
# 圆的半径增量
circleDelt = [0] * 10
# 圆是否存在,False代表该索引值下的圆不存在,True代表存在
circleExists = [False] * 10
# 圆的坐标x轴
circleX = [0] * 10
# 圆的坐标y轴
circleY = [0] * 10
# 颜⾊RGB值
RGBx = [0] * 10
RGBy = [0] * 10
RGBz = [0] * 10
while True:
# 停顿0.1秒
time.sleep(0.1)
for event in pygame.event.get():
# ⿏标按下
if event.type == pygame.MOUSEBUTTONDOWN:
# 获取圆不存在的索引值
num = circleExists.index(False)
# 将该索引值的圆设置为存在
circleExists[num] = True
# 圆的半径设置为0
radius[num] = 0
# 获取⿏标坐标
circleX[num], circleY[num] = pygame.mouse.get_pos()
# 随机获取颜⾊值
RGBx[num] = random.randint(0, 255)
RGBy[num] = random.randint(0, 255)
RGBz[num] = random.randint(0, 255)
# 画圆
pygame.draw.circle(screen, pygame.Color(RGBx[num], RGBy[num], RGBz[num]),
(circleX[num], circleY[num]), radius[num], 1)
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
for i in range(10):
# 圆不存在则跳过循环
if not circleExists[i]:
else:
# 随机圆的⼤⼩
if radius[i] < random.randint(10, 50):
# 圆的随机半径增量
circleDelt[i] = random.randint(0, 5)
radius[i] += circleDelt[i]
# 画圆
pygame.draw.circle(screen, pygame.Color(RGBx[i], RGBy[i], RGBz[i]),
(circleX[i], circleY[i]), radius[i], 1)
else:
# 若圆已达到最⼤,这将该索引值的圆设置为不存在
circleExists[i] = False
pygame.display.update()
更⼤的挑战
当孩⼦看到⾃⼰做的迷宫,被⼩乌龟⾛出来时,别提有多开⼼了。

不过,没多久,他就想要更复杂的迷宫,有多条分⽀的迷宫。

显然有⼿⼯的⽅式有点困难,⽽且⽆趣。

需要让程序⾃动⽣成迷宫。

分析代码之后,将其中的迷宫类移植过来,⽣成的结果之间导⼊到笔者写的迷宫类中,将迷宫规模设置为 100 X 100,震撼了:
巨型迷宫
看着⼩乌龟在巨⼤的迷宫中蹒跚,还有种莫名的悲伤~
有了有了迷宫⽣成⼯具,就很多好玩的了:
如何让乌龟更快的找到出路
如何让乌龟随机出现在迷宫中
如何动态设置迷宫的出⼊⼝
……
对这些问题,我们⼀⼀做了实现,孩⼦在整个过程中,积极参与,时不时因为好的想法⽽⼿舞⾜蹈,不亦乐乎……
关于坐标系设置
前⾯留了⼏个坑,是关于 Turtle 坐标系的,这⾥统⼀做下说明。

第⼀个问题,坐标单位
默认情况下,Turtle 的坐标单位是⼀个像素,如果要放⼤显⽰的华,需要计算出来我们使⽤的单元相当于多少个像素,然后每次计算坐标时都得考虑到这个值,当现实区域发⽣变化时还得调整这个数值,⾮常⿇烦,⽽且容易出错。

所以 Turtle 提供了⼀个设置我们⾃⼰坐标单位的⽅法 setworldcoordinates,它接受四个参数,分别是坐标系中,左下⾓的点 x 坐标,y坐标,和右上⾓的 x坐标、y坐标。

如果将左下⾓设置为 (-5, -5),右上⾓设置为 (5, 5),那么 Turtle 就会将坐标原点设置在屏幕中⼼,并将屏幕分割成 10 X 10 的⽅块,每个块的边长,相当于⼀个坐标单位,也就是说,当我们说将笔尖移动到 (3, 4) 这个坐标点时,Turtle 就会从屏幕中⼼向右移动三个单位,再向上移动4个单位。

这样就⾮常⽅便了,⽆论屏幕⼤⼩如何,像素⼤⼩如何,Turtle 都会按照我们的指令,做出正确的响应。

另⼀个问题是两个偏移量 xTranslate 和 yTranslate
分别是这样计算得到的:
self.xTranslate = -columnsInMaze/2
self.yTranslate = rowsInMaze/2
因为我们查找数据时⽤⾏列表⽰法⽐较⽅便,但在坐标系中,以原点为基准表⽰⽐较⽅便。

以上就是详解如何利⽤Python绘制迷宫⼩游戏的详细内容,更多关于Python迷宫游戏的资料请关注其它相关⽂章!。

相关文档
最新文档