海龟 (turtle) 画图终极实战:小海龟挑战大迷宫游戏

迷宫本来是指错综复杂的通道,很难找到从其内部到达入口或从入口到达中心的道路,十分复杂难辨,人进去后不容易出来的建筑物。由于迷宫充满挑战性和娱乐性,后来人们发明了迷宫游戏。这种游戏可以在纸上玩,也可以在计算机中玩,受到了多数人的青睐。

本文将使用 Python 的海龟绘图实现一个带多个关卡的走迷宫游戏。在游戏中,玩家可以通过键盘上的↑、↓、←、→等方向键控制小海龟从迷宫入口走到迷宫出口,从而实现走出迷宫的游戏效果。前置知识:

  1. 全网最细海龟 (turtle) 画图讲解 (一):初探海龟绘图
  2. 全网最细海龟 (turtle) 画图讲解 (二):窗口控制
  3. 全网最细海龟 (turtle) 画图讲解 (三):设置画笔样式
  4. 全网最细海龟 (turtle) 画图讲解 (四):绘制图形
  5. 全网最细海龟 (turtle) 画图讲解 (五):输入/输出文字及鼠标与键盘交互设计

1. 需求分析

为了增加游戏的趣味性和挑战性,小海龟挑战大迷宫游戏应该具备以下功能:

  1. 界面美观、易于操作。
  2. 由易到难,提供多个关卡。
  3. 玩家可以自行选择开始关卡。
  4. 标记行走路线。
  5. 可以查看答案,并且显示动画效果。

2. 系统设计

2.1 游戏功能结构

小海龟挑战大迷宫游戏主要分为两个界面,分别为主窗口和游戏闯关界面,其中游戏闯关界面共提供了三关,在每一关都可以手动走迷宫和显示答案(即自动走迷宫),具体的功能结构如下图所示。
在这里插入图片描述

2.2 游戏业务流程

在开发小海龟挑战大迷宫游戏前,需要先梳理出游戏业务流程。根据小海龟挑战大迷宫游戏的需求分析及功能结构,设计出如下图所示的游戏业务流程图。
在这里插入图片描述

2.3 系统预览

小海龟挑战大迷宫游戏是一款通过 Python 的海龟绘图实现的桌面游戏。运行程序,首先进入的是游戏主界面,在该界面中,玩家可以选择开始的关卡,效果如下图所示。

在主界面中输入代表关卡的数字,将进入到相应的关卡。例如,输入1,将进入到第一关,此时按下 F1 键,将显示答案(即走出迷宫的路线)。按下 F2 键,即可通过 ↑、↓、←、→ 方向键控制小海龟走迷宫。当成功走到迷宫的出口时,将给出提示,准备进入下一关。演示效果如下:
在这里插入图片描述

3. 系统开发必备

3.1 系统开发环境

本系统的软件开发及运行环境具体如下:

  1. 操作系统:Windows10 64位
  2. Python 版本:Python 3.7.5
  3. 开发工具:Pycharm专业版
  4. Python 内置模块:os、re

3.2 文件夹组织结构

小海龟挑战大迷宫游戏的文件夹组织结构如下图所示:
在这里插入图片描述

4. 主窗口设计

小海龟挑战大迷宫游戏的主窗口主要用于显示游戏窗口以及选择游戏的开始关卡。运行效果如图所示:

