【Kivy】Pythonで落ちもの系ゲーム開発(テトリス風)Part4

落ちもの系ゲーム開発のぱーとよんを表すサムネイル

こんにちは、にわこまです。

今回は、ブロックの削除判定と実際に削除する関数を作成します。削除判定は普通のテトリスと違って、少し複雑です。また、ブロックを下げる関数も作成します。

 

誤字脱字など何かございましたらご連絡お願いいたします。

 

 

スポンサードサーチ


落ちもの系ゲーム開発Part4

落ちもの系ゲーム開発のぱーとよんを表す画像

Part1で紹介した、今回作成するゲームの概要を改めて説明します。

 

今回開発するゲームは、テトリスに似ています。テトリスのルールは、以下のような7つのミノと呼ばれるブロックを積み重ねていき、横一列をミノで満たしたらその行が消され得点します。

7種類のブロックの形

 

 

作るゲームの概要

テトリスではミノが消される条件は、「横一列にミノがいっぱいになったら」ですが、今回開発するゲームでは隣り合うブロックが10になったら消されるというものです。

ゆえに、ブロック1つひとつには1~9までの数字が代入されます。

以下のような場合、オレンジ色の線で囲まれたブロックが消されます。

削除されるブロックを示した画像

 

さらに、ブロックが消されたら、その列上で消されたブロックの座標より高い位置にあるブロックは消されたブロックの数だけ下がります。(左図から右図のようになります。)

ブロックが削除された後、どのように上のブロックが動作するかを示した画像

 

 

開発環境

OS : Windows10

Python Version : 3.7

Kivy Version : 1.11.1

Numpy Version : 1.18.1

 

 

開発手順

1.全てのミノに共通する関数を作成します。(共通の初期値、動作、関数)

2.7種類のミノ(Iミノ、JミノLミノ、Oミノ、Sミノ、Tミノ、Zミノ)を作成します。

3.ブロックの削除関係

4.kivyを使ってアプリケーションを開発します。

 

1、2、3は4のための準備であるため、簡単です。4は1番ボリュームがあります。

 

 

今回は、

tenplus.pyに記述するプログラムを完成させます。今回のPart4以降、tenplus.pyに追記するプログラムが無いようにします。

 

 

削除するブロックの座標を得る関数

削除するブロックの座標を取得する関数を表す画像

ミノを盤面に表示するときとは逆に、盤面からブロックを削除するために、削除するブロックの座標を抽出します。

 

以下は、削除するブロックの座標を得る関数のソースコードです。tenplus.pyに追記します。

# Number 10 plus
import numpy as np
import random


# 共通関数
class CommonFunc():
    # 省略


# Iミノ
class Imino(CommonFunc):
    # 省略

# Jミノ
class Jmino(CommonFunc):
    # 省略

# Lミノ
class Lmino(CommonFunc):
    # 省略

# Oミノ
class Omino(CommonFunc):
    # 省略

# Sミノ
class Smino(CommonFunc):
    # 省略

# Tミノ
class Tmino(CommonFunc):
    # 省略

# Zミノ
class Zmino(CommonFunc):
    # 省略


# 削除する座標を算出する関数
def getdelPosList(field):
    posList = list(zip(*np.where((field != 10) & (field != 0))))
    delPosList = []
    for pos in posList:
        for i in offset:
            npos = (pos[0] + i[0], pos[1] + i[1])
            if((field[pos] + field[npos]) == 10):
                delPosList.append(npos)
                delPosList.append(pos)
    delPosList = list(set(delPosList))
    return delPosList


# global varialbe
global offset
offset = [(-1, 0), (0, 1), (1, 0), (0, -1)]

# 以下省略

 

 

コードの解説

先に「offset」について説明します。

55、56行目でoffsetを定義しています。対象の座標の上下左右を指定するために使用します。

後述するgetdelPosListでしか利用しませんが、一応グローバル変数として定義しています。

オフセットの使い方を示した画像

 

41行目から51行目の「getdelPosList」が、削除するブロックの座標を取得する関数です。引数は、field(盤面)となります。返却値は、座標が代入されたリストとなります。

42行目の「posList」は、盤面から10と0以外が代入されている座標を取得しています。つまり、ブロックが存在している座標を全て取得しています。

44行目から49行目の「2つのfor文」は、対象の座標と上下左右で隣り合う座標のペアを総当たり的に指定するためのものです。

46行目の「npos」は、対象の座標の上下左右を順に指定しています。

