文章目录
本节涉及到的内容比较多,而且是在
简版华容道
基础上修改的,设计思路和算法请看简版华容道,本节只讲改进部分, 请一定要认真仔细阅读。
1、整体框架修改
在做页面切换的时候遇到了问题:MainForm 为 QWidget ,设置一个 Layout 简单,给它切换 Layout 后第一个 Layout并没有消失,导致两个 Layout 重叠。
修改方案:HomePage 和 GamePage 继承自 QWidget,MainForm 中定义
QStackedLayout
属性 sltMain,sltMain 添加 HomePage 和 GamePage ,可以方便切换和删除。
2、页面框架
游戏页面框架如下图所示:
3、Switch 自定义控件
背景音乐开关是一个 Switch Button,但是QT没有该控件,所以需要自己实现,该部分参考了网上的资料:https://www.cnblogs.com/zhangxuan/p/9152513.html。
大致思路就是:如果点击该控件,启动定时器,滑块向另一方向移动直到边缘,然后设置颜色和显示的文字。实现的效果如图所示:
4、GamePage 实现
GamePage 是该游戏比较重量级的部分,逻辑也是最复杂的。
4.1 新增属性
GamePage 类中要添加几个属性:
- 游戏等级:设置游戏的等级,也就是行和列中数字方块的个数;
- 游戏时间:记录游戏所用时间;
- 游戏步数:记录游戏所走步数;
- 方块边长:间隔已经固定,根据游戏等级计算方块的边长;
self.level = level # 游戏等级
self.time = 0 # 游戏时间
self.steps = 0 # 游戏步数
self.spacing = 10 # 数字方块的间隔
self.blockWidth = (380 - self.spacing * (self.level + 1)) / self.level # 计算方块的边长
4.2 初始化布局
该页面采用的布局如下图所示:
代码实现:
def initUI(self):
# 启动定时器
self.timer.start(1000)
self.timer.timeout.connect(self.updateTime)
font = QFont('Microsoft YaHei')
font.setPointSize(20)
# 返回按钮
self.btnBack = QPushButton()
pic_dir = os.path.abspath('.') + '\\src\\images\\back.png'
self.btnBack.setFixedSize(40, 40)
self.btnBack.setIcon(QIcon(pic_dir))
self.btnBack.setIconSize(QSize(40, 40))
self.btnBack.clicked.connect(self.back)
# switch 按钮连接槽
self.swithcBtn.checkedChanged.connect(self.musicControl)
# 音乐
lbMusic = QLabel("音乐")
lbMusic.setFont(font)
self.swithcBtn.setFixedSize(80, 40)
self.hltTop = QHBoxLayout()
self.hltTop.addWidget(self.btnBack)
self.hltTop.addStretch()
self.hltTop.addWidget(lbMusic)
self.hltTop.addWidget(self.swithcBtn)
# 记录步数和时间
self.lbTime = QLabel('时间:{}s'.format(self.time))
self.lbStep = QLabel('步数:{}'.format(self.steps))
self.lbTime.setFont(font)
self.lbStep.setFont(font)
self.hltMedium = QHBoxLayout()
self.hltMedium.addWidget(self.lbTime)
self.hltMedium.addStretch()
self.hltMedium.addWidget(self.lbStep)
self.gltPannel.setSizeConstraint(QLayout.SetFixedSize)
# 设置方块间隔
self.gltPannel.setSpacing(self.spacing)
game_background_path = os.path.abspath('.') + '\\src\\images\\game_background.png'
self.onInit()
self.wdgGame.setFixedSize(400, 400)
self.wdgGame.setLayout(self.gltPannel)
self.wdgGame.setStyleSheet("""
background-color:rgb(242, 242, 242);
""")
# 重新开始按钮
self.btnRestart = QPushButton('重新开始')
self.btnRestart.setFixedSize(120, 40)
self.btnRestart.setStyleSheet("""
border-radius:5px;
padding:2px 4px;
color: white;
font: bold;
font-family: "Microsoft YaHei";
background-color:rgb(22, 155, 213);
font-size: 20px;""")
self.btnRestart.clicked.connect(self.restart)
self.vltMain.setSpacing(20)
self.vltMain.addLayout(self.hltTop)
self.vltMain.addLayout(self.hltMedium)
self.vltMain.addWidget(self.wdgGame, 0, Qt.AlignHCenter)
self.vltMain.addWidget(self.btnRestart, 0, Qt.AlignRight)
self.setLayout(self.vltMain)
self.setFixedSize(400, 600)
self.setStyleSheet("background-color:lightblue;")
# 初始化布局
def onInit(self):
# 清空二维数组
self.blocks = []
# 产生顺序数组
self.numbers = list(range(1, self.level ** 2))
self.numbers.append(0)
# 将数字添加到二维数组
for row in range(self.level):
self.blocks.append([])
for column in range(self.level):
temp = self.numbers[row * self.level + column]
if temp == 0:
self.zero_row = row
self.zero_column = column
self.blocks[row].append(temp)
# 打乱数组
for i in range(500):
random_num = random.randint(0, 3)
self.move(Direction(random_num))
# 初始化步数和时间,并更新显示
self.steps = 0
self.time = 0
self.lbTime.setText('时间:{}s'.format(self.time))
self.updatePanel()
self.updateSteps()
4.3 返回首页
由于返回首页是由 GamePage 的上一级(NumberHuaRong) 控制的,所以需要定义一个信号:
- backToHome:返回首页;
backToHome = pyqtSignal()
对应的,定义一个槽:
# 返回首页
def back(self):
self.backToHome.emit()
back() 槽和返回按钮相连接:
self.btnBack.clicked.connect(self.back)
也就是点击返回按钮的时候发送 backToHome 信号。
4.4 背景音乐控制
背景音乐的控制权同样交给 GamePage 的上一级(NumberHuaRong) ,因此定义两个信号:
- musicStart:播放背景音乐;
- musicStop:停止播放背景音乐。
对应的定义槽函数:
# 背景音乐控制
def musicControl(self, control):
if control:
self.musicStart.emit()
else:
self.musicStop.emit()
musicControl() 槽和 swithcBtn 连接:
self.swithcBtn.checkedChanged.connect(self.musicControl)
根据 switchBtn 的状态分别发送不同的控制信号。
4.5 更新步数
步数的更新在 move() 方法中发生,注意:由于使用方向键会导致组件焦点的变化,程序没有进行相应的移动,所以我们只检测
WSAD
来表示方向。检测到
WSAD
后步数增加并更新显示:
# 更新步数
def updateSteps(self):
self.lbStep.setText('步数:{}'.format(self.steps))
# 方块移动算法
def move(self, direction):
if(direction == Direction.UP): # 上
if self.zero_row != self.level - 1:
self.blocks[self.zero_row][self.zero_column] = self.blocks[self.zero_row + 1][self.zero_column]
self.blocks[self.zero_row + 1][self.zero_column] = 0
self.zero_row += 1
self.steps += 1
self.updateSteps()
if(...) # 此处省略其它三个方向的判断
4.6 更新时间
利用定时器,每过1s,self.time 增加1并更新显示:
self.timer = QTimer() # 定时器
...
# 启动定时器
self.timer.start(1000) # 设置超时时间为1s
self.timer.timeout.connect(self.updateTime) # 更新显示
...
# 更新时间
def updateTime(self):
self.time += 1
self.lbTime.setText('时间:{}s'.format(self.time))
4.7 重新开始
由于程序的初始化操作是在
self.onInit()
中进行的,所以重新开始只要执行以下该方法即可。定义槽函数并与btnRestart 的 clicked 信号连接即可:
self.btnRestart.clicked.connect(self.restart)
...
# 重新开始
def restart(self):
self.onInit()
5、HomePage 修改
HomePage 里面选择游戏难度后,NumberHuaRong 会根据难度实例化对应的 GamePage,所以游戏难度需要传递给 NumberHuaRong ,为了简单我们直接对 StyleButton 进行改造以达到“一劳永逸”的目的。定义信号:
btnText = pyqtSignal(int)
定义一个槽函数,便于发送信号:
def sendNumber(self):
self.btnText.emit(int(self.txt[0]))
然后在初始化时进行连接:
self.clicked.connect(self.sendNumber)
也就是点击 StyleButton 时会发送一个信号,该信号带有一个参数(游戏的难度),该参数取自按钮上的文本第一个字符并将其转成 int 类型。
6、NumberHuaRong 修改
6.1 根据游戏难度实例化 GamePage
定义槽函数 setLevel() 根据游戏的难度设置 GamePage 每行、每列数字方块的个数,并将其与 StyleButton 的 btnText 信号相连接:
# 游戏难度已选定
self.hp.btn3_3.btnText.connect(self.setLevel)
self.hp.btn4_4.btnText.connect(self.setLevel)
self.hp.btn5_5.btnText.connect(self.setLevel)
self.hp.btn6_6.btnText.connect(self.setLevel)
def setLevel(self, level):
# 如果 sltMain 的 item 个数为2,即已经包含了一个 GamePage,
# 需要将其删除,因为我们要添加一个新的 GamePage 进来
if self.sltMain.count() == 2:
self.sltMain.removeItem(self.sltMain.itemAt(1))
self.gp = GamePage(level)
self.sltMain.addWidget(self.gp)
self.sltMain.setCurrentIndex(1) # 设置 GamePage 为当前页面
# 背景音乐控制
self.gp.musicStart.connect(self.start)
self.gp.musicStop.connect(self.stop)
# 返回首页
self.gp.backToHome.connect(self.backToHomePage)
6.2 背景音乐控制
定义两个信号:
- musStart:播放背景音乐;
- musStop:暂停播放背景音乐。
musStart = pyqtSignal()
musStop = pyqtSignal()
对应的定义两个槽函数:
def start(self):
self.musStart.emit()
def stop(self):
self.musStop.emit()
然后这两个槽函数在 setLevel 中与 GamePage 的两个信号连接:
# 背景音乐控制
self.gp.musicStart.connect(self.start)
self.gp.musicStop.connect(self.stop)
6.3 返回首页
定义槽函数:
def backToHomePage(self):
self.sltMain.removeItem(self.sltMain.itemAt(1)) # 删除 GamePage
self.sltMain.setCurrentIndex(0)
然后与 GamePage 的
backToHome
信号连接即可:
# 返回首页
self.gp.backToHome.connect(self.backToHomePage)
6.4 媒体播放器
在 main 函数中创建媒体播放器:
# 媒体播放器
player = QMediaPlayer()
# 播放列表
play_list = QMediaPlaylist()
# 媒体资源
media_content = QMediaContent(QUrl.fromLocalFile(os.path.abspath('.') + '\\src\\sounds\\background_music.mp3'))
play_list.addMedia(media_content)
# 设置当前要播放的资源
play_list.setCurrentIndex(0)
# 循环播放
play_list.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop)
player.setPlaylist(play_list)
player.setVolume(50);
至此,游戏页面功能大致完成,来看一下效果图:
待完成功能:
- 保存成绩
- 查看排行