实现游戏主窗体的具体步骤如下:

  1. 创建一个 Python 文件 turtlemaze.py,并且在该文件的同级目录下创建两个目录,分别为 image(用于保存图片文件)和 map(用于保存地图文件)。
  2. 由于本游戏采用海龟绘图来实现,所以需要在 turtlemaze.py 文件的顶部导入海龟绘图的模块,具体代码如下:
    import turtle # 导入海龟绘图模块
    
  3. 定义保存游戏名字的变量 game_title,并且赋值为游戏的名字 小海龟挑战大迷宫,代码如下:
    game_title = '小海龟挑战大迷宫'  # 游戏名字
    
  4. 创建程序入口,在程序入口中,首先设置窗口标题为步骤 (3) 中定义的游戏名字,并且设置主窗口的尺寸,然后设置主窗口背景为提前设计好的图片(保存在 image 目录中),再使用海龟绘图的 numinput() 方法弹出一个数值对话框,用于输入开始的关卡,最后调用海龟绘图的 done() 方法启动事件循环,让打开的海龟绘图窗口不关闭,代码如下:
    if __name__ == '__main__':  # 程序入口
        turtle.title(game_title)  # 设置窗口标题
        # turtle 为主窗口海龟
        turtle.setup(width=700, height=520)  # 设置主窗口的尺寸
        turtle.bgpic('image/start.png')  # 主窗口背景
        turtle.colormode(255)  # 设置颜色模式
        level = turtle.numinput('选择关卡:', '输入1~3的数字!', default=1, minval=1, maxval=3)
        levelinit()  # 开始闯关
        turtle.listen()  # 添加键盘监听
        turtle.onkey(autopath, 'F1')  # 显示答案
        turtle.onkey(manualpath, 'F2')  # 手动走迷宫
        turtle.done()  # 启动事件循环
    

5. 游戏地图的设计

在走迷宫游戏中,游戏地图的设计十分重要,它关系到游戏的难度和趣味性。在小海龟挑战大迷宫游戏中,游戏地图的设计思路为:将代表入口(S)、出口(E)、墙(1)和通路(0)的标识保存在一个文本文件中,然后再通过 Python 读取该文本文件并转换为二维列表,最后通过嵌套的 for 循环根据二维列表绘制由多个正方形组成的迷宫地图,如图所示。

5.1 设计保存地图信息的TXT文件

在小海龟挑战大迷宫游戏中,地图信息被保存在扩展名为 .txt 的文本文件中。其中,数字 0 代表该位置为通路,数字 1 代表该位置为墙,不可以通过。另外,还需要在通路的起点和终点使用字母 S 和 E 进行标识。如下图所示,为一个设计好的保存地图信息的文本文件的内容。
在这里插入图片描述
在设计地图信息时,要保证在起点 S 和终点 E 之间有一条通路,如上图所示中的绿色线路为迷宫的通路。

5.2 读取文件并转换为二维列表

设计好保存地图信息的文本文件后,还需要读取该文件并将其转换为二维列表,方便进行地图的绘制。具体步骤如下:

(1) 在文件的顶部定义一个全局变量 txt_path,用于记录地图信息文本文件所在的位置,默认赋值为 map1.txt 文件,代码如下:

txt_path = 'map/map1.txt'  # 地图信息文本文件路径及名称

(2) 编写一个 get_map() 函数,该函数有一个参数,用于指定要读取地图文件的文件名(包括路径)。在该函数中,首先使用内置函数 open() 以只读模式打开要读取的文件,并且读取全部行,然后通过 for 循环读取每一行的内容,在该 for 循环中,使用空格将每一行中的内容分割为列表,并且添加到保存地图的列表中,从而实现二维列表,最后返回保存地图信息的二维列表。get_map() 函数的代码如下:

def get_map(filename):
    """
    功能:读取保存地图的文本文件内容到列表
    :param filename: 地图文件名
    :return: 地图列表
    """
    with open(filename, 'r') as f:  # 打开文件
        fl = f.readlines()  # 读取全部行
    maze_list = []  # 保存地图的列表
    for line in fl:  # 将读取的内容以空格分割为二维列表
        line = line.strip()  # 去掉空格
        line_list = line.split(" ")  # 以空格进行分割为列表
        maze_list.append(line_list)  # 将分割后的列表添加到地图列表中
    return maze_list  # 返回地图列表

5.3 绘制迷宫地图