47行目の「IF文」は、隣り合う座標の数字が足して10になることを判定しています。

48、49行目は、posnposを削除用のリストに追加しています。

50行目は、削除用のリストから重複を削除しています。

51行目の「return delPosList」は、削除する座標が格納されたリストを返却します。

 

 

動作確認

コマンドプロンプトを開き、プログラムが保存されているフォルダまで移動します。移動したら、以下のコマンドを入力し、インタプリタを起動します。

python

 

1.tenplus.pyから必要なものをインポートします。

>>> from tenplus import CommonFunc, field
>>> from tenplus import getdelPosList, offset

 

2.盤面に適当に数字を代入します。私は以下のように代入しました。

>>> field[(23, 1)] = 9
>>> field[(24, 1)] = 1
>>> field[(24, 2)] = 9
>>> field
array([[10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  9,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  1,  9,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]])

 

3.この状態でgetdelPosListを実行します。削除するブロックの座標が抽出されるか確認します。

>>> posList = getdelPosList(field)
>>> posList
[(24, 1), (24, 2), (23, 1)]

 

削除するブロックの座標が正しく抽出されていることを確認できたら動作確認は完了です。

いろんなパターンで試してみてください。

 

 

スポンサードサーチ


削除後のブロックを移動する関数

実際にブロックの削除とブロックの移動を行う関数を表す画像

前述した「getdelPosList」を使って、ブロックを削除することとその列の上のブロックを下げることを行います。イメージ図を以下に示します。

※オレンジ色で囲まれているブロックが削除されます。上下のブロックの位置関係に注目しください。空白は0、壁は10です。

ブロック削除のイメージ図

 

ブロック削除のイメージ図

 

以下は、ブロックの削除とその後のブロックの移動を行う関数のソースコードです。tenplus.pyに追記します。

# Number 10 plus
import numpy as np
import random


# 共通関数
class CommonFunc():
    # 省略


# Iミノ
class Imino(CommonFunc):
    # 省略

# Jミノ
class Jmino(CommonFunc):
    # 省略

# Lミノ
class Lmino(CommonFunc):
    # 省略

# Oミノ
class Omino(CommonFunc):
    # 省略

# Sミノ
class Smino(CommonFunc):
    # 省略

# Tミノ
class Tmino(CommonFunc):
    # 省略

# Zミノ
class Zmino(CommonFunc):
    # 省略


# 削除する座標を算出する関数
def getdelPosList(field):
    # 省略

# 実際に数字を下げる関数
def downNum(field, posList):
    downPosDict = {}    # col : [row1, row2]
    for pos in posList:
        row, col = pos[0], pos[1]
        if(col not in downPosDict):
            downPosDict[col] = [row]
        elif(col in downPosDict):
            downPosDict[col].append(row)
        else:
            print("downNum\nError : in / not in")
    for key in downPosDict.keys():
        temp = field[:, key]
        delList = sorted(downPosDict[key], reverse=True)
        zeroList = []
        for idx in delList:
            temp = np.delete(temp, idx)
            zeroList.append(0)
        temp = np.insert(temp, 0, zeroList)
        field[:, key] = temp
    pass

# 以下省略

 

 

コードの解説

45行目から64行目の「downNum」は、ブロックを削除することとその後のブロックを移動させる関数です。引数はfield(盤面)とposList(削除するブロックの座標→getdelPosListで取得したリスト)です。

46行目の「downPosDict」は、列をkeyに、行をvalueに持つ辞書です。実際にブロックを移動させるときに使用します。

例:{5:[24, 23]}
→5列目においては、24行目と23行目が削除されるということ

 

47行目から54行目の「for文」は、引数のposListを46行目の辞書「{col:[row1, row2, …]}」のような形に変化させています。

49行目の「IF文」は、downPosDictに特定の列が含まれていないことを判定する条件です。列がdownPosDictに含まれていない場合は、新しくvalueにリストを追加する必要があります。

50行目の「downPosDict[col] = [row]」は、downPosDictに新しくkeyに列を代入し、そのvalueに行のリストを代入しています。

51行目の「IF文」は、downPosDictに特定の列が含まれていることを判定する条件です。列がdownPosDictに含まれている場合は、valueのリストに行を追加します。

52行目の「downPosDict[col].append(row)」は、既に含まれている列の要素に行を追加しています。

 

55行目から63行目の「for文」は、ブロックの削除とブロックの移動を行っています。downPosDictのkeyに対して繰り返しを行っています。

