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

    最近突然很喜欢玩《扫雷》这个小游戏,特别是上班摸鱼的时候,嘿嘿。但是非常蛋疼,windows10取消了这款游戏内置,若想玩还必须要到windows store上面下载。此外,网络上的扫雷大都带了很多广告,非常影响体验。因此我决定自己写一个出来,想想应该也挺简单的,事实的确如此。刚好最近想学一下Python的GUI,因此就用PyQt5好了。一边学一边写,大概花了一整天终于把它写出来了,这里来分享一下实现。

    先来看效果图:

    主菜单:

index.png

    8x8界面:

a.png

    30x20界面:

c.png

    胜利者的界面:

b.png



主菜单页面

    主界面如上图,很简陋。直接上代码:

index.py

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QCoreApplication
from content import Content


class Index(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.basePath=os.path.join(os.getcwd(),'Saolei')
        
    def initUI(self):
        self.btnQuit = QPushButton('退出游戏', self)
        self.btnQuit.resize(self.btnQuit.sizeHint())
        self.btnQuit.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnStart8x8 = QPushButton('开始游戏-8x8', self)
        self.btnStart8x8.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnStart8x8.resize(self.btnStart8x8.sizeHint())
        self.btnStart16x16 = QPushButton('开始游戏-16x16', self)
        self.btnStart16x16.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnStart16x16.resize(self.btnStart16x16.sizeHint())
        self.btnStart16x30 = QPushButton('开始游戏-16x30', self)
        self.btnStart16x30.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnStart16x30.resize(self.btnStart16x30.sizeHint())
        self.btnStart30x20 = QPushButton('开始游戏-30x20', self)
        self.btnStart30x20.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnStart30x20.resize(self.btnStart30x20.sizeHint())
        self.btnAbout = QPushButton('关于作者', self)
        self.btnAbout.setStyleSheet('font-size:20px;font-family:"黑体";color:rgb(150,150,255)')
        self.btnAbout.resize(self.btnAbout.sizeHint())
        vbox = QVBoxLayout()#纵向布局
        vbox.addStretch(1)#添加伸展因子
        vbox.addWidget(self.btnStart8x8)
        vbox.addWidget(self.btnStart16x16)
        vbox.addWidget(self.btnStart16x30)
        vbox.addWidget(self.btnStart30x20)
        vbox.addWidget(self.btnAbout)
        vbox.addWidget(self.btnQuit)
        vbox.addStretch(1)
        hbox = QHBoxLayout()#横向布局
        hbox.addStretch(1)
        hbox.addLayout(vbox)
        hbox.addStretch(1)
        widget=QWidget()
        widget.setLayout(hbox)#把布局放到widget
        self.setCentralWidget(widget) #把widget放到window里面
        iconPath=os.path.join(os.getcwd(),r'Saolei/res/icon/window.png')#设置图标
        self.setWindowIcon(QIcon(iconPath))
        self.setWindowTitle('扫雷-主菜单')
        self.setGeometry(300, 300, 300, 200)
        self.show()
        
    def aboutMe(self):
        reply = QMessageBox.question(self, '关于作者', 'Coded by 陈建驱 \n Github:Github:https://github.com/chenjianqu \n'+\
        '个人博客:www.chenjianqu.com', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            pass
        else:
            self.close()
            
if __name__ == '__main__':
    app = QApplication(sys.argv)
    index = Index()
    content=Content()
    index.btnQuit.clicked.connect(QCoreApplication.instance().quit)
    index.btnStart8x8.clicked.connect(content.setSize8x8)
    index.btnStart16x16.clicked.connect(content.setSize16x16)
    index.btnStart16x30.clicked.connect(content.setSize16x30)
    index.btnStart30x20.clicked.connect(content.setSize30x20)
    index.btnAbout.clicked.connect(index.aboutMe)
    sys.exit(app.exec_())

    代码逻辑也简单,就是定义六个按钮,这些按钮可以通过setStyleSheet()这个函数设置样式。整体布局:最外面是横向布局,里面一层是纵向布局,布局通过添加伸展因子使得按钮居中。

    由于该类继承自QMainWindow,不能直接添加布局,需要先在窗口中添加一个widget,然后把布局放置到widget当中。

    在主函数中,创建了Index界面和Content页面对象,然后将Index类中的按钮的点击信号量连接到Content类的函数,用于启动不同窗口大小的游戏。


kernel.py

    把扫雷中方块的数字存放至一个数字数组中,kernel.py封装对这个数组的操作。首先看类Brick的构造函数:

import numpy as np
class Brick:
    def __init__(self,size,n):
        oneArr=np.ones((1,n))
        zeroArr=np.zeros((1,size[0]*size[1]-n))
        self.brickArray=np.append(oneArr,zeroArr) #值为1表示有炸弹
        np.random.shuffle(self.brickArray)
        self.brickArray=self.brickArray.astype(int).reshape(size)#得到只有炸弹的矩阵
        #设置数字数组——非边缘
        arr=np.zeros(self.brickArray.shape,dtype=int)
        for i in range(0,self.brickArray.shape[0]):
            for j in range(0,self.brickArray.shape[1]):
                if(self.brickArray[i,j]==0):
                    #四个角落的值
                    if(i==0 and j==0):
                        arr[i,j]=self.brickArray[i,j+1]+self.brickArray[i+1,j]+self.brickArray[i+1,j+1]
                    elif(i==self.brickArray.shape[0]-1 and j==0):
                        arr[i,j]=self.brickArray[i-1,j]+self.brickArray[i,j+1]+self.brickArray[i-1,j+1]
                    elif(i==0 and j==self.brickArray.shape[1]-1):
                        arr[i,j]=self.brickArray[i,j-1]+self.brickArray[i+1,j]+self.brickArray[i+1,j-1]
                    elif(i==self.brickArray.shape[0]-1 and j==self.brickArray.shape[1]-1):
                        arr[i,j]=self.brickArray[i,j-1]+self.brickArray[i-1,j]+self.brickArray[i-1,j-1]
                    #四个边缘的值
                    elif(i==0):
                        arr[i,j]=self.brickArray[i,j-1]+self.brickArray[i,j+1]+self.brickArray[i+1,j-1]+self.brickArray[i+1,j]+self.brickArray[i+1,j+1]
                    elif(i==self.brickArray.shape[0]-1):
                        arr[i,j]=self.brickArray[i-1,j-1]+self.brickArray[i-1,j]+self.brickArray[i-1,j+1]+self.brickArray[i,j-1]+self.brickArray[i,j+1]
                    elif(j==0):
                        arr[i,j]=self.brickArray[i-1,j]+self.brickArray[i-1,j+1]+self.brickArray[i,j+1]+self.brickArray[i+1,j]+self.brickArray[i+1,j+1]
                    elif(j==self.brickArray.shape[1]-1):
                        arr[i,j]=self.brickArray[i-1,j-1]+self.brickArray[i-1,j]+self.brickArray[i,j-1]+self.brickArray[i+1,j-1]+self.brickArray[i+1,j]
                    else:
                        arr[i,j]=self.brickArray[i-1,j-1]+self.brickArray[i-1,j]+self.brickArray[i-1,j+1]+self.brickArray[i,j-1]+\
                            self.brickArray[i,j+1]+self.brickArray[i+1,j-1]+self.brickArray[i+1,j]+self.brickArray[i+1,j+1]
        self.brickArray=self.brickArray*10+arr
        self.OpenFlagArr=np.zeros((self.brickArray.shape[0],self.brickArray.shape[1]),dtype=int)

    可以看到,Brick类定义了一个numpy二维数组brickArray,用于存放扫雷的数字,数组里数字10代表该位置是个炸弹,其它数字表示该位置周围8个方块中总共存在的炸弹的数量。炸弹数量通过一个双重循环计算得到,由于需要考虑到数组边缘和四周,故这个循环略显复杂。构造函数传入pos参数的是数字数组的大小,参数n是该数组中存在的炸弹数量。

    OpenFlagArr存放游戏过程中某个方块的标志,如当OpenFlagArr[i,j]的值为0时,表示该方块未被掀开,当值为1时,表示该方块已掀开,当值为2时,表示该方块被玩家标识为炸弹。

    下面再来看Brick类中的其他函数。

def isVictory(self):
    flag=True
    rows=self.brickArray.shape[0]
    cols=self.brickArray.shape[1]
    for i in range(rows):
        for j in range(cols):
            if(self.brickArray[i,j]==10 and self.OpenFlagArr[i,j]!=2):
                flag=False
            elif(self.OpenFlagArr[i,j]==2 and self.brickArray[i,j]!=10):
                flag=False
    return flag
    
def getSurplusNumber(self):
    n=0
    rows=self.brickArray.shape[0]
    cols=self.brickArray.shape[1]
    for i in range(rows):
        for j in range(cols):
            if(self.brickArray[i,j]==10 and self.OpenFlagArr[i,j]==0):
                n=n+1
    return n

    上面代码中的isVictory()函数判断是否已经找到所有的炸弹,getSurplusNumer()获取剩余的炸弹数量。

    下面两个函数用于判断某个方块的周围是否还存在炸弹。

def isNoBomb(self,pos):
    i=pos[0]
    j=pos[1]
    brickPosList=[]
    #四个角落的值
    if(i==0 and j==0):
        brickPosList.append((i,j+1))
        brickPosList.append((i+1,j))
        brickPosList.append((i+1,j+1))
    elif(i==self.brickArray.shape[0]-1 and j==0):
        brickPosList.append((i-1,j))
        brickPosList.append((i,j+1))
        brickPosList.append((i-1,j+1))
    elif(i==0 and j==self.brickArray.shape[1]-1):
        brickPosList.append((i,j-1))
        brickPosList.append((i+1,j-1))
        brickPosList.append((i+1,j))
    elif(i==self.brickArray.shape[0]-1 and j==self.brickArray.shape[1]-1):
        brickPosList.append((i,j-1))
        brickPosList.append((i-1,j))
        brickPosList.append((i-1,j-1))
    #四个边缘的值
    elif(i==0):
        brickPosList.append((i,j-1))
        brickPosList.append((i,j+1))
        brickPosList.append((i+1,j-1))
        brickPosList.append((i+1,j))
        brickPosList.append((i+1,j+1))
    elif(i==self.brickArray.shape[0]-1):
        brickPosList.append((i-1,j-1))
        brickPosList.append((i-1,j))
        brickPosList.append((i-1,j+1))
        brickPosList.append((i,j-1))
        brickPosList.append((i,j+1))
    elif(j==0):
        brickPosList.append((i-1,j))
        brickPosList.append((i-1,j+1))
        brickPosList.append((i,j+1))
        brickPosList.append((i+1,j))
        brickPosList.append((i+1,j+1))
    elif(j==self.brickArray.shape[1]-1):
        brickPosList.append((i-1,j-1))
        brickPosList.append((i-1,j))
        brickPosList.append((i,j-1))
        brickPosList.append((i+1,j-1))
        brickPosList.append((i+1,j))
    else:
        brickPosList.append((i-1,j-1))
        brickPosList.append((i-1,j))
        brickPosList.append((i-1,j+1))
        brickPosList.append((i,j-1))
        brickPosList.append((i,j+1))
        brickPosList.append((i+1,j-1))
        brickPosList.append((i+1,j))
        brickPosList.append((i+1,j+1))
    return self.checkSame(brickPosList)
    
def checkSame(self,brickPosList):
flag=True
    for p in brickPosList:
        if(self.brickArray[p[0],p[1]]==10 and self.OpenFlagArr[p[0],p[1]]!=2):
            flag=False
    return flag


篇幅所限,下一篇(http://www.chenjianqu.com/show-35.html)继续。。


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

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