# 踩地雷
https://docs.google.com/presentation/d/1rgTD9G5UcJ00ab0qPNBH6KezdZhYMYgV26G-mnOPhEc/edit#slide=id.g214325071ea_0_59
https://docs.google.com/document/d/1fQJgmJXq2ST-p5_FGMym74ffLy3-bh-4Q2-JXO7CWxE/edit
```python=
import pygame
import random
pygame.init()
bg_color = (192, 192, 192)
grid_color = (128, 128, 128)
game_width = 10 # 遊戲視窗寬度
game_height = 10 # 遊戲視窗高度
numMine = 9 # 炸彈數量
grid_size = 32 # 網格大小
border = 32 # 邊界寬度
top_border = 120# 邊界高度
display_width = grid_size * game_width + border * 2 # 展示寬度
display_height = grid_size * game_height + border + top_border # 展示高度
gameDisplay = pygame.display.set_mode((display_width, display_height)) # 創建視窗
timer = pygame.time.Clock() # 創建計時器
pygame.display.set_caption("Minesweeper") # 標題名稱
# 引入圖片
spr_emptyGrid = pygame.image.load("Sprites/empty.png")
spr_flag = pygame.image.load("Sprites/flag.png")
spr_grid = pygame.image.load("Sprites/Grid.png")
spr_grid1 = pygame.image.load("Sprites/grid1.png")
spr_grid2 = pygame.image.load("Sprites/grid2.png")
spr_grid3 = pygame.image.load("Sprites/grid3.png")
spr_grid4 = pygame.image.load("Sprites/grid4.png")
spr_grid5 = pygame.image.load("Sprites/grid5.png")
spr_grid6 = pygame.image.load("Sprites/grid6.png")
spr_grid7 = pygame.image.load("Sprites/grid7.png")
spr_grid8 = pygame.image.load("Sprites/grid8.png")
spr_grid7 = pygame.image.load("Sprites/grid7.png")
spr_mine = pygame.image.load("Sprites/mine.png")
spr_mineClicked = pygame.image.load("Sprites/mineClicked.png")
spr_mineFalse = pygame.image.load("Sprites/mineFalse.png")
# 創建繪製文字的函數
def drawText(txt, size,y=-20):
# 使用指定的字體、大小和文本字符串創建一個新的文本表面
screen_text = pygame.font.SysFont("simhei", size).render(txt, True, (255, 0, 0))
# 獲取文本表面的矩形邊界框
rect = screen_text.get_rect()
# 設置文本表面的中心位置,橫向置中,縱向在遊戲區域上方偏移一定距離
rect.center = (game_width * grid_size / 2 + border, game_height * grid_size / 2 + top_border + y)
# 在遊戲顯示表面上繪製文本表面
gameDisplay.blit(screen_text, rect)
# Create class grid
class Grid:
def __init__(self, xGrid, yGrid, type):
self.xGrid = xGrid # 網格的 x 座標
self.yGrid = yGrid # 網格的 y 座標
self.clicked = False # 布林變數,用於檢查該網格是否被點擊
self.mineClicked = False # 布林變數,用於檢查該網格是否被點擊並且是地雷
self.mineFalse = False # 布林變數,用於檢查玩家是否標錯網格
self.flag = False # 布林變數,用於檢查玩家是否標記了網格
# 創建 rectObject 以處理繪製和碰撞
self.rect = pygame.Rect(border + self.xGrid * grid_size, top_border + self.yGrid * grid_size, grid_size, grid_size)
self.val = type # 網格的值,-1 表示地雷
def drawGrid(self):
# 根據格子的bool變數和值繪製格子
if self.mineFalse: # 若炸彈放錯地方
gameDisplay.blit(spr_mineFalse, self.rect)
else:
if self.clicked: # 若被點擊
if self.val == -1: # 若為炸彈
if self.mineClicked: # 若已經被揭開的炸彈
gameDisplay.blit(spr_mineClicked, self.rect)
else: # 若尚未揭開的炸彈
gameDisplay.blit(spr_mine, self.rect)
else: # 若為空格或者有數字
if self.val == 0: # 若為空格
gameDisplay.blit(spr_emptyGrid, self.rect)
elif self.val == 1: # 若為1
gameDisplay.blit(spr_grid1, self.rect)
elif self.val == 2: # 若為2
gameDisplay.blit(spr_grid2, self.rect)
elif self.val == 3: # 若為3
gameDisplay.blit(spr_grid3, self.rect)
elif self.val == 4: # 若為4
gameDisplay.blit(spr_grid4, self.rect)
elif self.val == 5: # 若為5
gameDisplay.blit(spr_grid5, self.rect)
elif self.val == 6: # 若為6
gameDisplay.blit(spr_grid6, self.rect)
elif self.val == 7: # 若為7
gameDisplay.blit(spr_grid7, self.rect)
elif self.val == 8: # 若為8
gameDisplay.blit(spr_grid8, self.rect)
else: # 若未被點擊
if self.flag: # 若有旗幟
gameDisplay.blit(spr_flag, self.rect)
else: # 若為初始狀態
gameDisplay.blit(spr_grid, self.rect)
def revealGrid(self):
self.clicked = True # 該格子被點擊
# 如果該格為0,自動揭示周圍格子
if self.val == 0:
for x in range(-1, 2):
if self.xGrid + x >= 0 and self.xGrid + x < game_width:
for y in range(-1, 2):
if self.yGrid + y >= 0 and self.yGrid + y < game_height:
if not grid[self.yGrid + y][self.xGrid + x].clicked: # 周圍格子未被點擊過
grid[self.yGrid + y][self.xGrid + x].revealGrid() # 揭示周圍格子
elif self.val == -1: # 如果是地雷
# 自動揭示所有地雷
for m in mines:
if not grid[m[1]][m[0]].clicked: # 未被點擊過的地雷
grid[m[1]][m[0]].revealGrid() # 揭示地雷
def updateValue(self):
# 當所有格子都生成後,更新該格的值
if self.val != -1:
for x in range(-1, 2):
if self.xGrid + x >= 0 and self.xGrid + x < game_width:
for y in range(-1, 2):
if self.yGrid + y >= 0 and self.yGrid + y < game_height:
if grid[self.yGrid + y][self.xGrid + x].val == -1:
self.val += 1 # 將該格周圍地雷數量加1,用來表示該格是地雷數量
def gameLoop():
gameState = "Playing" # 設定遊戲狀態
flagLeft = numMine # 剩餘旗子數量
global grid # 存取全域變數grid
grid = [] # 初始化grid
global mines # 存取全域變數mines
t = 0 # 計時器,一開始時間設為0
# 生成地雷
mines = [[random.randrange(0, game_width),
random.randrange(0, game_height)]] # 在遊戲畫面上的隨機位置生成一個地雷
# 隨機生成地雷,從第二顆開始產生
for c in range(numMine - 1):
pos = [random.randrange(0, game_width),
random.randrange(0, game_height)] # 隨機生成一個地雷位置
same = True
while same:
# 檢查是否和前面的地雷位置重複
for i in range(len(mines)):
if pos == mines[i]:
pos = [random.randrange(0, game_width),random.randrange(0, game_height)] # 如果和前面的地雷重複,重新生成一個地雷位置
break
if i == len(mines) - 1:
same = False
mines.append(pos) # 將新的地雷位置加入地雷列表
for j in range(game_height):
line = []
for i in range(game_width):
if [i, j] in mines: # 如果該位置有地雷,則建立一個val為-1的格子
line.append(Grid(i, j, -1))
else: # 如果該位置沒有地雷,則建立一個val為0的格子
line.append(Grid(i, j, 0))
grid.append(line) # 將整行格子加入到grid列表中
# 更新格子的數值
for i in grid:#每一行的line
for j in i:#每一個grid的object
j.updateValue() # 根據周圍的地雷數量更新該格子的數值
while gameState != "Exit":
# Reset screen 重置畫面
gameDisplay.fill(bg_color)
# User inputs 使用者輸入
for event in pygame.event.get():
# Check if player close window 檢查玩家是否關閉視窗
if event.type == pygame.QUIT:
gameState = "Exit"
# Check if play restart 檢查是否重新開始遊戲
if gameState == "Game Over" or gameState == "Win":
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
gameState = "Exit"
gameLoop() # 重新開始
else:
if event.type == pygame.MOUSEBUTTONDOWN:
for i in grid:
for j in i:
if j.rect.collidepoint(event.pos):
if event.button == 1:
# If player left clicked of the grid 當玩家點擊格子時
j.revealGrid() # 顯示格子
# If it's a mine 如果點擊的是地雷
if j.val == -1:
gameState = "Game Over" # 遊戲結束
j.mineClicked = True # 標記為點擊的地雷
elif event.button == 3:
# If the player right clicked 當玩家右鍵點擊
if not j.clicked:
if j.flag:
j.flag = False # 取消旗子
flagLeft += 1 # 地雷數加1
else:
j.flag = True # 設定旗子
flagLeft -= 1 # 地雷數減1
# Check if won 檢查是否贏得遊戲
w = True # 創建一個變量 w,用於判斷是否所有方格都已被點擊
for i in grid: # 遍歷所有方格
for j in i:
j.drawGrid() # 繪製當前方格
if j.val != -1 and not j.clicked: # 如果該方格已被揭開或被標記為地雷,則不計入勝利的條件
w = False # 如果還有方格未被點擊,則 w 設為 False
if w and gameState != "Exit": # 如果 w 為 True,且遊戲狀態不為 "Exit"
gameState = "Win" # 將遊戲狀態設為 "Win",表示玩家贏得了遊戲
# Draw Texts 繪製文字
if gameState != "Game Over" and gameState != "Win": # 如果遊戲還未結束或未贏得遊戲
t += 1 # 將 t 變量加一
elif gameState == "Game Over": # 如果遊戲狀態為 "Game Over"
drawText("Game Over!", 50) # 繪製 "Game Over!" 文字
drawText("R to restart", 35, 30) # 繪製 "R to restart" 文字,並指定其在 Y 軸的位置
for i in grid:
for j in i:
if j.flag and j.val != -1: # 如果該方格被標記為地雷但實際上不是地雷
j.mineFalse = True # 將該方格的 mineFalse 屬性設為 True,以顯示該方格不是地雷的標誌
else: # 如果遊戲狀態為 "Win"
drawText("You WON!", 50) # 繪製 "You WON!" 文字
drawText("R to restart", 35, 50) # 繪製 "R to restart" 文字,並指定其在 Y 軸的位置
# Draw time 繪製時間
s = str(t // 15) # 將時間轉換為字符串
screen_text = pygame.font.SysFont("Calibri", 50).render(s, True, (0, 0, 0)) # 創建字體對象
gameDisplay.blit(screen_text, (90, 50)) # 在屏幕上顯示時間
screen_text = pygame.font.SysFont("Calibri", 50).render("Time", True, (0, 0, 0))
gameDisplay.blit(screen_text, (65, 5))# 在屏幕上顯示time
# Draw flag 繪製旗子
screen_text = pygame.font.SysFont("Calibri", 50).render(str(flagLeft), True, (0, 0, 0)) # 創建字體對象
gameDisplay.blit(screen_text, (270, 50)) # 在屏幕上顯示剩餘旗子數
screen_text = pygame.font.SysFont("Calibri", 50).render("Flag", True, (0, 0, 0))
gameDisplay.blit(screen_text, (240, 5)) # 在屏幕上顯示flag
pygame.display.update() # 更新屏幕
timer.tick(15) # 限制畫面更新速度,以防止遊戲過快
gameLoop()#開始遊戲
pygame.quit()
quit()