软件实习项目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(殷越)