从文本文件中读取到地图信息并保存到二维列表中以后,就可以使用该二维列表绘制迷宫地图了,具体步骤如下:
(1) 在实现绘制迷宫地图时,需要定义保存迷宫通道颜色、地图的总行数和总列表、一个格子的尺寸等全局变量,具体代码如下:

road_color = (191, 217, 225)  # 迷宫通道的颜色
R, C = 0, 0  # 迷宫地图的总行数R、总列数C
cell_size = 20  # 一个格子的尺寸
area_sign = {
    
    }  # 记录入口和出口索引位置
mazeList = []  # 地图列表

(2) 在绘制迷宫地图时,需要两个海龟对象,分别用于绘制地图和出入口标记,代码如下:

map_t = turtle.Turtle()  # 绘制地图的海龟
map_t.speed(0)  # 设置绘图速度最快(地图绘制)
sign_t = turtle.Turtle()  # 绘制入口和出口标记的海龟

(3) 在绘制地图时,由于需要绘制多个正方形组成迷宫地图,所以需要编写一个公共的绘制正方形的方法 draw_square(),该方法包括 3 个参数:第一个参数 ci,用于指定列索引 (对应二维列表中的列);第二个参数 ri,用于指定行索引 (对应二维列表中的行),第三个参数 colorsign,用于指定颜色标识。在该方法中,首先根据索引值 ci 和 ri 计算出正方形起点的 x 和 y 坐标,并且将海龟光标移动到该位置,然后根据指定的颜色标识设置正方形的填充颜色,并绘制填充的正方形,最后隐藏海龟光标。draw_square() 方法的代码如下:

def draw_square(ci, ri, colorsign):
    """
    功能:绘制组成地图的小正方形
    :param ci: 列索引
    :param ri: 行索引
    :param colorsign: 填充颜色
    :return: 
    """
    tx = ci * cell_size - C * cell_size / 2  # 根据索引值计算每个正方形的起点(x坐标)
    ty = R * cell_size / 2 - ri * cell_size  # 根据索引值计算每个正方形的起点(y坐标)
    map_t.penup()  # 抬笔
    map_t.goto(tx, ty)  # 移动到绘图起点(正方形的左上角)
    if colorsign == '1':  # 判断是否为墙(如果为墙,则随机生成填充颜色)
        r = random.randint(100, 130)  # 红色值
        g = random.randint(150, 180)  # 绿色值
        map_t.color(r, g, 200)  # 指定颜色为随机生成的颜色
    else:
        map_t.color(colorsign)  # 设置为指定的通道颜色
    map_t.pendown()  # 落笔
    map_t.begin_fill()  # 填充开始
    for i in range(4):  # 绘制正方形
        map_t.fd(cell_size)
        map_t.right(90)
    map_t.end_fill()  # 填充结束
    map_t.ht()  # 隐藏海龟光标

由于在指定墙的颜色时,为了提升界面效果,采用了随机生成墙的颜色,所以需要导入随机数模块,代码如下:

import random

(4) 由于需要在地图上标记迷宫的起点和终点,即入口和出口,所以还需要定义一个绘制入口和出口标记的方法 draw_sign(),该方法包括 3 个参数,分别用于指定列索引、行索引和标记文字的内容。在该方法中,首先将索引位置转换为坐标位置,并隐藏海龟光标,然后设置抬笔并移动海龟光标到标记位置,再将画笔设置为红色,最后绘制标记文字。draw_sign() 方法的代码如下:

def draw_sign(ci, ri, word):
    """
    功能:绘制入口和出口标记
    :param ci: 列索引
    :param ri: 行索引
    :param word: 标记文字内容
    :return: 
    """
    cx, cy = itoc((ci, ri))  # 将索引位置转换为坐标位置
    sign_t.ht()  # 隐藏海龟光标
    sign_t.penup()  # 抬笔
    sign_t.goto(cx, cy)  # 移动到标记位置
    sign_t.color('red')  # 设置画笔为红色
    sign_t.write(word, font=('黑体', 12, 'normal'))  # 绘制标记文字

