Help us understand the problem. What is going on with this article?

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

0.最初に

今回作るものがどういう感じで動くのか見てみたい方は、こちら(youtubeの動画)でどうぞ。今回ここで解説することよりも動画の方がより深く解説することになると思います。

1.効果音とBGMのダウンロード

今回はフリーで提供されている効果音BGMを使いました。
また、pygameで効果音を使う場合はwavファイルでないといけないのでここでmp3からwavに変換しました。

2.処理を書く

全体のコードを載せると長くなるのでGithubでご覧ください。ここでは関数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でも解説しているのでそちらも良かったらご覧ください。質問等がございましたらその動画のコメント欄もしくは、この記事のコメント欄でどうぞ。また、いいなと思ったらチャンネル登録お願いします。

igor-bond16
主にPythonを使って電子工作、機械学習、webアプリ作成、GUI、ゲームなどを作ってYoutubeで発信してます。ブログもやってます。
https://www.youtube.com/channel/UCDYbu9aViDvkubFcwgbbKDA
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした