---
# System prepended metadata

title: 踩地雷

---

# 踩地雷
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()