在上面的代码中,实现将索引位置转换为坐标位置,调用了 itoc() 方法,该方法有一个参数 ituple,该参数为由行、列索引组成的元组。在 itoc() 方法中,将根据行、列索引值计算每个正方形中心点的 x 和 y 坐标,并且将计算结果以元组类型返回。itoc() 方法的代码如下:

def itoc(ituple):
    """
    将索引位置转换为实际坐标位置
    :param ituple: 行、列索引组成的元组
    :return: 实际坐标位置
    """
    ci = ituple[0]
    ri = ituple[1]
    tx = ci * cell_size - C * cell_size / 2  # 根据索引值计算每个正方形的起点(x坐标)
    ty = R * cell_size / 2 - ri * cell_size  # 根据索引值计算每个正方形的起点(y坐标)
    cx = tx + cell_size / 2  # 正方形中心的x坐标
    cy = ty - cell_size / 2  # 正方形中心的y坐标
    return (cx, cy)

(5) 创建绘制迷宫地图的 draw_map() 方法,该方法的参数为保存地图信息的二维列表。在该方法中,将通过两个嵌套的 for 循环遍历二维列表,并且根据标记内容绘制不同颜色的正方形从而完成迷宫地图,如果标记为 S 或 E 时,还需要绘制入口和出口标记,并且记录相应的索引值。代码如下:

def draw_map(mazelist):
    """
    功能:遍历地图列表绘制迷宫地图
    :param mazelist: 保存地图数据的列表
    :return: 
    """
    turtle.tracer(0)  # 隐藏动画效果
    global area_sign  # 全局变量,记录入口和出口索引位置
    for ri in range(R):  # 遍历行
        for ci in range(C):  # 遍历列
            item = mazelist[ri][ci]
            if item in ['1']:  # 判断墙
                draw_square(ci, ri, '1')  # 绘制墙
            elif item == "S":  # 判断入口
                draw_square(ci, ri, road_color)  # 绘制通道
                draw_sign(ci - 1, ri, '入口')  # 标记入口
                area_sign['entry_i'] = (ci, ri)  # 保存入口索引
            elif item == "E":  # 判断出口
                draw_square(ci, ri, road_color)  # 绘制通道
                draw_sign(ci - 1, ri, '出口')  # 标记出口
                area_sign['exit_i'] = (ci, ri)  # 保存出口索引
            else:
                draw_square(ci, ri, road_color)  # 绘制通道
    turtle.tracer(1)  # 显示动画效果

在上面的代码中,调用 turtle.tracer() 方法用于显示或隐藏动画效果。传递的值为 0 时,表示隐藏动画效果;传递的值为 1 时,表示显示动画效果。

6. 走迷宫设计

在小海龟挑战大迷宫游戏中,走迷宫时主要分为手动走迷宫和显示答案 (即自动走迷宫),下面分别进行介绍。

6.1 手动走迷宫

在游戏的主窗体中,输入开始的关卡,将进入到相应关卡中,此时按下键盘上的 <F2> 键,将进入手动走迷宫模式。在该模式下,玩家通过按下键盘上的 ↑、↓、←、→ 方向键控制小海龟沿着通路移动,如下图所示。小海龟直接从入口走到出口,则完成本关任务,并且提示进入下一关。

实现手动走迷宫的步骤如下:
(1) 实现手动走迷宫需要创建一个海龟对象,并且设置它的画笔粗细、绘图速度、海龟光标形状等,代码如下:

manual_t = turtle.Turtle()  # 手动走迷宫的海龟
manual_t.pensize(5)  # 画笔粗细(手动)
manual_t.speed(0)  # 设置绘图速度最快(手动)
manual_t.shape('turtle')  # 设置海龟光标为小海龟(手动)
manual_t.ht()  # 隐藏手动走迷宫所用的海龟光标(手动)

(2) 定义一个记录向 4 个方向探索对应的索引变化规则,代码如下:

