Python:遊戲:寫一個和 XP 上一模一樣的“掃雷”

丹楓無跡發表於2019-03-27

本文程式碼基於 python3.6 和 pygame1.9.4。

這次,我們來模仿做一個 XP 上的掃雷,感覺 XP 上的樣式比 win7 上的好看多了。

Python:遊戲:寫一個和 XP 上一模一樣的“掃雷”

原諒我手殘,掃雷基本就沒贏過,測試的時候我是偷偷的把雷的數量從99改到50才贏了。。。

下面將一下我的實現邏輯。

首先,如何表示雷和非雷,一開始想的是,建立一個二維陣列表示整個區域,0表示非地雷,1表示地雷。後來一想不對,還有標記為地雷,標記為問號,還有表示周邊雷數的數字,好多狀態,乾脆就做個類吧

class BlockStatus(Enum):
    normal = 1  # 未點選
    opened = 2  # 已點選
    mine = 3    # 地雷
    flag = 4    # 標記為地雷
    ask = 5   # 標記為問號
    bomb = 6    # 踩中地雷
    hint = 7    # 被雙擊的周圍
    double = 8  # 正被滑鼠左右鍵雙擊


class Mine:
    def __init__(self, x, y, value=0):
        self._x = x
        self._y = y
        self._value = 0
        self._around_mine_count = -1
        self._status = BlockStatus.normal
        self.set_value(value)

    def __repr__(self):
        return str(self._value)
        # return f'({self._x},{self._y})={self._value}, status={self.status}'

    def get_x(self):
        return self._x

    def set_x(self, x):
        self._x = x

    x = property(fget=get_x, fset=set_x)

    def get_y(self):
        return self._y

    def set_y(self, y):
        self._y = y

    y = property(fget=get_y, fset=set_y)

    def get_value(self):
        return self._value

    def set_value(self, value):
        if value:
            self._value = 1
        else:
            self._value = 0

    value = property(fget=get_value, fset=set_value, doc='0:非地雷 1:雷')

    def get_around_mine_count(self):
        return self._around_mine_count

    def set_around_mine_count(self, around_mine_count):
        self._around_mine_count = around_mine_count

    around_mine_count = property(fget=get_around_mine_count, fset=set_around_mine_count, doc='四周地雷數量')

    def get_status(self):
        return self._status

    def set_status(self, value):
        self._status = value

    status = property(fget=get_status, fset=set_status, doc='BlockStatus')
複製程式碼

佈雷就很簡單了,隨機取99個數,從上往下順序排就是了。

class MineBlock:
    def __init__(self):
        self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]

        # 埋雷
        for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
            self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
複製程式碼

我們點選一個格子的時候,只要根據點選的座標,找到對應的 Mine,看它的值是多少,就知道有沒有踩中雷了。

如果沒踩中雷的話,要計算周邊8個位置中有幾個雷,以便顯示對應的數字。

如果周邊有雷,那麼顯示數字,這個簡單,可是如果周邊沒有雷,那就要顯示一片區域,直到有雷出現,如下圖,我只點了當中一下,就出現了那麼大一片區域

Python:遊戲:寫一個和 XP 上一模一樣的“掃雷”

這個計算其實也容易,只要用遞迴就可以了,如果計算出周圍的雷數為0,則遞迴計算周邊8個位置的四周雷數,直到雷數不為0。

class MineBlock:
  def open_mine(self, x, y):
        # 踩到雷了
        if self._block[y][x].value:
            self._block[y][x].status = BlockStatus.bomb
            return False

        # 先把狀態改為 opened
        self._block[y][x].status = BlockStatus.opened

        around = _get_around(x, y)

        _sum = 0
        for i, j in around:
            if self._block[j][i].value:
                _sum += 1
        self._block[y][x].around_mine_count = _sum

        # 如果周圍沒有雷,那麼將周圍8個未中未點開的遞迴算一遍
        # 這就能實現一點出現一大片開啟的效果了
        if _sum == 0:
            for i, j in around:
                if self._block[j][i].around_mine_count == -1:
                    self.open_mine(i, j)

        return True


def _get_around(x, y):
    """返回(x, y)周圍的點的座標"""
    # 這裡注意,range 末尾是開區間,所以要加 1
    return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1)
            for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) if i != x or j != y]
複製程式碼

接下來還有一個麻煩的地方,我們經常滑鼠左右鍵同時按下,如果雷被全部標記,則會一下子開啟周圍所有的格子,如果其中有標記錯的,那麼不好意思,GAME OVER。

如果沒有全標記完,會有一個效果顯示周圍一圈未被開啟和標記的格子

class MineBlock:
   def double_mouse_button_down(self, x, y):
        if self._block[y][x].around_mine_count == 0:
            return True

        self._block[y][x].status = BlockStatus.double

        around = _get_around(x, y)

        sumflag = 0     # 周圍被標記的雷數量
        for i, j in _get_around(x, y):
            if self._block[j][i].status == BlockStatus.flag:
                sumflag += 1
        # 周邊的雷已經全部被標記
        result = True
        if sumflag == self._block[y][x].around_mine_count:
            for i, j in around:
                if self._block[j][i].status == BlockStatus.normal:
                    if not self.open_mine(i, j):
                        result = False
        else:
            for i, j in around:
                if self._block[j][i].status == BlockStatus.normal:
                    self._block[j][i].status = BlockStatus.hint
        return result

    def double_mouse_button_up(self, x, y):
        self._block[y][x].status = BlockStatus.opened
        for i, j in _get_around(x, y):
            if self._block[j][i].status == BlockStatus.hint:
                self._block[j][i].status = BlockStatus.normal
複製程式碼

掃雷的主要邏輯就這麼多,剩下來的就是一些雜七雜八的事件了。


相關博文推薦:


掃碼關注我的個人公眾號,回覆 “掃雷” 獲取原始碼。


掃碼關注我的個人公眾號

相關文章