踩地雷,是一款單人或者多人的電腦遊戲。遊戲目標是找出所有沒有地雷的方格,完成遊戲;
要是按了有地雷的方格,遊戲失敗。
(From維基百科)
minesweeper.online
一、Board class
我們先寫 Board class,完成__init__函式
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():
利用一個二微陣列創造一個空棋盤並且把炸彈隨機放在棋盤中
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():
利用它知道每個格子周邊八格的地雷數
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 兩兩做比較
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():
挖格子的時候執行的函式,會回傳一個布林值表示是否挖到炸彈
透過遞迴滿足往外挖的功能
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
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
函式滿足四個步驟:
2~4可以用迴圈完成
re.split(A, B):把B按照A的規則切開成不同的東西
A是一個正則表達式,偏複雜,有興趣可以研究一下
Code
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函式就結束了
if __name__ == "__main__": play()