# 要探索4个方向对应索引的变化规则
direction = [
    (1, 0),  # 右
    (-1, 0),  # 左
    (0, 1),  # 上
    (0, -1)  # 下
]

(3) 编写手动走迷宫时通用探索并移动的 manual_move() 函数。在该函数中,首先根据索引的变化规则列表中的数据获取到目标点的位置并且调用 ctoi() 函数转换为行、列索引,然后在地图列表中获取到目标点的标记,并且根据标记进行不同的操作,如果是通路则向前移动并且绘制红色线,如果是已探索的路,则绘制与通道相同颜色的线;如果是出口,则调用 win_tip() 函数显示过关提示,代码如下:

def manual_move(d):
    """
    功能:手动走迷宫时通用探索并移动函数
    :param d: 向不同方面走时索引的变化规则
    :return: 
    """
    dc, dr = d  # 将表示方向的元组分别赋值给两个变量dc和dr,其中dc为x轴方向,dr为y轴方向
    rici = ctoi(round(manual_t.xcor(), 1) + dc * cell_size, round(manual_t.ycor(), 1) + dr * cell_size)  # 获取行列索引
    point = mazeList[rici[0]][rici[1]]  # 获取地图列表中对应点的值
    print('移动:', rici, point)
    if point == '0':  # 通路
        manual_t.color('red')
        mazeList[rici[0]][rici[1]] = '$'  # 将当前位置标记为已探索
        manual_t.forward(cell_size)  # 向前移动
        print('00')
    elif point == '$':  # 已探索
        manual_t.color(road_color)  # 绘制和通道相同颜色的线,达到擦除痕迹的效果
        mazeList[rici[0] + dr][rici[1] - dc] = '0'  # 将当前位置的前一个点设置为未探索(目的是取消标记)
        manual_t.forward(road_color)  # 向前移动
        manual_t.color('red')
    elif point == 'E':  # 出口
        wintip()

在上面的代码中,调用了 ctoi() 方法来实现将坐标位置转换为对应的索引位置。该方法包括两个参数,分别用于指定列坐标和行坐标,返回值为计算后的行列索引的元组。代码如下:

def ctoi(cx, cy):
    """
    根据cx和cy求在列表中对应的索引
    :param cx: x轴坐标
    :param cy: y轴坐标
    :return: 元组,(ci,ri)
    """
    ci = ((C - 1) * cell_size / 2 + cx) / cell_size  # 计算列索引
    ri = ((R - 1) * cell_size / 2 - cy) / cell_size  # 计算行索引
    return (int(ri), int(ci))  # 返回行列索引的元组

(4) 编写 manual_path() 函数,用于控制手动走迷宫。在该函数中,首先清除绘图,并且隐藏海龟,然后重新读取地图数据,并且调用 c_move_to() 函数根据坐标位置移动到入口位置(不画线),再设置画笔颜色及粗细,最后让海龟屏幕 (TurtleScreen)获得焦点,并且设置 ↑、↓、←、→ 方向键被按下时调用的函数。代码如下:

def manual_path():
    """
    功能:手动走迷宫
    :return: 
    """
    manual_t.clear()  # 清除绘图
    auto_t.ht()  # 隐藏海龟
    auto_t.clear()  # 清除绘图
    global mazeList  # 定义全局变量
    mazeList = get_map(txt_path)  # 重新读取地图数据
    # print(area_sign['entry_i'][0],area_sign['entry_i'][1])
    c_move_to(manual_t, itoc(area_sign['entry_i']))  # 移动到入口位置
    manual_t.st()  # 显示手动走迷宫所用的海龟光标
    manual_t.width(3)  # 设置画笔粗细为3像素
    manual_t.color('red')  # 设置画笔为红色
    manual_t.getscreen().listen()  # 让海龟屏幕(TurtleScreen)获得焦点
    manual_t.getscreen().onkeyrelease(up_move, 'Up')  # 按下向上方向键
    manual_t.getscreen().onkeyrelease(down_move, 'Down')  # 按下向下方向键
    manual_t.getscreen().onkeyrelease(left_move, 'Left')  # 按下向左方向键
    manual_t.getscreen().onkeyrelease(right_move, 'Right')  # 按下向右方向键

