python快速实现简易扫雷小游戏

关于扫雷的模式设定:

以windows XP自带扫雷winmine.exe为例(其它版本的扫雷游戏与之大同小异)。游戏区包括雷区、地雷计数器(位于左上角,记录剩余地雷数)和计时器(位于右上角,记录游戏时间),确定大小的矩形雷区中随机布置一定数量的地雷(初级为9*9个方块10个雷,中级为16*16个方块40个雷,高级为16*30个方块99个雷,自定义级别可以自己设定雷区大小和雷数,但是雷区大小不能超过24*30),玩家需要尽快找出雷区中的所有不是地雷的方块,而不许踩到地雷。

游戏的基本操作包括左键单击(Left Click)、右键单击(Right Click)、双击(Chording)三种。其中左键用于打开安全的格子,推进游戏进度;右键用于标记地雷,以辅助判断,或为接下来的双击做准备;双击在一个数字周围的地雷标记完时,相当于对数字周围未打开的方块均进行一次左键单击操作:

左键单击:在判断出不是雷的方块上按下左键,可以打开该方块。如果方块上出现数字,则该数字表示其周围3×3区域中的地雷数(一般为8个格子,对于边块为5个格子,对于角块为3个格子。所以扫雷中最大的数字为8);如果方块上为空(相当于0),则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。

右键单击:在判断为地雷的方块上按下右键,可以标记地雷(显示为小红旗)。重复一次或两次操作可取消标记(如果在游戏菜单中勾选了“标记(?)”,则需要两次操作来取消标雷)。

双击:同时按下左键和右键完成双击。当双击位置周围已标记雷数等于该位置数字时操作有效,相当于对该数字周围未打开的方块均进行一次左键单击操作。地雷未标记完全时使用双击无效。若数字周围有标错的地雷,则游戏结束,标错的地雷上会显示一个“ ×”。

雷诀八条

第一条:基本定式不要忘,现场推理真够呛。

第二条:鼠标点击不要快,稳定节奏把空开。

第三条:顺手标雷不要惯,积累下来记录悬。

第四条:无从下手不要愣,就近猜雷把心横。

第五条:遇到猜雷不要怕,爆了脸上不留疤。

第六条:猜雷猜错不要悔,哭天抢地也白费。

第七条:碰上好局不要慌,紧盯局部慢扩张。

第八条:痛失好局不要恨,既然有缘定有份。

完整代码如下:

主要依赖库(tkinter,PyQt5)

#coding: utf-8
import sys
from tkinter import messagebox
from random import randint
from PyQt5.QtWidgets import \
    QApplication,           \
    QWidget,                \
    QPushButton,            \
    QLCDNumber,             \
    QDesktopWidget,         \
    QMessageBox
from PyQt5.QtCore import Qt
  
  
class Mine(object):
    mine = 9
    no_mine = 0
    n_mine = 10
    width = 10
    height = 10
  
    def __init__(self, width=10, height=10, nMines=10):
        self.map = []
        for _ in range(height):
            t_line = []
            for _ in range(width):
                t_line.append(self.no_mine)
            self.map.append(t_line)
          
        self.width = width
        self.height = height
        self.n_mine = nMines
  
        self.remix()
      
    # 打乱布局重新随机编排
    def remix(self):
  
        for y in range(self.height):
            for x in range(self.width):
                self.map[y][x] = self.no_mine
  
        def add_mark(x, y):
            # 如果不是雷的标记就+1
            if self.map[y][x]+1 < self.mine:
                self.map[y][x] += 1
          
        mine_count = 0
  
        while mine_count < self.n_mine:
            x = randint(0, self.width-1)
            y = randint(0, self.height-1)
  
            if self.map[y][x] != self.mine:
                self.map[y][x] = self.mine
                  
                mine_count += 1
  
                # 雷所在的位置的8个方位的数值+1
                ## 上下左右
                if y-1 >= 0: add_mark(x, y-1)
                if y+1 < self.height: add_mark(x, y+1)
                if x-1 >= 0: add_mark(x-1, y)
                if x+1 < self.width: add_mark(x+1, y)
                ## 四个角: 左上角、左下角、右上角、右下角
                if x-1 >= 0 and y-1 >=1: add_mark(x-1, y-1)
                if x-1 >= 0 and y+1 < self.height: add_mark(x-1, y+1)
                if x+1 < self.width and y-1 >= 1: add_mark(x+1, y-1)
                if x+1 < self.width and y+1 < self.height: add_mark(x+1, y+1)
      
    def __getitem__(self, key):
        return self.map[key]
  
    def __str__(self):
        format_str = ""
        for y in range(self.height):
            format_str += str(self[y]) + "\n"
        return format_str
    __repr__ = __str__
  
