基于PyQt的扫雷游戏实现_下篇

    接上篇文章(http://www.chenjianqu.com/show-34.html)继续。

    上一篇文章已经给出了主菜单的实现,以及操作数字数组的Brick类。在介绍游戏界面的实现之前,先自定义Button控件。

mButton.py

from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt,QObject,pyqtSignal
import sys
from PyQt5.QtGui import QFont
import time

class MySignal(QObject):
    doubleClick=pyqtSignal()
    rightClick=pyqtSignal()
    leftClick=pyqtSignal()
    
class MyButton(QPushButton):
    def __init__(self,text,parent=None):
        super().__init__(text,parent)
        self.c=MySignal()
        self.lastTime=0
    def mousePressEvent(self,e):
        #当信号发射时,连接的槽函数将会自动执行。
        if(e.buttons()==Qt.RightButton):
            self.c.rightClick.emit()
        elif(e.buttons()==Qt.LeftButton):#左键事件处理
            current=time.time()
            delta=current-self.lastTime
            self.lastTime=current
            if(delta<0.2):#鼠标双击时
                self.c.doubleClick.emit()
            else:
                self.c.leftClick.emit()

    PyQt默认的点击事件只有单击左键,若想获得鼠标的双击和右击只能自己实现。类MySignal定义三个信号量,左键单击、右键单击、左键双击。MyButton继承QPushButton,当发生鼠标事件时,会触发mousePressEvent()函数。右键按下,则发送右键单击信号量,左键按下时,判断两次按下的时间间隔,可判断是单击还是双击,发送相应的信号量。


content.py

    先看构造函数:

class Content(QMainWindow):
    def __init__(self):
        super().__init__()
        self.size=(8,8)
        self.n=10
        self.kernal=None
        self.initMenu(self)
    @staticmethod
    def initMenu(self):
        #退出游戏
        self.basePath=os.path.join(os.getcwd(),'Saolei')
        pathIcon=os.path.join(self.basePath,r'res/icon/close.png')
        exitAction = QAction(QIcon(pathIcon), '&退出', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(qApp.quit)
        #回到主菜单
        pathIcon=os.path.join(self.basePath,r'res/icon/toindex.png')
        backAction = QAction(QIcon(pathIcon), '&回到首页', self)
        backAction.setShortcut('Ctrl+B')
        backAction.setStatusTip('Back to index')
        backAction.triggered.connect(self.close)
        self.statusBar()
        #创建一个菜单栏
        self.menubar = self.menuBar()
        fileMenu = self.menubar.addMenu('&File')#添加菜单
        fileMenu.addAction(exitAction)#添加事件
        fileMenu.addAction(backAction)
        #创建一个工具栏
        self.toolbar = self.addToolBar('Exit')
        self.toolbar.addAction(exitAction)
        self.toolbar.addAction(backAction)
        iconPath=os.path.join(os.getcwd(),r'Saolei/res/icon/window.png')#设置图标
        self.setWindowIcon(QIcon(iconPath))
        self.setWindowTitle('扫雷-游戏界面')
        self.setGeometry(300, 300, 300, 200)

    构造函数主要实现菜单栏和工具栏的初始化,这里只添加了两个动作:完全退出程序和关闭当前页面,这段代码比较简单,没有什么需要特别解释的。

    下面这个函数用于初始化并显示游戏界面,它们在主菜单界面中被调用用于开启游戏窗口。

def setSize8x8(self):
    self.size=(8,8)
    self.n=10
    self.initUI()
    self.show()
def setSize16x16(self):
    self.size=(16,16)
    self.n=30
    self.initUI()
    self.show()
def setSize16x30(self):
    self.n=40
    self.size=(16,30)
    self.initUI()
    self.show()
def setSize30x20(self):
    self.n=100
    self.size=(30,20)
    self.initUI()
    self.show()

    接下来就是游戏主界面的初始化显示了。

def initUI(self):
    self.grid=QGridLayout()
    self.kernal=Brick(self.size,self.n)
    if(self.size[1]==8):
        brickSize=(80,60)
        self.grid.setSpacing(6)#设置组件之间的间距。
    elif(self.size[1]==16):
        brickSize=(50,40)
        self.grid.setSpacing(4)
    elif(self.size[1]==30):
        brickSize=(40,30)
        self.grid.setSpacing(1)
    elif(self.size[0]==30):
        brickSize=(40,20)
        self.grid.setSpacing(1)
    for i in range(self.size[0]):
        for j in range(self.size[1]):
            button=MyButton("")
            button.setObjectName(str((i,j)))
            button.setFixedSize(brickSize[0],brickSize[1])
            button.setStyleSheet("font-size:20px;background-color:rgb(230.230.230)")
            button.c.leftClick.connect(partial(self.display,button))
            button.c.rightClick.connect(partial(self.markBrick,button))
            button.c.doubleClick.connect(partial(self.doubleHandle,button))
            self.grid.addWidget(button,i,j)
    self.lblTimer = QLabel('时间',self)
    self.lblTimer.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
    self.lblNumber = QLabel('剩余',self)
    self.lblNumber.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
    self.timer=QTimer()
    self.timer.timeout.connect(self.showTimer)
    self.timer.start()
    self.initTime=time.time()
    lblLayout=QHBoxLayout()
    lblLayout.addWidget(self.lblNumber)
    lblLayout.addWidget(self.lblTimer)
    self.mainLayout=QVBoxLayout()
    self.mainLayout.addLayout(lblLayout)
    self.mainLayout.addLayout(self.grid)
    widget=QWidget()
    widget.setLayout(self.mainLayout)
    self.setCentralWidget(widget)

    首先获得Brick对象,然后根据不同的方块数设置方块不同大小和间距。通过双重循环创建方块,并让左键、右键、左键双击信号量连接到不同的信号槽函数,使用偏函数传递参数给信号槽函数,同时为了区分各个方块,将Button对象名设置为他们的位置元组字符串。将所有的方块都放入网格布局中。接下来是添加两个Lable控件用于显示剩余的炸弹数和时间。后面就是将各个控件排列好,放置到页面上。

    从上面的代码可以看到,当方块单击执行的函数是display(),右键执行markBrick()函数,双击执行doubleHandle()函数。下面先看display函数。

def display(self,btn):
    pos=eval(btn.objectName())#解析位置元组
    self.expand(pos)

    pos是btn在网状布局中的位置,因为扫雷有这样的规则:单击一个值为0的方块时,它能自动将0方块周围所有的方块都掀开。为了达到这样的效果,这里定义expand()函数,递归的掀开方块。

def expand(self,pos):
    if(self.kernal.OpenFlagArr[pos[0],pos[1]]==1 or self.kernal.OpenFlagArr[pos[0],pos[1]]==2):#掀开这个方块的标记
        return
    n=self.kernal.getSurplusNumber()
    self.lblNumber.setText('剩余数量:'+str(n))
    if(self.kernal.isVictory()):
        self.victory()
    self.kernal.OpenFlagArr[pos[0],pos[1]]=1
    n=self.kernal.getBrickArr()[pos[0],pos[1]]#获得对应的值
    #获取gridlayout中对应的按钮
    lyitem=self.grid.itemAtPosition(pos[0],pos[1])
    btn=lyitem.widget()
    btn.setStyleSheet("background-color:rgb(200,200,255)")
    if(n==10):
        btn.setText('*')
        btn.setStyleSheet("background-color:rgb(150,255,150)")
        self.failure()
    elif(n==0):
        btn.setText('')
        i=pos[0]
        j=pos[1]
        #四个角落的值
        if(i==0 and j==0):
            self.expand((i,j+1))
            self.expand((i+1,j))
            self.expand((i+1,j+1))
        elif(i==self.size[0]-1 and j==0):
            self.expand((i-1,j))
            self.expand((i,j+1))
            self.expand((i-1,j+1))
        elif(i==0 and j==self.size[1]-1):
            self.expand((i,j-1))
            self.expand((i+1,j))
            self.expand((i+1,j-1))
        elif(i==self.size[0]-1 and j==self.size[1]-1):
            self.expand((i,j-1))
            self.expand((i-1,j))
            self.expand((i-1,j-1))
        #四个边缘的值
        elif(i==0):
            self.expand((i,j-1))
            self.expand((i,j+1))
            self.expand((i+1,j-1))
            self.expand((i+1,j))
            self.expand((i+1,j+1))
        elif(i==self.size[0]-1):
            self.expand((i-1,j-1))
            self.expand((i-1,j))
            self.expand((i-1,j+1))
            self.expand((i,j-1))
            self.expand((i,j+1))
        elif(j==0):
            self.expand((i-1,j))
            self.expand((i-1,j+1))
            self.expand((i,j+1))
            self.expand((i+1,j))
            self.expand((i+1,j+1))
        elif(j==self.size[1]-1):
            self.expand((i-1,j-1))
            self.expand((i-1,j))
            self.expand((i,j-1))
            self.expand((i+1,j-1))
            self.expand((i+1,j))
        else:
            self.expand((i-1,j-1))
            self.expand((i-1,j))
            self.expand((i-1,j+1))
            self.expand((i,j-1))
            self.expand((i,j+1))
            self.expand((i+1,j-1))
            self.expand((i+1,j))
            self.expand((i+1,j+1))
    else:
        btn.setText(str(n))

    上面这个函数定义了当鼠标点击时,方块会怎么进行变化。接下来看,右键时会把方块标记为炸弹,右键时间处理函数:

#右击,设定方块标记
def markBrick(self,btn):
    pos=eval(btn.objectName())
    if(self.kernal.OpenFlagArr[pos[0],pos[1]]==0):
        self.kernal.OpenFlagArr[pos[0],pos[1]]=2
        if(self.kernal.isVictory()):self.victory()
        btn.setStyleSheet("background-color:rgb(255,150,150)")
    else:
        self.kernal.OpenFlagArr[pos[0],pos[1]]=0
        btn.setStyleSheet("background-color:rgb(230,230,230)")
    n=self.kernal.getSurplusNumber()
    self.lblNumber.setText('剩余数量:'+str(n))

双击事件处理函数:

def doubleHandle(self,btn):
    pos=eval(btn.objectName())
    if(self.kernal.isNoBomb(pos)):
        i=pos[0]
        j=pos[1]
        #四个角落的值
        if(i==0 and j==0):
            self.dbButton((i,j+1))
            self.dbButton((i+1,j))
            self.dbButton((i+1,j+1))
        elif(i==self.size[0]-1 and j==0):
            self.dbButton((i-1,j))
            self.dbButton((i,j+1))
            self.dbButton((i-1,j+1))
        elif(i==0 and j==self.size[1]-1):
            self.dbButton((i,j-1))
            self.dbButton((i+1,j))
            self.dbButton((i+1,j-1))
        elif(i==self.size[0]-1 and j==self.size[1]-1):
            self.dbButton((i,j-1))
            self.dbButton((i-1,j))
            self.dbButton((i-1,j-1))
        #四个边缘的值
        elif(i==0):
            self.dbButton((i,j-1))
            self.dbButton((i,j+1))
            self.dbButton((i+1,j-1))
            self.dbButton((i+1,j))
            self.dbButton((i+1,j+1))
        elif(i==self.size[0]-1):
            self.dbButton((i-1,j-1))
            self.dbButton((i-1,j))
            self.dbButton((i-1,j+1))
            self.dbButton((i,j-1))
            self.dbButton((i,j+1))
        elif(j==0):
            self.dbButton((i-1,j))
            self.dbButton((i-1,j+1))
            self.dbButton((i,j+1))
            self.dbButton((i+1,j))
            self.dbButton((i+1,j+1))
        elif(j==self.size[1]-1):
            self.dbButton((i-1,j-1))
            self.dbButton((i-1,j))
            self.dbButton((i,j-1))
            self.dbButton((i+1,j-1))
            self.dbButton((i+1,j))
        else:
            self.dbButton((i-1,j-1))
            self.dbButton((i-1,j))
            self.dbButton((i-1,j+1))
            self.dbButton((i,j-1))
            self.dbButton((i,j+1))
            self.dbButton((i+1,j-1))
            self.dbButton((i+1,j))
            self.dbButton((i+1,j+1))

def dbButton(self,pos):
    lyitem=self.grid.itemAtPosition(pos[0],pos[1])
    btn=lyitem.widget()
    self.display(btn)


完整代码:https://github.com/chenjianqu/MineSweeper_PyQt5

首页 所有文章 机器人 计算机视觉 自然语言处理 机器学习 编程随笔 关于