在上面的代码中,调用了 c_move_to 函数根据坐标位置移动到入口位置(不画线),该函数包括两个参数:第一个参数 t,用于指定要操作的海龟对象;第二个参数则为记录坐标位置的元组。在该函数中,首先隐藏海龟光标,并且设置抬笔,然后移动海龟到指定位置,再设置落笔。代码如下:

def c_move_to(t, ctuple):  # 移动到指定位置
    """
    功能:根据坐标位置移动到指定位置(不画线)
    :param t: 海龟对象
    :param ctuple: 记录坐标位置的元组
    :return: 
    """
    t.ht()  # 隐藏海龟光标
    t.penup()  # 抬笔
    t.goto(ctuple[0], ctuple[1])  # 移动到坐标指定的位置
    t.pendown()  # 落笔

(6) 编写 ↑、↓、←、→ 方向键对应的方法。在每个方法中设置海龟朝向,并且调用 manual_move() 函数探索并移动海龟。代码如下:

def up_move():  # 朝上
    manual_t.setheading(90)  # 设置海龟朝向
    manual_move(direction[2])  # 手动探索并移动


def down_move():  # 朝下
    manual_t.setheading(270)  # 设置海龟朝向
    manual_move(direction[3])  # 手动探索并移动


def left_move():  # 朝左
    manual_t.setheading(180)  # 设置海龟朝向
    manual_move(direction[1])  # 手动探索并移动


def right_move():  # 朝右
    manual_t.setheading(0)  # 设置海龟朝向
    manual_move(direction[0])  # 手动探索并移动

(7) 在程序入口中,添加键盘监听,并且设置当按下 <F2> 键时,调用 manual_path() 函数开启手动走迷宫。代码如下:

turtle.listen()  # 添加键盘监听
turtle.onkey(manual_path, 'F2')  # 手动走迷宫

6.2 显示答案(自动走迷宫)

在游戏的主窗体中,输入开始的关卡,将进入到相应关卡中,此时按下键盘上的 <F1> 键,将显示正确的行走路线,即进入自动走迷宫模式。在该模式下,玩家不需要操作,小海龟就会自动从出口走向入口,同时留下行走痕迹,如图所示。

说明:在玩走迷宫游戏时,如果通路比较复杂,可以采用从出口开始向入口逆向行走。但是本文中实现的小海龟挑战大迷宫游戏,在手动走迷宫时不支持逆向行走。

实现自动走迷宫步骤如下:

(1) 实现自动走迷宫需要创建一个海龟对象,并且设置它的画笔粗细、绘图速度、海龟光标形状等,代码如下:

auto_t = turtle.Turtle()  # 自动走迷宫的海龟
auto_t.pensize(5)  # 画笔粗细(自动)
auto_t.speed(0)  # 设置绘图速度最快(手动)
auto_t.ht()  # 隐藏海龟光标

(2) 编写根据索引位置移动海龟(画线)的 draw_path() 函数。该函数包括 3 个参数,分别用于指定列、行索引和画笔颜色。在该函数中,首先设置海龟光标显示,然后应用 itoc() 函数将索引位置转换为坐标位置,再设置画笔颜色,最后移动海龟到指定位置,代码如下:

def draw_path(ci, ri, color="green"):  # 自动绘制用
    """
    功能:根据索引位置移动海龟(画线)
    :param ci: 列索引
    :param ri: 行索引
    :param color: 画笔颜色
    :return: 
    """
    auto_t.st()  # 显示海龟光标
    cx, cy = itoc((ci, ri))  # 将索引位置转换为坐标位置
    auto_t.color(color)
    auto_t.goto(cx, cy)

