实验项目三(2.0):基于A*搜索算法迷宫游戏开发

一、前言

迷宫游戏是非常经典的游戏。要求:

  1. 在该题中随机生成一个迷宫,并求解迷宫;
  2. 游戏支持玩家走迷宫,和系统走迷宫路径两种模式。玩家走迷宫,通过键盘方向键控制,并在行走路径上留下痕迹;系统走迷宫路径要求基于A*算法实现,输出走迷宫的最优路径并显示。
  3. 设计交互友好的游戏图形界面。

二、基本流程

在这里插入图片描述
思路梳理
在这里插入图片描述

三、迷宫随机生成

随机生成迷宫可以用到多种算法,包括深度优先遍历、随机prim算法、递归回溯算法等等。这里我选用了随机prim算法。
在这里插入图片描述
原始版本的随机Prim算法是维护一个墙的列表。

  1. 随机选择一个迷宫单元作为起点,加入检查列表并标记为已访问
  2. 当检查列表非空时,随机从列表中取出一个迷宫单元,进行循环
    ①如果当前迷宫单元有未被访问过的相邻迷宫单元
    a. 随机选择一个未访问的相邻迷宫单元
    b. 去掉当前迷宫单元与相邻迷宫单元之间的墙
    c. 标记相邻迷宫单元为已访问,并将它加入检查列表
    ②否则,当前迷宫单元没有未访问的相邻迷宫单元,则从检查列表删除当前迷宫单元

设置基本参数

    def setMap(self, x, y, value):
        if value == MAP_ENTRY_TYPE.MAP_EMPTY:
            self.map[y][x] = 0
        elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
            self.map[y][x] = 1
        elif value == MAP_ENTRY_TYPE.MAP_START:
            self.map[y][x] = 2
        elif value == MAP_ENTRY_TYPE.MAP_END:
            self.map[y][x] = 3

