本文程式碼基於 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
複製程式碼
掃雷的主要邏輯就這麼多,剩下來的就是一些雜七雜八的事件了。
相關博文推薦:
掃碼關注我的個人公眾號,回覆 “掃雷” 獲取原始碼。