(3) 编写 auto_path() 函数,用于控制自动走迷宫。在该函数中,首先重新读取地图数据,并且隐藏海龟,然后清除绘图,并设置画笔粗细和绘图速度,再隐藏海龟光标,最后调用 find() 函数开始探索。代码如下:

def auto_path():
    """
    功能:查看答案(自动走迷宫)
    :return: 
    """
    global mazeList  # 定义全局变量
    mazeList = get_map(txt_path)  # 重新读取地图数据
    manual_t.ht()  # 隐藏海龟
    manual_t.clear()  # 清除绘图
    auto_t.clear()  # 清除绘图
    auto_t.pensize(5)  # 设置画笔粗细
    auto_t.speed(0)  # 绘图速度
    auto_t.ht()  # 隐藏海龟光标
    find(mazeList)  # 开始探索

(4) 编写 find() 函数,该函数有一个参数,用于指定地图列表。在该函数中,首先清除帮助绘图,然后通过嵌套的 for 循环遍历保存地图的二维列表,并且找到入口位置,再调用 draw _path() 函数绘制海龟移动痕迹,并且调用 find_next() 函数进行递归探索当前路线是否为通路。代码如下:

def find(mazeList):
    """
    功能:开始探索
    :param mazeList: 地图列表
    :return: 
    """
    auto_t.clear()  # 清空帮助
    start_r, start_c = 0, 0
    for ri in range(R):
        for ci in range(C):
            item = mazeList[ri][ci]
            if item == "S":
                start_r, start_c = ri, ci
    auto_t.penup()  # 抬笔
    draw_path(start_c, start_r)
    find_next(mazeList, start_c, start_r)

(5) 编写递归搜索判断是否为通路的函数 find_next()。该函数包括 3 个参数,分别为地图列表、列索引和行索引,返回值为布尔值,为 True 表示是通路;为 False 表示为墙。在该函数中,从地图列表中获取要探索位置对应的标记,然后根据获取的标记进行相应的判断,如果当前点为通路,还需要递归调用 find_next() 函数继续判断直到找到一条通路,此时才不再探索,并且返回 True。代码如下:

def find_next(mlist, ci, ri):
    """
    功能:递归搜索判断是否为通路
    :param mlist: 地图列表
    :param ci: 列索引
    :param ri: 行索引
    :return: 布尔值,表示是否为通路
    """
    if mlist[ri][ci] == "E":
        imoveto(ci, ri)  # 移动到出口
        return True
    if not (0 <= ci < C and 0 <= ri < R):  # 判断位置是否不合法
        return False
    if mlist[ri][ci] in ['1', '$']:  # 判断是否为墙或者已探索过的
        return False
    mlist[ri][ci] = "$"  # 标记已探索过
    for d in direction:  # 尝试从不同方向探索是否为通路,如果发现一条通路,则不再继续探索
        dc, dr = d  # # 将索引变化规则的值分别赋值给dc和dr,其中dc为x轴方向,dr为y轴方向
        found = find_next(mlist, ci + dc, ri + dr)  # 递归调用
        if found:  # 如果是通路则绘制线路
            draw_path(ci, ri)  # 绘制线路
            return True  # 返回True,不再探索
    return False  # 当所有方向都不通时,返回False

在上面的代码中,调用了 imoveto() 函数根据索引位置移动海龟(不画线),该函数包括两个参数,分别用于指定列索引和行索引。在该函数中,首先设置抬笔,并且调用 itoc() 函数将传递的索引位置转换为坐标位置,然后将海龟移动到指定位置,再设置落笔,最后设置海龟的光标形状、画笔颜色,并且显示海龟光标。代码如下:

def imoveto(ci, ri):
    """
    功能:根据索引位置移动海龟(不画线)
    :param ci: 列索引
    :param ri: 行索引
    :return: 
    """
    auto_t.penup()  # 抬笔
    cx, cy = itoc((ci, ri))  # 将索引位置转换为坐标位置
    auto_t.goto(cx, cy)  # 移动到指定位置
    auto_t.pendown()  # 落笔
    auto_t.shape('turtle')  # 设置海龟光标的形状
    auto_t.color('red')  # 设置画笔颜色为红色
    auto_t.st()  # 显示海龟光标

