# Python實作課程
## 第六堂課 : MineSweeper
## 2022 / 6 / 10
### Tony
---
# 遊戲介紹
----

踩地雷,是一款單人或者多人的電腦遊戲。遊戲目標是找出所有沒有地雷的方格,完成遊戲;
要是按了有地雷的方格,遊戲失敗。
(From維基百科)
[minesweeper.online](https://minesweeper.online/tw/)
---
# 遊戲流程
----
1. 給玩家看初始棋盤
2. 玩家決定要挖開哪一格
3. 如果挖到地雷,遊戲結束
如果沒有,一直向四面八方挖,直到每個邊界格子的周圍八格都至少有一顆地雷,並把數字標在格子上
4. 給玩家看新的棋盤
5. 重複步驟2~4直到可挖的格子都被挖開
---
# 開始寫Code
----
[模板](https://github.com/Tony041010/2022_CRC_Python_Project_Class_Template/blob/main/MineSweeper.py)
----
一、Board class
我們先寫 Board class,完成__init__函式
* dim_size : 棋盤大小(邊長)
* num_bombs : 炸彈數量
* dug : 把已挖過的格子座標用(row, col)的Tuple記錄在這個set裡面
```python=
def __init__(self, dim_size, num_bombs):
self.dim_size = dim_size # dim stands for "dimention"
self.num_bombs = num_bombs
self.board = self.make_new_board()
self.assign_values_to_board()
self.dug = set()
```
----
make_new_board():
利用一個二微陣列創造一個空棋盤並且把炸彈隨機放在棋盤中
```python=
def make_new_board(self):
board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)]
# It'll be llike this :
# [[None, None,......, None],
# [None, None,......, None],
# [None, None,......, None],
# [None, None,......, None]]
# plant the bombs
bombs_planted = 0 #紀錄已經放進去的炸彈數
while bombs_planted < self.num_bombs:
loc = random.randint(0, self.dim_size**2 - 1) # loc stands for "location"
row = loc // self.dim_size
col = loc % self.dim_size
if board[row][col] == '*': #用星星代表地雷
#代表已經放過了,跳過
continue
board[row][col] = '*'
bombs_planted += 1
return board
```
----
assign_values_to_board():
利用它知道每個格子周邊八格的地雷數
```python=
def assign_values_to_board(self):
# r, c stands for "row, column"
for r in range(self.dim_size):
for c in range(self.dim_size):
if self.board[r][c] == '*'
continue #不需要知道地雷格子的資訊
self.board[r][c] = self.get_num_neighboring_bombs(r, c)
```
----
get_num_neighboring_bombs():
取得特定格子周圍的地雷數,透過已知的row跟column來得到周圍八格的座標
為了不要超出index的範圍,把row-1(+1)跟column-1(+1)與0和self.dim_size-1 兩兩做比較
```python=
def get_num_neighboring_bombs(self, row, column):
# top left : (row-1, col-1)
# top middle : (row-1, col)
# top right : (row-1, col+1)
# left : (row, col-1)
# middle : (row, col)
# right : (row, col+1)
# bottom left : (row+1, col-1)
# bottom middle : (row+1, col)
# bottom right : (row+1, col+1)
# make sure to not go out of bounds!
num_neighboring_bombs = 0
for r in range( max(0, row-1), min(self.dim_size-1, row+1) + 1 ):
for c in range( max(0, col-1), min(self.dim_size-1, col+1) + 1 ):
if r == row and c == col: #本身那格不用檢查
continue
if self.board[r][c] == '*':
num_neighboring_bmbs += 1
return num_neighboring_bombs
```
----
dig():
挖格子的時候執行的函式,會回傳一個布林值表示是否挖到炸彈
透過遞迴滿足往外挖的功能
```python=
def dig(self, row, col):
self.dug.add( (row, col) )
if self.board[row][col] == '*':
return False
elif self.board[row][col] > 0: #周圍有炸彈,就不用繼續連鎖挖下去
return True
# self.board[row][col] == 0 : 用遞迴連鎖挖
for r in range( max(0, row-1), min(self.dim_size-1, row+1) + 1 ):
for c in range( max(0, col-1), min(self.dim_size-1, col+1) + 1 ):
if (r, c) in self.dug:
continue #已經挖過的格子就不用檢查
self.dig(r, c)
return True
```
----
\_\_str\_\_():
這是一個特殊函式(跟__init__類似),當要把這個class的物件print出來時就會被呼叫,因此我們可以自訂要印出來的東西
模板已經幫你寫好了,可以理解一下上半部的Code
```python=
def __str__(self):
visible_board = [ [None for _ in range(self.dim_size)] for _ in range(self.dim_size) ]
for row in range(self.dim_size):
for col in range(self.dim_size):
if (row, col) in self.dug: #有挖過的才印出來
visible_board[row][col] = str(self.board[row][col])
else:
visible_board[row][col] = ' '
```
到這邊就完成Board class的所有東西了
現在回來寫play function
----
二、Play function
函式滿足四個步驟:
1. 創建棋盤
2. Show出棋盤並請使用者輸入要挖的格子
3. If挖到炸彈,遊戲結束
If沒有,連鎖挖直到周邊格子的周圍都有至少一顆地雷
4. 重複2~4直到格子被挖完
2~4可以用迴圈完成
----
re.split(A, B):把B按照A的規則切開成不同的東西
A是一個正則表達式,偏複雜,有興趣可以研究一下
Code
```python=
def play(dim_size=10, num_bombs=10):
# Step 1
board = Board(dim_size, num_bombs)
#Step 2 ~ Step 4
safe = True #挖格子時會回傳的布林值,預設True
while len(board.dug) < board.dim_size**2 - board.num_bombs:
print(board)
print('')
user_input = re.split(',(\\s)*', input('Where would you like to dig? Input as roe, col:')) # '0,-3'
row, col = int(user_input[0]), int(user_input[-1]) #直接取第一個跟最後一個,避免剛剛plit時多切到奇怪的東西
# check有沒有亂輸入
if row < 0 or row >= board.dim_size or col < 0 or col >= board.dim_size:
print("Invalid location. Try again.")
continue
#if it's valid, we dig
safe = board.dig(row, col)
if not safe:
#挖到炸彈了
break #(game over. RIP)
time.sleep(0.5) #每次挖完都間隔半秒
time.sleep(1)#留一秒停頓讓玩家覺得有事情要發生了
#到這邊表示遊戲要結束,確認玩家是贏是輸
if safe:
print("CONGRATULATIONS! YOU ARE VICTORIOUS")
else:
print("SORRY, GAME OVER :(")
#揭露盤面
board.dug = [(r,c) for r in range(board.dim_size) for c in range(board.dim_size)]
print(board)
time.sleep(5) #留五秒讓玩家看
```
----
最後完成main函式就結束了
```python=
if __name__ == "__main__":
play()
```
{"metaMigratedAt":"2023-06-17T01:56:06.238Z","metaMigratedFrom":"YAML","title":"Python實作四:MineSweeper","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"theme\":null}","contributors":"[{\"id\":\"4f731eff-9d88-41f4-af56-2e3e02f20cfc\",\"add\":5631,\"del\":23}]"}