class LCDCounter(QLCDNumber):
    __counter = 0
    def __init__(self, start=0, parent=None):
        super().__init__(4, parent)
        self.setSegmentStyle(QLCDNumber.Flat)
        self.setStyleSheet("background: black; color: red")
        self.counter = start
      
    @property
    def counter(self):
        return self.__counter
    @counter.setter
    def counter(self, value):
        self.__counter = value
        self.display(str(self.__counter))
      
    def inc(self):
        self.counter += 1
    def dec(self):
        self.counter -= 1
  
class MineButton(QPushButton):
    # 按钮类型
    MINE = Mine.mine        # 雷
    NOTMINE = Mine.no_mine  # 不是雷
    m_type = None
  
    # 按钮状态
    mark = False    # 是否是标记状态(默认: 未被标记)
   
    s_flag = '😶'   # 标记
    s_mine = '🥣'  # 雷
    s_success = ''
    # 按钮是否按下(默认False: 未按下)
    __pushed = False
  
    # 按钮对应map的位置
    m_x = 0
    m_y = 0
  
    def __init__(self, map_pos, m_type, parent):
        super().__init__(parent)
        self.m_type = m_type
        self.pushed = False
        self.m_x = map_pos[0]
        self.m_y = map_pos[1]
      
    @property
    def pushed(self):
        return not self.__pushed
    @pushed.setter
    def pushed(self, value):
        self.__pushed = not value
        self.setEnabled(self.__pushed)
  
    ## 按钮上的鼠标按下事件
    def mousePressEvent(self, e):
        #print("m_x:%d"%self.m_x, "m_y:%d"%self.m_y, "m_type:%d"%self.m_type)
  
        p = self.parent()
        # 记录鼠标单击次数
        p.nwap_lcd_clicked.counter += 1
  
        # 左键扫雷
        if e.buttons() == Qt.LeftButton:
            # 踩中雷, 全部雷都翻起来
            if self.m_type == self.MINE:
                for t_line_btn in p.btn_map:
                    for btn in t_line_btn:
                        if btn.m_type == btn.MINE:
                            btn.setText(btn.s_mine)
                        else:
                            if btn.mark != True:
                                if btn.m_type != btn.NOTMINE:
                                    btn.setText(str(btn.m_type))
                        btn.pushed = True
                # 苦逼脸
                p.RestartBtn.setText('重置')
                QMessageBox.critical(self, "失败!", "您不小心踩到了雷! " + self.s_mine)
                return None
            elif self.m_type == self.NOTMINE:
                self.AutoSwap(self.m_x, self.m_y)
            else:
                self.setText(str(self.m_type))
              
            p.mine_counter -= 1
            self.pushed = True
        # 右键添加标记
        elif e.buttons() == Qt.RightButton:
            if self.mark == False:
                self.setText(self.s_flag)
                self.mark = True
            else:
                self.setText("")
                self.mark = False
          
        self.setFocus(False)
      
  
    ## 当按下的位置是NOTMINE时自动扫雷
    def AutoSwap(self, x, y):
        p = self.parent()
        map_btn = p.btn_map
          
        def lookup(t_line, index):
            # 向左扫描
            i = index
            while i >= 0 and not t_line[i].pushed and t_line[i].m_type != MineButton.MINE:
                if t_line[i].m_type != MineButton.NOTMINE:
                    t_line[i].setText(str(t_line[i].m_type))
                t_line[i].pushed = True
                p.mine_counter -= 1
                p.nwap_lcd_counter.counter = p.mine_counter
                i -= 1
                if t_line[i].m_type != MineButton.NOTMINE:
                    break
            # 向右扫描
            i = index + 1
            while i < p.mine_map.width and not t_line[i].pushed and t_line[i].m_type != MineButton.MINE:
                if t_line[i].m_type != MineButton.NOTMINE:
                    t_line[i].setText(str(t_line[i].m_type))
                t_line[i].pushed = True
                p.mine_counter -= 1
                p.nwap_lcd_counter.counter = p.mine_counter
                i += 1
                if t_line[i].m_type != MineButton.NOTMINE:
                    break
          
        # 向上扫描
        j = y
        while j >= 0:
            lookup(map_btn[j], x)
            j -= 1
        # 向下扫描
        j = y + 1
        while j < p.mine_map.height:
            lookup(map_btn[j], x)
            j += 1
     
  
