# Python實作課程 ## 第六堂課 : MineSweeper ## 2022 / 6 / 10 ### Tony --- # 遊戲介紹 ---- ![](https://i.imgur.com/vq9qBN2.png) 踩地雷,是一款單人或者多人的電腦遊戲。遊戲目標是找出所有沒有地雷的方格,完成遊戲; 要是按了有地雷的方格,遊戲失敗。 (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}]"}
    1995 views