マインスイーパーをPygameで

# 製作の経緯
友人が今度授業でマインスイーパーを作るらしく、僕も気になったので作ってみた。授業ではJavaを扱うようだが、どうせだったら最近勉強中のpythonで作れたらいいなと思いpygameを選択。

:::note warn
ささっと調べて実装しただけなので、pygameの機能を変に使っている可能性あり。
:::

# 成果物

https://youtu.be/hF4-0W825Bc


# プログラミング
### ソースコード
https://github.com/Mitachi-H/Minesweeper

### 導入

```python:game.py
if __name__ == "__main__":
    pygame.init()#初期化
    screen = pygame.display.set_mode((600, 800))
    font = pygame.font.Font(None, 35)
    main()#ゲーム処理開始
```

### メイン関数

描写の方法が独特だと感じた。というのも、毎フレームごとにscreenはリセットされ、再度絵を描く作業を行わないと行けないから。（上手い方法があるのかも）

```python:game.py
def main ():
    bomb=[[False if random.random()>0.2 else True for _ in range(28)] for _ in range(20)]#ボム管理、一定確率でボムセット
    doubtBomb=[[False]*28 for _ in range(20)]#旗管理
    showNumList=[]#見えている数字管理
    numList=[[0]*28 for _ in range(20)]#すべての数字管理
    setnumList(numList,bomb)#数値をボムに合わせる
    clicked=[[False]*28 for _ in range(20)]#クリック済み管理
    gameover=False
    gameclear=False
    while True:
        #描画一式
        drawBackGround()#背景描写
        showAllClicked(clicked)#クリック済み色変更
        showAllDoubtBomb(doubtBomb)#旗表示
        drawBombLeft(countbombLeft(bomb,doubtBomb))#残り爆弾数管理
        showAllNumber(showNumList)#数字表示
        showGameover(gameover)#ゲームオーバー表示
        showAllBomb(gameover,bomb)#ゲームオーバー時ボム表示
        showGameclear(gameclear)#ゲームクリア表示
        pygame.display.update()#画面を更新

        for event in pygame.event.get():
            if event.type == pygame.MOUSEBUTTONDOWN:
                #ゲームオーバーかクリアなら操作無効
                if gameover or gameclear:
                    continue

                i,j = getij(event.pos)#クリックしたマス目取得
                if checkij(i,j,clicked):#i,jが適正値でclickされていないなら
                    if event.button==1:#左クリックなら
                        if bomb[i][j]:#ボムをクリックしたら
                            gameover=True
                        clicked[i][j]=True
                        if doubtBomb[i][j]:#旗をクリックしたら
                            doubtBomb[i][j]=False
                        num=numList[i][j]#数字取得
                        if num==0:
                            showNearby0(i,j,numList,clicked,showNumList)#隣接する0を表示
                        else:
                            showNumList.append((i,j,num))#数字表示
                    if event.button==3:#右クリックなら
                        doubtBomb[i][j]=not doubtBomb[i][j]
                        if bomb==doubtBomb:#旗を全て立てたら
                            gameclear=True
                        pass
            if event.type == pygame.QUIT: # 閉じるボタンが押されたら終了
                pygame.quit()     # Pygameの終了(画面閉じられる)
                sys.exit()
            # キーを押したとき
            if event.type == pygame.KEYDOWN:
                # ESCキーなら終了
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
                    sys.exit()
```
### その他の関数

```python:game.py
def setnumList(numList,bomb):
    for i,iList in enumerate(numList):
        for j in range(len(iList)):
            numList[i][j]=checkNumber(i,j,bomb)
    pass

def checkNumber(i,j,bomb):
    num=0
    for s in [-1,0,1]:
        for t in [-1,0,1]:
            if i+s<0 or i+s>19:
                continue
            if j+t<0 or j+t>27:
                continue
            if s==0 and t==0:
                continue
            num+=int(bomb[i+s][j+t])
    return num

def drawBackGround():
    black = (0,0,0)
    slategrey=(112,128,144)
    screen.fill(slategrey) # 背景を塗りつぶす
    #譜面生成
    for x in range(50,551,25):
        pygame.draw.line(screen,black, (x,50), (x,750), 1)#横20マス
    for y in range(50,751,25):
        pygame.draw.line(screen,black, (50,y), (550,y), 1)#縦28マス

def showAllClicked(clicked):
    for i,iList in enumerate(clicked):
        for j,click in enumerate(iList):
            if click:
                drawClicked(i,j)

def drawClicked(i,j):
    grey = (220,220,220)
    pygame.draw.rect(screen, grey, (50+i*25,50+j*25,25,25), width=0)
    pass

def showAllDoubtBomb(doubtBomb):
    for i,iList in enumerate(doubtBomb):
        for j,isDoubtbomb in enumerate(iList):
            if isDoubtbomb:
                drawDoubtBomb(i,j)

def drawDoubtBomb(i,j):
    lightsalmon	=(255,160,122)
    pygame.draw.circle(screen, lightsalmon,(63+i*25, 63+j*25), 10, width=0)
    pass

def drawBombLeft(bombLeft):
    black = (0,0,0)
    text2 = font.render(str(bombLeft), True,black)#描画する文字列の設定
    screen.blit(text2, (275,15))# 文字列の表示位置
    pass

def countbombLeft(bomb,bombLeft):
    ans=0
    for bombs,lefts in zip(bomb,bombLeft):
        ans+=sum(bombs)-sum(lefts)
    return ans

def showAllNumber(showNumList):
    colorList = [(0,0,0),(0,0,255),(0,138,0),(255,0,0),(0,0,139),(139,0,0)]#white,blue,green,red,darkblue,darkred
    for (i,j,num) in showNumList:
        drawNumber(i,j,num,colorList)

def drawNumber(i,j,num,colorList):
    text = font.render(str(num), True,colorList[num])#描画する文字列の設定
    screen.blit(text, [56+i*25, 52+j*25])# 文字列の表示位置

def showGameover(gameover):
    if gameover:
        black = (0,0,0)
        text = font.render("GameOver", True,black)#描画する文字列の設定
        screen.blit(text, (245,765))# 文字列の表示位置

def showAllBomb(gameover,bomb):
    if gameover:
        for i,iList in enumerate(bomb):
            for j,isbomb in enumerate(iList):
                if isbomb:
                    drawBomb(i,j)
    pass

def drawBomb(i,j):
    red = (255,0,0)
    pygame.draw.circle(screen, red,(63+i*25, 63+j*25), 10, width=0)
    pass

def showGameclear(gameclear):
    if gameclear:
        black = (0,0,0)
        text = font.render("GameClear", True,black)#描画する文字列の設定
        screen.blit(text, (245,765))# 文字列の表示位置

def getij(pos):
    x,y=pos
    return ((x-50)//25,(y-50)//25)

def checkij(i,j,clicked):
    if i<0 or i>19:
        return False
    if j<0 or j>27:
        return False
    return not clicked[i][j]

def showNearby0(i,j,numList,clicked,showNumList):
    for s in [-1,0,1]:
        for t in [-1,0,1]:
            if i+s<0 or i+s>19:
                continue
            if j+t<0 or j+t>27:
                continue
            if s==0 and t==0:
                continue
            if not clicked[i+s][j+t]:
                clicked[i+s][j+t]=True
                num=numList[i+s][j+t]
                if num==0:
                    showNearby0(i+s,j+t,numList,clicked,showNumList)
                else:
                    showNumList.append((i+s,j+t,num))
```

# 感想
毎回描画しなきゃいけないの大変すぎ。Unityってすごかったんだ...。あとこのレベルならささっと実装できるようになってて嬉しい。成長成長。