---
# System prepended metadata

title: 'Pygame遊戲實作 : 踩地雷'

---

# Pygame遊戲實作 : 踩地雷

最後一堂社課了，除了代表即將準備考試外，也代表Pygame的基礎操作也差不多熟悉了
接下來展示的程式，有一些bug，就由大家來找出並且解決，可以當作是你們的side project。
(此程式完全是由我自行撰寫，因此只要修改了就可以當作自己的作品)

## 遊戲想法
基本 : 踩到地雷即輸，沒踩到則加分
數字 : 周圍有多少地雷
Flag : 防止誤觸，通常會在有地雷的地方加上
當採到周圍沒有地雷的區域 : 開始遍歷，一直到找到周圍有地雷的格子

## 做法
為了達到此目的，需要有一個container，存放flags、mines以及被踩中的格子，姑且叫做coords
然後需要有一套公式，計算出周圍有多少地雷
有關遍歷的部分，首先會想到的是BFS

### 有關BFS
BFS是從根節點開始，沿著樹的寬度遍歷樹的節點。如果所有節點均被存取，則演算法中止。
![](https://upload.wikimedia.org/wikipedia/commons/4/46/Animated_BFS.gif)

使用此演算法，踩中0的點後，可以依序從周圍開始找，若周圍也是0，就繼續尋找，直到找到全部都非0。

## 初始化
```python=
import pygame
from pygame.locals import *
import numpy as np
import sys
import random
from queue import Queue
WINDOWWIDTH = 400
WINDOWHEIGHT = 400
CELLSIZE = 20
WHITE     = (255, 255, 255)
BLACK     = (  0,   0,   0)
RED       = (255,   0,   0)
GREEN     = (  0, 255,   0)
DARKGREEN = (  0, 155,   0)
DARKGRAY  = ( 40,  40,  40)
BLUE =  (0, 0, 255)
BGCOLOR = DARKGREEN
FPS = 30
FPSCLOCK = pygame.time.Clock()
```
### 產生50個地雷
```python=
def generateMines():
    mines = set() # Why Set?? 請各位想想
    while len(mines) < 50:
        x = random.randint(0, (WINDOWWIDTH)//20 - 1) 
        y = random.randint(0, (WINDOWHEIGHT)//20 - 1)
        mines.add((x * 20, y * 20))
    return mines
```
### 畫出地雷
```python=
def drawMines(mines):
    for mine in mines:
        x = mine[0]
        y = mine[1]
        rect = pygame.Rect(x + 1,y + 1,CELLSIZE - 1,CELLSIZE - 1)
        pygame.draw.rect(DISPLAYSURF,RED,rect)
```

### 畫出格子 包括「被踩中的地區」以及flag
```python=
def drawBlocks(coords,mines):
    font = pygame.font.Font('freesansbold.ttf',18)
    # draw flags
    for flag in flags:
        x = flag[0]
        y = flag[1]
        rect = pygame.Rect(x+1 , y +1,CELLSIZE - 1 ,CELLSIZE - 1)
        pygame.draw.rect(DISPLAYSURF,BLUE,rect)
    #drawMines(mines)
    # draw coords
    for coord in coords:
        x = coord[0]
        y = coord[1]
        rect = pygame.Rect(x+1 , y +1,CELLSIZE - 1 ,CELLSIZE - 1)
        num = 0
        for i in [-1,0,1]:
            for j in [-1,0,1]:
                if ((x+(i*20),y+(j*20))) in mines:
                    num+=1
        text = font.render(str(num),True,BLACK)
        text_rect = text.get_rect()
        text_rect.topleft = (x,y)
        pygame.draw.rect(DISPLAYSURF,WHITE,rect)
        DISPLAYSURF.blit(text,text_rect)
```
### 畫出線
```python=
def drawGrid():
    for x in range(0, WINDOWWIDTH, CELLSIZE): # draw vertical lines
        pygame.draw.line(DISPLAYSURF, WHITE, (x, 0), (x, WINDOWHEIGHT))
    for y in range(0, WINDOWHEIGHT, CELLSIZE): # draw horizontal lines
        pygame.draw.line(DISPLAYSURF, WHITE, (0, y), (WINDOWWIDTH, y))
```
### 終止程式

```python=
def terminate():
    pygame.quit()
    sys.exit()
```
### 挖掘(按下左鍵)
```python=
def dig(x,y,mines,coords):
    x //= 20
    x *= 20
    y //= 20
    y *= 20
    if (x,y) not in mines and (x,y) not in flags:
        coords.add((x,y))
        coords = traverse(x,y,mines,coords)
        # print(coords)
        return coords, True
    elif (x,y) in mines and (x,y) not in flags:
        drawMines(mines)
        return coords, False
    else: return coords,True
```
### 遍歷方塊
```python=
def traverse(x,y,mines,coords):
    coords.add((x,y))
    q = Queue()
    q.put((x,y))
    
    while not q.empty():
        temp = q.get(0)
        x = temp[0]
        y = temp[1]
        flag = 0
        #　if there is any numbers in the block
        for i in [-1,0,1]:
            for j in [-1,0,1]:
                if ((x+(i*20),y+(j*20))) in mines:
                    flag = 1
        if (flag): continue
        for i in [-1,0,1]:
            for j in [-1,0,1]:
                if ((x+(i*20),y+(j*20)) not in coords and x+(i*20) >= 0 and x+(i*20) < 400 and y+(j*20) >= 0 and y+(j*20) < 400): 
                    q.put((x+(i*20),y+(j*20)))
                    coords.add((x+(i*20),y+(j*20)))
        
    
    return coords
```

### 設定Flag (按下右鍵)
```python=
def setFlags(x,y):
    x //= 20
    x *= 20
    y //= 20
    y *= 20
    if (x,y) in flags:
        flags.remove((x,y))
    elif (x,y) not in coords:
        flags.add((x,y))
    
```

### 看看是否按下按鍵 (上次社課內容)
```python=
def checkForKeyPress():
    if len(pygame.event.get(QUIT)) > 0:
        terminate()

    keyUpEvents = pygame.event.get()
    if len(keyUpEvents) == 0:
        return None
    if keyUpEvents[0].type== KEYDOWN and keyUpEvents[0].key == K_ESCAPE:
        terminate()
    return keyUpEvents[0]
```
### 畫出分數
```python=
def drawScore(score):
    scoreSurf = BASICFONT.render('Score: %s' % (score), True, BLACK)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (WINDOWWIDTH - 120, 10)
    DISPLAYSURF.blit(scoreSurf, scoreRect)
```
### 遊戲結束畫面
```python=
def showGameOverScreen(str1,str2):
    drawMines(mines)
    gameOverFont = pygame.font.Font('freesansbold.ttf', 100)
    gameSurf = gameOverFont.render(str1, True, BLACK)
    overSurf = gameOverFont.render(str2, True, BLACK)
    gameRect = gameSurf.get_rect()
    overRect = overSurf.get_rect()
    gameRect.midtop = (WINDOWWIDTH / 2, 10)
    overRect.midtop = (WINDOWWIDTH / 2, gameRect.height + 10 + 25)

    DISPLAYSURF.blit(gameSurf, gameRect)
    DISPLAYSURF.blit(overSurf, overRect)
    pygame.display.update()
    pygame.time.wait(1000)
    checkForKeyPress() # clear out any key presses in the event queue
    while True:
        if checkForKeyPress():
            pygame.event.get() # clear event queue
            return
```

## 程式執行
```python=
def run():
    
    global mines,flags,coords
    mines = generateMines()
    coords = set()
    flags = set()
    flag = True
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    terminate()
            elif event.type == MOUSEBUTTONUP and event.button == 1: # 按下滑鼠左鍵
                x, y= pygame.mouse.get_pos()
                coords, flag = dig(x,y,mines,coords)
            elif event.type == MOUSEBUTTONUP and event.button == 3: # 按下滑鼠右鍵
                x, y= pygame.mouse.get_pos()
                setFlags(x,y)
        # if lose
        if (flag == False): return False
        
        DISPLAYSURF.fill(BGCOLOR)
        drawGrid()
        #drawMines(mines)
        drawBlocks(coords,mines)
        drawScore(len(coords))
        pygame.display.update()
        # if win
        if (400 - len(coords) == len(mines)): return True
        FPSCLOCK.tick(FPS)
```

## main method
```python=
def main():
    global FPSCLOCK,DISPLAYSURF,BASICFONT
    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT))
    BASICFONT = pygame.font.Font('freesansbold.ttf',18)
    
    pygame.display.set_caption("踩地雷")
    while True:
        
        if (run()==False):showGameOverScreen('You','Lose')
        else : showGameOverScreen('You','Win')
```

