软件实习项目3——基于A*算法的迷宫(代码实现)

类变量的定义以及类的初始化__init__

class MazeMap(QWidget):
    # 类变量
    # 记录已访问的迷宫通路单元的坐标
    checked_x = 3
    checked_y = 3

    # 迷宫地图的行列
    row = 2 * checked_y + 1
    column = 2 * checked_x + 1

    # 玩家起始位置
    player_x = 0
    player_y = 1

    checked = []  # 已访问列表
    maze_map = []  # 地图列表
    route = []
    position = {1: (0, -1), 2: (0, 1), 3: (-1, 0), 4: (1, 0)}  # 1:上 2:下 3:左 4:右
    key_direction = 0  # 键盘方向
    victory = 0  # 玩家是否成功到达
    cat = 'head.png'  # 玩家头像

    # 设置几个字体
    font1 = QtGui.QFont("Calibri", 20)
    font2 = QtGui.QFont("Calibri", 15)
    font3 = QtGui.QFont("Calibri", 13)
    font4 = QtGui.QFont("幼圆", 20)
    font5 = QtGui.QFont("幼圆", 13)

    # 所有按钮都按照这个样式
    buttons_style = "QPushButton{background-color: rgb(255, 255, 255); border-radius:10px}" \
                    "QPushButton:hover{Background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 #cdced1, stop:1 #f6f7fa);}" \
                    "QPushButton:pressed{Background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 #f6f7fa, stop:1 #cdced1);}"

    # 类初始化
    def __init__(self):
        QWidget.__init__(self)
        self.setWindowTitle('Maze')  # 窗口标题
        self.setWindowIcon(QIcon('head.png'))  # 设置窗口图标
        ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")  # 任务栏图标
        self.setStyleSheet('background-color:#000000')
        self.resize(800, 800)  # 游戏窗口大小
        self.GameStart()
        self.map_init()
        self.dfsMazeCreation((0, 0))
        self.map_done = 1

一、迷宫地图的生成

1、初始化地图

	# 初始化地图,将地图设置为1与0间隔的形式
    def map_init(self):
        self.checked_x = self.checked_x + 1
        self.checked_y = self.checked_y + 1
        self.row = 2 * self.checked_y + 1
        self.column = 2 * self.checked_x + 1
        self.player_x = 0
        self.player_y = 1
        self.key_direction = 0
        self.victory = 0
        self.map_done = 0
        self.route = []
        self.checked = [[0 for _ in range(self.checked_x)] for _ in range(self.checked_y)]  # 初始化已访问列表
        self.maze_map = [[0 for _ in range(2 * self.checked_x + 1)] for _ in range(2 * self.checked_y + 1)]  # 初始化地图

        # 生成横向1和0间隔的列表
        for i in range(2 * self.checked_x + 1):
            if i % 2 == 0:
                for j in range(2 * self.checked_x + 1):
                    self.maze_map[i][j] = 1

        # 生成纵向1和0间隔的列表
        for i in range(2 * self.checked_y + 1):
            if i % 2 == 0:
                for j in range(2 * self.checked_y + 1):
                    self.maze_map[j][i] = 1

        # 打通起点和终点的墙
        self.maze_map[1][0] = 0  # 打通起点的墙
        self.maze_map[self.row - 2][self.column - 1] = 0  # 打通终点的墙

2、深度优先算法生成迷宫

	# 使用深度优先算法生成迷宫地图
    def dfsMazeCreation(self, current):
        c_x, c_y = current  # 设置当前通路单元坐标
        current_x = 2 * c_x + 1
        current_y = 2 * c_y + 1
        self.checked[c_y][c_x] = 1  # 将当前坐标记为已访问
        direction_list = [1, 2, 3, 4]
        # 遍历四个方向的邻居通路单元
        for i in range(4):
            random.shuffle(direction_list)  # 打乱方向列表
            direction = direction_list.pop()
            # 例:start=(0,0),position[direction]=(0,1),zip=((0,0),(0,1)),next_x=0+0,next_y=0+1
            next_x, next_y = (x + y for x, y in zip(current, self.position[direction]))  # 定义下一个要访问的邻居通路单元
            # 若出界了,则跳过
            if next_x < 0 or next_x >= self.checked_x or next_y < 0 or next_y >= self.checked_y or self.checked[next_y][next_x] == 1:
                continue
            # 打通当前通路单元和其邻居通路单元中间的墙,即将迷宫地图中的1变为0
            if direction == 1:  # 上
                wall_x, wall_y = current_x, current_y - 1
            elif direction == 2:  # 下
                wall_x, wall_y = current_x, current_y + 1
            elif direction == 3:  # 左
                wall_x, wall_y = current_x - 1, current_y
            else:  # 右
                wall_x, wall_y = current_x + 1, current_y
            self.maze_map[wall_y][wall_x] = 0
            self.dfsMazeCreation((next_x, next_y))

