完整数字华容道04:游戏主体逻辑

  • Post author:
  • Post category:其他


本节涉及到的内容比较多,而且是在

简版华容道

基础上修改的,设计思路和算法请看简版华容道,本节只讲改进部分, 请一定要认真仔细阅读。



1、整体框架修改

在做页面切换的时候遇到了问题:MainForm 为 QWidget ,设置一个 Layout 简单,给它切换 Layout 后第一个 Layout并没有消失,导致两个 Layout 重叠。

修改方案:HomePage 和 GamePage 继承自 QWidget,MainForm 中定义

QStackedLayout

属性 sltMain,sltMain 添加 HomePage 和 GamePage ,可以方便切换和删除。



2、页面框架

游戏页面框架如下图所示:

01_页面框架



3、Switch 自定义控件

背景音乐开关是一个 Switch Button,但是QT没有该控件,所以需要自己实现,该部分参考了网上的资料:https://www.cnblogs.com/zhangxuan/p/9152513.html。

大致思路就是:如果点击该控件,启动定时器,滑块向另一方向移动直到边缘,然后设置颜色和显示的文字。实现的效果如图所示:

02_switchButton



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 初始化布局

该页面采用的布局如下图所示:

03_布局结构

代码实现:

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);

至此,游戏页面功能大致完成,来看一下效果图:
04_各个难度展示

待完成功能:

  • 保存成绩
  • 查看排行



版权声明:本文为yaoyefengchen原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。