7. 关卡设置

在小海龟挑战大迷宫游戏中,共提供了3个关卡,每个关卡的难易程序逐渐增加,第3关为关底,通过第3关后将退出游戏。下面将介绍如何在小海龟挑战大迷宫游戏中进行关卡设置。

7.1 初始化关卡信息

编写初始化关卡信息的函数 level_init(),在该函数中,首先清除绘图,并定义一些全局变量,然后根据关卡数设置采用的地图文件和背景图片,并且调用 get_map() 函数获取地图列表,再根据获取的地图数据设置窗口尺寸,并且设置背景,最后隐藏海龟光标,并且调用 draw_map() 函数绘制地图,代码如下:

def level_init():
    """
    功能:关卡初始化
        游戏规则:
        按下F2键开始手动走迷宫;按下F1键查看答案
        按下↑↓←→方向键控制小海龟移动,闯关成功后,按Enter进入下一关
    :return: 
    """
    manual_t.clear()  # 清除绘图
    auto_t.clear()  # 清除绘图
    turtle.clear()  # 清除绘图
    global txt_path, level, mazeList, R, C  # 定义全局变量
    if level == 1:  # 第一关的地图文件和背景
        txt_path = "map/map1.txt"
        levelbg = 'image/level1.png'
    elif level == 2:  # 第二关的地图文件和背景
        txt_path = "map/map2.txt"
        levelbg = 'image/level2.png'
    elif level == 3:  # 第三关的地图文件和背景
        txt_path = "map/map3.txt"
        levelbg = 'image/level3.png'
    else:
        turtle.bye()  # 退出程序
        return
    mazeList = get_map(txt_path)  # 获取地图数据
    R, C = len(mazeList), len(mazeList[0])
    turtle.setup(width=C * cell_size + 50, height=R * cell_size + 100)  # 根据地图调整窗口尺寸
    turtle.bgpic(levelbg)  # 设置背景图片

    '''  
    # 如果想要手动绘制关卡数,可以使用下面的两行代码
    cmoveto(turtle, (1 * cellsize - C * cellsize / 2, R * cellsize / 2+10))
    turtle.write('关卡:'+str(int(level)), font=('宋体', 16, 'normal'))    
    '''
    turtle.ht()  # 隐藏海龟光标
    draw_map(mazeList)  # 绘制地图

(2) 在程序入口中,调用 level_init() 函数,开始闯关,代码如下:

level_init()  # 开始闯关

7.2 实现过关提示

过关提示功能,即玩家闯过一关后给出的提示。在小海龟挑战大迷宫游戏中,过关提示分为两种:一种是进入关底时给出提示并且退出游戏;

另一种是进入下一关时给出提示,如下图所示。

在 6.1 小节中编写的 manual_move() 函数中,如果为出口,则调用 win_tip() 函数显示过关提示。下面将编写该函数,用于在屏幕中显示过关提示。如果是第3关,则退出游戏;否则提示进入下一关。代码如下:

def win_tip():
    """
    功能:制作过关提示
    :return: 
    """
    global level
    c_move_to(manual_t, (-150, 0))
    manual_t.color('blue')
    if int(level) == 3:
        manual_t.write('\n恭喜您顺利通关!', font=('黑体', 20, 'bold'))
        turtle.onkey(turtle.bye, key='Return')  # 监听按下Enter键退出游戏
    else:
        manual_t.write('\n恭喜过关!\n按下Enter进入下一关!', font=('黑体', 20, 'bold'))
        level += 1
        manual_t.color('red')
        turtle.onkey(level_init, key='Return')  # 监听按下Enter键

7.3 源码及素材下载地址

点击 此处 下载源码及素材

8. 总结

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xw1680/article/details/112308829