二、玩家走迷宫

1、键盘事件

	# 按下键盘前进
    def keyPressEvent(self, event):
        QWidget.keyPressEvent(self, event)
        key = event.key()
        # 1、按了↑
        if key == Qt.Key_Up or key == Qt.Key_W:
            self.key_direction = 1
        # 2、按了↓
        elif key == Qt.Key_Down or key == Qt.Key_S:
            self.key_direction = 2
        # 3、按了←
        elif key == Qt.Key_Left or key == Qt.Key_A:
            self.key_direction = 3
        # 4、按了→
        elif key == Qt.Key_Right or key == Qt.Key_D:
            self.key_direction = 4
        self.remove_footprint()

        # 5、按空格
        if key == Qt.Key_Space:
            self.checked_x = self.checked_x - 1
            self.checked_y = self.checked_y - 1
            self.map_init()
            self.dfsMazeCreation((0, 0))
            self.map_done = 1
            self.route = []

        # 6、按R寻路
        if key == Qt.Key_R:
            self.key_direction = 0
            self.show_route()

        self.player()

2、玩家移动

	# 玩家移动
    def player(self):
        if self.key_direction == 1 and self.maze_map[self.player_y - 1][self.player_x] == 0:
            self.player_y -= 1  # 纵坐标-1
        elif self.key_direction == 2 and self.maze_map[self.player_y + 1][self.player_x] == 0:
            self.player_y += 1  # 纵坐标+1
        elif self.key_direction == 3 and self.maze_map[self.player_y][self.player_x - 1] == 0 and self.player_x - 1 >= 0:
            self.player_x -= 1  # 横坐标-1
        elif self.key_direction == 4 and self.maze_map[self.player_y][self.player_x + 1] == 0 and self.player_x + 1 <= self.column - 1:
            self.player_x += 1  # 横坐标+1
        if self.player_x == self.column - 1 and self.player_y == self.row - 2:
            self.victory = 1
            self.update()
            self.GameOver()
        self.update()

三、A*算法迷宫寻路

1、A*寻路

# A*算法寻路
def AStar(maze_map, row, column, player_x, player_y):
    open_list = []  # 待访问列表
    close_list = []  # 已访问列表
    open_list.append(NODE(player_x, player_y, row, column))  # 把起点作为节点类的对象,加入open_list
    end = NODE(column - 2, row - 2, row, column)  # 创建终点的节点类对象
    # 遍历open_list,直到当前节点为终点
    while len(open_list):
        current_node = open_list[0]
        for node in open_list:
            if node.f < current_node.f:
                current_node = node  # 将F值最小的节点作为当前节点
        open_list.remove(current_node)  # 从open_list中移除当前节点
        close_list.append(current_node)  # 在close_list中放入当前节点

        # 若当前节点为终点,返回终点
        if current_node.x == end.x and current_node.y == end.y:
            path = []
            while current_node is not None:
                path.append((current_node.x, current_node.y))
                current_node = current_node.parent
            path.reverse()
            return path

        # 找到所有邻居节点,并放入邻居节点列表
        neighbors = [NODE(current_node.x, current_node.y - 1, row, column),
                     NODE(current_node.x, current_node.y + 1, row, column),
                     NODE(current_node.x - 1, current_node.y, row, column),
                     NODE(current_node.x + 1, current_node.y, row, column)]

        # 遍历邻居节点
        for node in neighbors:
            # 若非墙结点未超界、不在close_list中
            if 0 <= node.x < column and 0 <= node.y < row and maze_map[node.y][node.x] != 1 \
                    and not whether_in_list(node, close_list):
                if not whether_in_list(node, open_list):
                    node.note_node(current_node, end)  # 标记current_node作为该邻居节点的父节点,通过end来计算并标记F值
                    open_list.append(node)  # 将满足条件的邻居节点放入open_list
                else:
                    for i in open_list:
                        if node.x == i.x and node.y == i.y:
                            if node.g < i.g:
                                i.note_node(node, end)
                            break


def whether_in_list(node, list):
    for i in list:
        if node.x == i.x and node.y == i.y:
            return True
    return False


# 节点类
class NODE:
    def __init__(self, x, y, row, column):
        self.x = x
        self.y = y
        self.g = 0
        self.h = abs(column - 2 - self.x) + abs(row - 2 - self.y)
        self.f = self.g + self.h
        self.parent = None

    # 标记节点的父节点以及F值
    def note_node(self, parent, end):
        self.parent = parent
        if parent is not None:
            self.g = parent.g + 1
        self.h = abs(end.x - self.x) + abs(end.y - self.y)
        self.f = self.g + self.h

