Python:遊戲:寫一個和 XP 上如出一轍的「掃雷」

本文代碼基於 python3.6 和 pygame1.9.4。python

此次,咱們來模仿作一個 XP 上的掃雷,感受 XP 上的樣式比 win7 上的好看多了。數組

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

下面將一下個人實現邏輯。post

首先,如何表示雷和非雷,一開始想的是,創建一個二維數組表示整個區域,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個數,從上往下順序排就是了。spa

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,看它的值是多少,就知道有沒有踩中雷了。code

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

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

這個計算其實也容易,只要用遞歸就能夠了,若是計算出周圍的雷數爲0,則遞歸計算周邊8個位置的四周雷數,直到雷數不爲0。blog

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
複製代碼

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


相關博文推薦:


掃碼關注個人我的公衆號,回覆 「掃雷」 獲取源碼。


掃碼關注個人我的公衆號
相關文章
相關標籤/搜索