接上篇文章(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)