バトルシップ(BattleShip)というゲームをpygameとtkinterを使って作ってみた

#0.最初に

今回作るものがどういう感じで動くのか見てみたい方は、[こちら](https://youtu.be/jZ3F4kNnxM4)(youtubeの動画)でどうぞ。

#1.効果音とBGMのダウンロード
今回はフリーで提供されている[効果音](http://soundbible.com/)と[BGM](http://www.orangefreesounds.com/epic-battle-music/)を使いました。
また、pygameで効果音を使う場合はwavファイルでないといけないので[ここで](https://online-audio-converter.com/)mp3からwavに変換しました。

#2.処理を書く
全体のコードを載せると長くなるので[Github](https://github.com/igor-bond16/Battle_Ship_Game/blob/master/Battle%20Ship%20Complete/battle_ship_game.py)でご覧ください。ここでは関数1つづつ解説していきます。

まず今回使うライブラリーをインポートします。今回はマトリックスの操作のためにnumpyを使います。

```library.py
import pygame
from pygame.locals import *
import numpy as np
import tkinter as tk
import random
```
そしてこれはその名の通りフィールドを描く関数です。今回のフィールドは碁盤の目状になっています。ここではたいして難しいことはやっていないのでじっくり見てもらえば分かると思います。p2_fieldという関数もあるのですがやっていることはこの関数と同じです。

```battle_ship.py
def p1_field(win):
    rows = 10
    x = 55
    y = 55
    sizeBwn = 65

    for i in range(rows):
        x += sizeBwn
        y += sizeBwn
        pygame.draw.line(win,(0,0,0),(x,55),(x,705))
        pygame.draw.line(win,(0,0,0),(55,y),(705,y))

    pygame.draw.line(win,(0,0,0),(55,55),(55,705),5)
    pygame.draw.line(win,(0,0,0),(55,55),(705,55),5)
    pygame.draw.line(win,(0,0,0),(55,705),(705,705),5)
    pygame.draw.line(win,(0,0,0),(705,55),(705,705),5)
    
    drawnumbers(87.5,win)
    drawstring(87.5,win)
    font = pygame.font.SysFont('comicsens',100)
    text = font.render("Your Field",5,(0,0,255))
    win.blit(text,(380-text.get_width()/2,730))
```
これはさっきの関数で最後に呼び出されるものです。これは数字とアルファベットを描きますがゲームとはほとんど関係なく装飾のようなものです。

```battle_ship.py
def drawnumbers(pos,win):
    for b in num_list:
        font = pygame.font.SysFont('comicsens',25)
        text = font.render(b,1,(0,0,0))
        win.blit(text,(pos - (text.get_width()/2),5))
        pos += 65

def drawstring(pos,win):
    for i in st_list:
        font = pygame.font.SysFont('comicsens',25)
        text = font.render(i,1,(0,0,0))
        win.blit(text,(5,pos - (text.get_width()/2)))
        pos += 65
```
これは船の位置をしていするときに呼び出される関数です。今回は一番最初に呼び出されます。これは以前QiitaにTkinterで押したボタンの色だけを変える方法で紹介したものと同じものを使っています。シンプルなやり方を教えていただきましたが、船を描くときに上手く行かなかったのでそれは保留にさせていただきましす。

```battle_ship.py
def setting_ships():
    column = -1
    row = 0
    root = tk.Tk()
    root.title('Set Your Ships')
    root.geometry('470x310')
    for i in range(101):
        if i > 0:
            if i%10 == 1:
                row += 1 
                column = -1
            column += 1
            text=f'{i}'
            btn = tk.Button(root, text=text)
            btn.grid(column=column, row=row)
            btn.config(command=collback(btn,i))  
    root.mainloop()
    main()

def collback(btn,i):
    def nothing():
        btn.config(bg='#008000')
        numbers.append(i)
    return nothing
```
次に呼び出される関数はこれです。これはさっきのフィールドを描いてdraw_shipsという関数を呼び出します。

```battle_ship.py
def main():
    pygame.display.set_caption("battle ship")     
    win.fill((255,255,255)) 
    p1_field(win)
    p2_field(win)
    draw_ships(array_1,numbers)
    bomb_buttons()

    pygame.quit()

```
これがdraw_shipsです。ここに渡されるarrayと他の関数に渡すarrayは同じもので2次元配列になっています。ここではさっきTkinterで指定した船の位置をフィールド上に描きます。numbersというリストは押したボタンの番号が格納されています。

```battle_ship.py
array_1 = np.zeros([10,10],dtype=int)
array_2 = np.zeros([10,10],dtype=int)

def draw_ships(array, numbers):
    width = 65
    numbers = np.sort(numbers)
    for i in numbers:
        array.flat[i-1] = 1
    array = array.reshape(-1,1)
    for i in np.where(array == 1)[0]:
        index = i//10
        if i >= 10:
            columns = int(str(i)[1:])
        else:
            columns = i
        pygame.draw.rect(win,(0,0,0),(55+width*columns,55+width*index,width,width),0)
    pygame.display.update()
    ai_ships(array_2)
```
そして次に呼び出される関数はこれです。aiと書いていますがこれは単純にランダムに船を設置しているだけです。相手となるaiの船は見えてはいけないのでarrayに船の位置を格納するだけです。

```battle_ship.py
def ai_ships(array):
    num_5 = random.randint(0,5)
    num_4_1 = random.randint(8,9)
    num_4_2 = random.randint(3,6)
    num_3_1 = random.randint(3,6)
    num_3_2 = random.randint(5,7)
    num_2 = random.randint(2,6)
    num_1_1 = random.randint(0,2)
    num_1_2 = random.randint(5,9)
    if num_5 >= 7:
        array[num_5:num_5+5,0] = 1
    if num_5 <= 6:
        array[-5:,0] = 1
    array[num_3_1,num_3_2:num_3_2+3] = 1
    array[num_2,2:4] = 1
    array[num_4_1,num_4_2:num_4_2+4] = 1
    array[num_1_1,num_1_2] = 1
    array = array.reshape(-1,1)
    pygame.display.update()
```
そしてゲームが始まりまたボタンが表示されます。collbackにあるbombingという関数で当たったかどうかやその後の処理が行われます。

```battle_ship.py
def bomb_buttons():
    column = -1
    row = 0
    root = tk.Tk()
    root.title('bombing')
    root.geometry('470x310')
    for i in range(101):
        if i > 0:
            if i%10 == 1:
                row += 1 
                column = -1
            column += 1
            text=f'{i}'
            btn = tk.Button(root, text=text)
            btn.grid(column=column, row=row)
            btn.config(command=collback2(btn,i))      
    root.mainloop()

def collback2(btn,i):
    def nothing2():
        btn.config(bg='#008000')
        bombing(i,btn)
    return nothing2
```
これがそのbombingという関数です。ここで使うbombというリストには0〜99の値が格納されています。そして、指定した所に船があればボタンを赤くし効果音とともに赤いマルをフィールドに描きます。船がなかったら黒いマルが描かれます。そして自分のターンが終わったらaiのターンになります。どちらかの船が全滅したらゲームは終了し全滅させた方が勝者です。

```battle_ship.py
bomb = [i for i in range(100)]

def bombing(i,btn):
    global p1_counter,p2_counter
    font = pygame.font.SysFont('comicsens',100)
    i -= 1
    array = array_2.reshape(-1,1) 
    width = 65  
    index=i//10
    if i >= 10:
        columns = int(str(i)[1:])
    else:
        columns = i
    if array[i] == 1:
        pygame.draw.circle(win,(255,0,0),((730+columns*width)+width//2,(55+index*width)+width//2),width//2,0)
        bombed_sound.play()
        p1_counter += 1
        btn.config(bg='#FF0000')
    elif array[i] == 0:
        pygame.draw.circle(win,(0,0,0),((730+columns*width)+width//2,(55+index*width)+width//2),width//2,0)
        failed.play()
    if p1_counter == 15:
        pygame.mixer.music.stop()
        text = font.render('You Win!!',5,(255,0,0))
        win.blit(text,(750-text.get_width()/2,200))
        pygame.display.update()
        victory.play()

    num = random.choice(bomb)
    bomb.pop(bomb.index(num))
    array1 = array_1.reshape(-1,1)
    index=num//10
    if num >= 10:
        columns = int(str(num)[1:])
    else:
        columns = num
    if array1[num] == 1:
        pygame.draw.circle(win,(255,0,0),((55+columns*width)+width//2,(55+index*width)+width//2),width//2,0)
        p2_counter += 1
        bombed_sound.play()
    elif array1[num] == 0:
        pygame.draw.circle(win,(0,0,0),((55+columns*width)+width//2,(55+index*width)+width//2),width//2,0)
    if p2_counter == 15:
        pygame.mixer.music.stop()
        text = font.render('You Lose...',1,(0,0,255))
        win.blit(text,(750-text.get_width()/2,200))
        pygame.display.update()
        lose.play()

    pygame.display.update()
```

#最後に
このバトルシップゲームの作り方は[Youtube](https://youtu.be/jZ3F4kNnxM4)でも解説しているのでそちらも良かったらご覧ください。質問やアドバイスなどがあればぜひコメントしてください。