56行目の「temp = field[:, key]」は、変数tempに盤面のkeyで指定された1列を代入しています。

56行目の処理を説明した画像

 

57行目の「delList = sorted(downPosDict[key], reverse=True)」は、変数delListにdownPosDict[key]で指定された行のリストを代入しています。その際、「reverse=True」によって降順になるように指定しています。

59行目から61行目の「for文」は、delListに代入された数字の内、大きい数字から順に繰り返し処理を行っています。

60行目の「temp = np.delete(temp, idx)」は、抽出した一列の内idxに当たる数字を削除し、新たに変数tempに代入しています。大きい数字から処理を行うため、後の処理においても整合性は保たれます。

66行目の処理を詳しく説明した図

 

61行目の「zeroList」は、数字を削除した分だけ0をリストに追加しています。

62行目の「temp = np.insert(temp, 0, zeroList)」は、配列tempの0番目にzeroListを追加し、変数tempに代入しています。tempにおける0番目とは、盤面における市場上の行のことを指します。

62行目の処理を図を使って説明

 

63行目の「field[:, key] = temp」は、削除と移動の処理が終わった配列を元の列に新しく代入しています。

 

 

動作確認

コマンドプロンプトを開き、プログラムを保存したフォルダまで移動します。移動したら以下のコマンドを入力し、インタプリタを実行します。

python

 

1.必要なものをtenplus.pyからインポートします。

>>> from tenplus import field, offset
>>> from tenplus import getdelPosList, downNum

 

2.盤面に適当に数字を代入します。私は以下のように代入しました。

>>> field[(22, 1)] = 6
>>> field[(23, 1)] = 9
>>> field[(24, 1)] = 1
>>> field[(24, 2)] = 9
>>> field
array([[10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  9,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  1,  9,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]])

 

3.getdelPosListを実行し、さらにdownNumを実行します。

>>> posList = getdelPosList(field)
>>> posList
[(24, 1), (24, 2), (23, 1)]
>>> downNum(field, posList)
>>> field
array([[10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10],
       [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]])

 

削除後のブロックの移動が正しく動作しているか確認できたら、動作確認は完了です。

 

 

終了を判定する関数

終了を判定する関数を表す画像

特定の座標にブロックが存在しているかを判定し、ゲーム自体の終了を見極めます。今回の26行12列の場合は、(4, 5)と(4, 6)の座標を対象としました。ミノの初期位置です。

 

以下は、ゲームの終了を判定するソースコードです。tenplus.pyに追記します。

# 上記省略

# 実際に数字を下げる関数
def downNum(field, posList):
    # 省略

def checkExitCond(field, pos):
    # check exit condition
    # fieldにおけるposにある数字がゼロであるとき
    # 終了していないときは、Falseを返す
    # 終了しているときは、Trueを返す
    num = field[pos]
    if(num == 0):
        return False
    return True

# 下記省略

 

 

コードの解説

7行目から15行目の「checkExitCond」は、終了を判定する関数です。引数は座標となります。引数は、field(盤面)とpos(座標)となります。終了している場合はTrueを、終了していない場合はFalseを返します。

12行目の「num = field[pos]」は、引数で指定された座標の数字をnumに代入しています。

13行目の「IF文」は、取り出された数字が0であるかを判定します。

14行目の「return False」は、終了されていないためFalseを返却します。

15行目の「return True」は、終了されているためTrueを返却します。

 

 

動作確認

コマンドプロンプトを開き、プログラムを保存したフォルダまで移動します。移動したら以下のコマンドを入力し、インタプリタを実行します。

python

 

1.tenplus.pyから必要なものをインポートします。

>>> from tenplus import field, checkExitCond

 

2.盤面に適当に数字を代入し、終了判定を実行します。

>>> field[(4, 5)] = 9
>>> checkExitCond(field, (4, 5))
True

 

指定した座標に数字があった場合は、Trueが返却され、ない場合はFalseが返却されることを確認できたら動作確認は完了です。

 

 

スポンサードサーチ


まとめ

まとめの画像

今回は、削除するブロックの座標を取得する関数と実際に数字を削除することと数字を下げることを行う関数と終了判定を行う関数を作成しました。

 

複雑な処理を行っている場合が多かったと思います。処理を理解しきれなかった場合は、異論は箇所にprint文を書き加えて、変数の型や構造を見るようにしましょう。

 

本記事で作成したコードを以下に示します。

 

 

最後までお読みいただきありがとうございます。


スポンサードサーチ