判断是否已经访问过(访问过则代表不设定为墙

    def isVisited(self, x, y):
        return self.map[y][x] != 1

如果当前位置四周的相邻结点有墙

    if len(directions):
        # 随机选择一个方向的墙
        direction = choice(directions)
        if direction == WALL_DIRECTION.WALL_LEFT:
            map.setMap(2 * (x - 1) + 1, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            map.setMap(2 * x, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            checklist.append((x - 1, y))
        elif direction == WALL_DIRECTION.WALL_UP:
            map.setMap(2 * x + 1, 2 * (y - 1) + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            map.setMap(2 * x + 1, 2 * y, MAP_ENTRY_TYPE.MAP_EMPTY)
            checklist.append((x, y - 1))
        elif direction == WALL_DIRECTION.WALL_RIGHT:
            map.setMap(2 * (x + 1) + 1, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            map.setMap(2 * x + 2, 2 * y + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            checklist.append((x + 1, y))
        elif direction == WALL_DIRECTION.WALL_DOWN:
            map.setMap(2 * x + 1, 2 * (y + 1) + 1, MAP_ENTRY_TYPE.MAP_EMPTY)
            map.setMap(2 * x + 1, 2 * y + 2, MAP_ENTRY_TYPE.MAP_EMPTY)
            checklist.append((x, y + 1))
        return True
    else:
        # 找不到四周有未经访问的墙(即周边的墙都已经访问过
        return False

随机prim算法

def randomPrim(map, width, height):
    # 起始点的设置
    startX, startY = (0, 0)
    map.setMap(2*startX+0, 2*startY+1, MAP_ENTRY_TYPE.MAP_START)
    # 终点的设置
    endX, endY = (width, height)
    map.setMap(2*endX-0, 2*endY-1, MAP_ENTRY_TYPE.MAP_END)

    checklist = []
    checklist.append((startX, startY))
    while len(checklist):
        # 从checklist中选择一个随机方向的墙
        entry = choice(checklist)
        if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
            # 如果四周的墙都已访问过,则把该结点位置从checklist中删除
            checklist.remove(entry)

考虑到后续键盘移动迷宫和自动寻路迷宫都需要地图,我将此算法生成的地图以“ . ”、“ # ”分别标记为通路和墙壁,以及S作为起始点而E作为终点,并导出生成一个txt文件。如下
在这里插入图片描述

四、移动迷宫与尾迹生成

首先复制txt文件中的内容,赋给map数组。
由于要用到键盘控制移动,使用了turtle库进行绘图,并使用三张图片绘制迷宫的基本元素,wall是迷宫的墙体,pr作为当前移动的位置,la为迷宫移动的尾迹。
在这里插入图片描述

1. 绘制迷宫

    def make_maze(self):
        level = levels[current_level - 1]
        for i in range(len(level)):
            # 取出某一行
            row = level[i]
            # 获取到某一元素的坐标
            for j in range(len(row)):
                screen_x = -244 + 12 * j
                screen_y = 185 - 12 * i

                char = row[j]
                #如果元素为X,则画出迷宫
                if char == '#':
                    self.goto(screen_x,screen_y)
                    self.stamp()
                    walls.append((screen_x,screen_y))
                elif char == 'S':
                    player.goto(screen_x,screen_y)
                    player.st()

在这里插入图片描述

2. 键盘响应

mz.listen()
mz.onkey(player.go_right, 'Right')
mz.onkey(player.go_left, 'Left')
mz.onkey(player.go_up, 'Up')
mz.onkey(player.go_down, 'Down')

3. 当前位置的移动以及尾迹生成

class Player(t.Turtle):
    #当前位置的移动
    def __init__(self):
        super().__init__()
        # 先隐藏起来,隐秘进行运动
        self.ht()
        self.shape('images/pr.gif')
        self.speed(0)
        self.penup()

    # 右移
    def go_right(self):
        # print('going right')
        self.shape('images/la.gif')
        self.stamp()
        go_x = self.xcor() + 12
        go_y = self.ycor()
        self.shape('images/pr.gif')
        self.move(go_x, go_y)

    # 左移
    def go_left(self):
        # print('going left')
        self.shape('images/la.gif')
        self.stamp()
        go_x = self.xcor() - 12
        go_y = self.ycor()
        self.shape('images/pr.gif')
        self.move(go_x,go_y)

    # 上移
    def go_up(self):
        # print('going up')
        self.shape('images/la.gif')
        self.stamp()
        go_x = self.xcor()
        go_y = self.ycor() + 12
        self.shape('images/pr.gif')
        self.move(go_x, go_y)

    # 下移
    def go_down(self):
        # print('going down')
        self.shape('images/la.gif')
        self.stamp()
        go_x = self.xcor()
        go_y = self.ycor() - 12
        self.shape('images/pr.gif')
        self.move(go_x, go_y)

    # 运动时的共同行为
    def move(self, go_x, go_y):
        if (go_x, go_y) not in walls:
            self.goto(go_x, go_y)
        else:
            print('撞墙了')

4. 运行效果

在这里插入图片描述

五、A*迷宫自动寻路

1. 关键名词解释

  1. open集:包含已搜索到的待检测结点
  2. close集:包含已检测的结点
  3. G:从开始结点到当前结点的移动量
  4. H:当前结点到目标结点的移动量估计值
    4邻域的曼哈顿距离
    8邻域的对角线距离
  5. F:G + H
  6. 父节点指针:每个结点都有,确定追踪关系

2. 主循环

  1. 从open集中取一个最优结点n(F最小)来检测
  2. 将结点n从open集中移除,加入close集
  3. n若是目标结点,则算法结束
  4. 否则尝试添加n结点的所有邻结点n‘
    ①邻结点在close集中,已检测过则无需添加
    ②邻结点在open集中
    a. 如果重新计算的G值比邻结点保存的G值小,则要更新这个邻结点的G值和F值,以及父节点
    b. 否则不做操作
    ③否则将邻结点加入open集,并设置其父结点为n,以及设置G和F的值

3. 初始化地图

        for i in range(self.width):
            for j in range(self.height):
                # 生成墙壁
                if test_map[j][i] == '#':
                    node = self.canvas.create_rectangle(i * 10 + 50 - self.__r,
                                                   j * 10 + 50 - self.__r, i * 10 + 50 + self.__r,
                                                   j * 10 + 50 + self.__r,
                                                   fill="#000000",  # 填充黑色
                                                   outline="#ffffff",  # 轮廓白色
                                                   tags="node",
                                                   )
              
                # 生成通路
                if test_map[j][i] == '*':
                    node = self.canvas.create_rectangle(i * 10 + 50 - self.__r,
                                                   j * 10 + 50 - self.__r, i * 10 + 50 + self.__r,
                                                   j * 10 + 50 + self.__r,
                                                   fill="#00ff00",  # 填充绿色
                                                   outline="#ffffff",  # 轮廓白色
                                                   tags="node",
                                                   )

4. 核心代码

# 查找路径的入口函数
    def find_path(self):
        # 构建开始节点
        p = Node_Elem(None, self.s_x, self.s_y, 0.0)
        while True:
            # 扩展节点
            self.extend_round(p)
            # 如果open表为空,则不存在路径,返回
            if not self.open:
                return
            # 取F值最小的节点
            idx, p = self.get_best()
            # 到达终点,生成路径,返回
            if self.is_target(p):
                self.make_path(p)
                return
            # 把此节点加入close表,并从open表里删除
            self.close.append(p)
            del self.open[idx]

 # 取F值最小的节点
    def get_best(self):
        best = None
        bv = 10000000  # MAX值
        bi = -1
        for idx, i in enumerate(self.open):
            value = self.get_dist(i)
            if value < bv:
                best = i
                bv = value
                bi = idx
        return bi, best

    # 求距离
    def get_dist(self, i):
        # F = G + H
        # G 为当前路径长度,H为估计终点长度
        return i.dist + math.sqrt((self.e_x - i.x) * (self.e_x - i.x)) + math.sqrt((self.e_y - i.y) * (self.e_y - i.y))

# 扩展节点
    def extend_round(self, p):
        # 上下左右四个方向移动
        xs = (0, -1, 1, 0)
        ys = (-1, 0, 0, 1)
        for x, y in zip(xs, ys):
            new_x, new_y = x + p.x, y + p.y
            # 检查位置是否合法
            if not self.is_valid_coord(new_x, new_y):
                continue
            # 构造新的节点,计算距离
            node = Node_Elem(p, new_x, new_y, p.dist + self.get_cost(
                p.x, p.y, new_x, new_y))
            # 新节点在关闭列表,则忽略
            if self.node_in_close(node):
                continue
            i = self.node_in_open(node)
            # 新节点在open表
            if i != -1:
                # 当前路径距离更短
                if self.open[i].dist > node.dist:
                    # 更新距离
                    self.open[i].parent = p
                    self.open[i].dist = node.dist
                continue
            # 否则加入open表
            self.open.append(node)

5. 运行结果

在这里插入图片描述
按下enter键后自动寻路成功
在这里插入图片描述

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于A*算法的迷宫小游戏开发,可以让玩家面对迷宫的挑战,通过智慧和策略找到迷宫的出口。 首先,我们需要设计一个迷宫地图。可以采用多种方式生成迷宫地图,如随机生成、手动设计或者使用迷宫生成算法。迷宫地图由起点、终点以及迷宫墙壁组成。 接下来,我们使用A*算法来寻找最佳路径。A*算法是一种启发式搜索算法,通过估计每个节点到目标点的距离来决定搜索方向。在实现A*算法时,需要定义一个启发函数来评估节点的价值,以便选择最优的路径。在该游戏中,可以使用曼哈顿距离或欧几里得距离作为启发函数。 当玩家开始游戏后,可以使用方向键或鼠标来控制角色移动。同时,在游戏界面上显示迷宫地图和玩家的当前位置。 在实现A*算法时,需要考虑一些特殊情况。比如,如何处理墙壁、如何处理无法到达的位置等。可以采用合适的数据结构,如优先队列或堆栈,来实现算法搜索和路径的存储。 最后,为了增加游戏的趣味性和挑战性,可以在迷宫中添加一些道具或陷阱,用来干扰玩家的寻路过程。比如,道具可以提供额外的移动能力,而陷阱则会减慢玩家的速度。 通过以上方法,基于A*算法的迷宫小游戏可以提供给玩家一个有趣而挑战的寻路体验。同时,这个游戏也可以帮助玩家锻炼逻辑思维和空间认知能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值