利用Python自製掃雷遊戲

  • 2019 年 10 月 8 日
  • 筆記

本文轉自公眾號『大齡碼農的Python之路』

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

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

原諒我手殘,掃雷基本就沒贏過,測試的時候我是偷偷的把雷的數量從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個位置中有幾個雷,以便顯示對應的數字。

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

這個計算其實也容易,只要用遞歸就可以了,如果計算出周圍的雷數為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  

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

公眾號後台回復 「掃雷」 獲取源碼。