2、显示路径、移除脚印

    def show_route(self):
        self.route = AStar.AStar(self.maze_map, self.row, self.column, self.player_x, self.player_y)
        for (x, y) in self.route:
            if self.player_x == x and self.player_y == y:
                index = self.route.index((x, y))
                self.route = self.route[index + 1:]
                break

    def remove_footprint(self):
        for (x, y) in self.route:
            if self.player_x == x and self.player_y == y:
                index = self.route.index((x, y))
                self.route = self.route[index + 1:]
                break

四、绘制游戏界面

	# 绘制迷宫游戏
    def paintEvent(self, event):
        QWidget.paintEvent(self, event)
        painter = QPainter(self)
        row_space = self.height() / self.row  # 行间距
        column_space = self.width() / self.column  # 列间距
        # 背景图片
        painter.drawImage(QRectF(0, 0, self.width(), self.height()), QImage('bg.jpg'))
        # 绘制老鼠
        if self.victory == 0:
            painter.drawImage(
                QRectF((self.column - 1) * column_space, (self.column - 2) * row_space, column_space, row_space),
                QImage('mouse.png'))

        # 绘制迷宫地图
        for row in range(self.row):
            for column in range(self.column):
                element = self.maze_map[row][column]
                # 该位置的元素为1,表明它是墙
                if element == 1:
                    painter.drawImage(QRectF(column * column_space, row * row_space, column_space, row_space),
                                      QImage('light.png'))

        # 绘制迷宫路径
        for (x, y) in self.route:
            painter.drawImage(QRectF(x * column_space, y * row_space, column_space, row_space),
                              QImage('path.png'))

        # 绘制玩家
        painter.drawImage(QRectF(self.player_x * column_space, self.player_y * row_space, column_space, row_space),
                          QImage(self.cat))

五、游戏开始与结束界面

	# 开始游戏
    def GameStart(self):
        # 消息框的大小和样式
        message_box = QMessageBox()
        message_box.setStyleSheet('width: 450; height: 70')
        message_box.setWindowTitle('WELCOME')
        message_box.setFont(self.font4)
        message_box.setText("喵咪探险迷宫🐱")
        message_box.setWindowIcon(QIcon('head.png'))  # 设置消息框图标
        message_box.setStyleSheet('background-color:#deebf7')

        # click to start按钮
        message_box.setStandardButtons(QMessageBox.Yes)
        yes = message_box.button(QMessageBox.Yes)
        yes.setText("Click to Start")
        yes.setFont(self.font2)
        yes.setStyleSheet(self.buttons_style)

        # 下拉框猫咪选项
        items = ['喵喵:', '银渐层', '英短蓝白', '橘猫', '奶牛猫', '三花猫']
        cb = QComboBox(message_box)
        cb.addItems(items)  # 添加下拉框选项
        cb.setGeometry(340, 27, 125, 45)
        cb.setFont(self.font5)
        cb.setStyleSheet('background-color: rgb(255, 255, 255); border-radius:10px')
        cb.currentIndexChanged.connect(self.catChoosing)  # 下拉框选项改变即触发
        self.catChoosing(cb.currentIndex())  # 将下拉框此时的选项索引传递给speedChanging函数

        # 退出游戏开始消息框,进入游戏主界面
        message_box.exec()
        self.show()

    # GameStart方法中猫咪下拉框选项改变时调用的槽函数,用来选择自己喜欢的猫咪
    def catChoosing(self, index):
        if index == 1:
            self.cat = 'head.png'
        elif index == 2:
            self.cat = 'head1.png'
        elif index == 3:
            self.cat = 'head2.png'
        elif index == 4:
            self.cat = 'head3.png'
        elif index == 5:
            self.cat = 'head4.png'

    # 结束游戏
    def GameOver(self):
        message_box = QMessageBox()
        message_box.setStyleSheet('width: 200; height: 70')
        message_box.setWindowTitle('END')
        message_box.setFont(self.font1)
        message_box.setWindowIcon(QIcon('head.png'))  # 设置消息框图标
        message_box.setWindowOpacity(0.75)  # 透明度
        message_box.setText("You Win!")
        # 设置游戏结束消息框的两个按钮
        message_box.setStandardButtons(QMessageBox.Retry | QMessageBox.Abort)
        retry = message_box.button(QMessageBox.Retry)
        retry.setText("Next Level →")
        retry.setFont(self.font2)
        retry.setStyleSheet(self.buttons_style)
        abort = message_box.button(QMessageBox.Abort)
        abort.setText("Exit")
        abort.setFont(self.font2)
        abort.setStyleSheet(self.buttons_style)
        abort.clicked.connect(QCoreApplication.instance().quit)  # 点击此按钮则退出程序
        message_box.exec()
        self.map_init()
        self.dfsMazeCreation((0, 0))
        self.map_done = 1

——2020/12/27(殷越)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值