class MineWindow(QWidget):
  
    def __init__(self):
        super().__init__()
        self.mine_map = Mine(nMines=16)
        self.InitGUI()
        #print(self.mine_map)
          
    def InitGUI(self):
          
        w_width = 304
        w_height = 344
  
        self.resize(w_width, w_height)
        self.setFixedSize(self.width(), self.height())
        self.setWindowTitle("扫雷")
  
        ## 窗口居中于屏幕
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.x(), qr.y())
  
  
        l_start_x = 2
        l_start_y = 40
        l_x = l_start_x
        l_y = l_start_y
        l_width = 30
        l_height = 30
  
        # 雷区按钮
        self.btn_map = []
        for h in range(self.mine_map.height):
            l_x = l_start_x
            self.btn_map.append(list())
            for w in range(self.mine_map.width):
                self.btn_map[h].append(MineButton([w, h], self.mine_map[h][w], self))
                self.btn_map[h][w].resize(l_width, l_height)
                self.btn_map[h][w].move(l_x, l_y)
                self.btn_map[h][w].show()
                l_x += l_width
            l_y += l_height
  
        r_width = 40
        r_height = 30
  
        # 恢复按钮
        self.RestartBtn = QPushButton('重置', self)
        self.RestartBtn.clicked.connect(self.restart_btn_event)
        self.RestartBtn.resize(r_width, r_height)
        self.RestartBtn.move((w_width-r_width)//2, 6)
  
        ## 计数器
        self.__mine_counter = self.mine_map.width * self.mine_map.height - self.mine_map.n_mine
  
        ## 两个LCD显示控件
        # 操作次数
        self.nwap_lcd_clicked = LCDCounter(0, self)
        self.nwap_lcd_clicked.move(44, 8)
  
        # 无雷块个数
        self.nwap_lcd_counter = LCDCounter(self.mine_counter, self)
        self.nwap_lcd_counter.move(204, 8)
          
    def restart_btn_event(self):
        self.mine_map.remix()
        #QMessageBox.information(self, "look up", str(self.mine_map))
        for y in range(len(self.btn_map)):
            for x in range(len(self.btn_map[y])):
                self.btn_map[y][x].pushed = False
                self.btn_map[y][x].setText("")
                self.btn_map[y][x].m_type = self.mine_map[y][x]
          
        self.mine_counter = self.mine_map.width * self.mine_map.height - self.mine_map.n_mine
        self.RestartBtn.setText('重置')
        self.nwap_lcd_clicked.counter = 0
        self.nwap_lcd_counter.counter = self.mine_counter
      
    ### 计数器
    @property
    def mine_counter(self):
        return self.__mine_counter
    @mine_counter.setter
    def mine_counter(self, value):
        self.__mine_counter = value
        self.nwap_lcd_counter.dec()
        if self.mine_counter == 0:
            for t_line_btn in self.btn_map:
                for btn in t_line_btn:
                    if btn.m_type == btn.MINE:
                        btn.setText(btn.s_success)
                        btn.pushed = True
            QMessageBox.information(self, "恭喜!", "您成功扫雷! " + MineButton.s_success)
  
  
if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MineWindow()
    w.show()
    sys.exit(app.exec_())

对源代码进行了些许不必要的删减,使其更为精简;以及一定的修改,原始代码中关于重新开始游戏图标、标记图标、雷图标的对应字符代码(如&#9873;&#9760;等)似乎均已失效,导致显示为代码本身,为此更换了其它图标标识和文字标注

运行效果如下:

主要参考来源:

https://www.jb51.net/article/250800.htm

 

 

 

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值