【Kivy】Pythonで迷路開発Part3

【Kivy】Pythonで迷路開発Part3を表すサムネイル

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

今回は、前回Part2で作成したlabyrinth03.pyの問題点とそれの解決方法を紹介します。今回で盤面生成のプログラムを完成させるため、問題点や解決方法が少し難しくなります。

 

誤字脱字や分からない点が、ございましたらご連絡お願いいたします。

 

 

スポンサードサーチ


【Kivy】Pythonで迷路開発Part3

Part3を表す画像

今回のシリーズは、2Dの迷路を開発することを目的としています。(プレイヤーを操作してスタートからゴールまでの時間を競います。)

 

今回のシリーズの主な内容

1.迷路の盤面を生成するプログラムの開発

2.Kivyで迷路アプリケーション開発

 

迷路の盤面を生成するプログラムでは、手書きで迷路を作るような処理を、プログラムに直していきます。

Kivyでの開発は、プレイヤーと壁との当たり判定に気を付けて開発を行います。

 

 

今回は

今回は、1つ目の迷路の盤面を作成するプログラムを作成します。Part2で作成したlabyrinth03.pyの問題とその解決方法を紹介します。

 

 

問題提起

問題提起を表す画像

問題は2つあります。

1つ目は、以下の図のように1本道の盤面が生成されてしまう可能性があることです。

1つ目の問題を表す画像

 

2つ目は、以下の図のようにゴールにたどり着く前に分岐をしすぎて結局ゴールにたどり着けなくなることです。

2つ目の問題を表す画像

 

 

解決策

1つ目の問題は、盤面生成が終わった後に分岐点を見つけて分岐路を作る関数を作成することで解決します。

2つ目の問題は、2つの場合に分けて問題を解決します。

1つ目の場合は下の左図のように、「ゴールの左上のマス(ピンクのマス)」と「ゴールの2つ上のマス(ピンクのマス)」に道が作成された場合、「ゴールの1つ左のマス(黄色のマス)」に道を作成し、盤面生成を終了します。

2つ目の場合は下の右図のように、「ゴールの左上のマス(ピンクのマス)」と「ゴールの2つ左のマス(ピンクのマス)」に道が作成された場合、「ゴールの1つ上のマス(黄色のマス)」に道を作成し、盤面生成を終了します。

2つ目の問題の解決策を表す画像

 

 

スポンサードサーチ


盤面生成4

盤面生成4を洗わず画像

labyrinth03.pyで発生していた問題を解決するプログラムを以下に示します。

 

labyrinth04.pyを作成し、以下のコードを書きこみます。

import numpy as np
import random


def nextCrossBlocks(field, pos, rowNext, colNext):
    offset = [(0, -1), (-1, 0), (0, 1), (1, 0)]
    pos = (pos[0] * (-1), pos[1] * (-1))
    offset.remove(pos)
    rowMax, colMax = np.shape(field)
    blocks = []
    for pos in offset:
        row = rowNext + pos[0]
        col = colNext + pos[1]
        if(row != 0 and row != rowMax-1 and col != 0 and col != colMax):
            blocks.append(field[(row, col)])
    return blocks

def findFork(field):
    posList = list(zip(*np.where(field == 1)))
    offset = [(0, -1), (-1, 0), (0, 1), (1, 0)]
    rowMax, colMax = np.shape(field)
    possible = []
    for point in posList:
        for pos in offset:
            row = point[0] + pos[0]
            col = point[1] + pos[1]
            block = field[(row, col)]
            if(row != 0 and row != rowMax-1 and col != 0 and col != colMax-1 and block == 0):
                blocks = nextCrossBlocks(field, pos, row, col)
                if(1 not in blocks):
                    possible.append(point)
                    break
    return possible

def makeLabyrinth(field):
    rowMax, colMax = np.shape(field)
    posGoal = list(zip(*np.where(field == 2)))[0]   # ゴールのマス
    posGoalUp = (posGoal[0] - 1, posGoal[1])        # ゴールの1つ上のマス
    posGoalLeft = (posGoal[0], posGoal[1] - 1)      # ゴールの1つ左のマス
    posGoalLT = (posGoal[0] - 1, posGoal[1] - 1)    # ゴールの左上のマス
    posGoalUp2 = (posGoal[0] - 2, posGoal[1])       # ゴールの2つ上のマス
    posGoalLeft2 = (posGoal[0], posGoal[1] - 2)     # ゴールの2つ左のマス
    rowCurrent, colCurrent = (1, 1)                 # スタートのマス
    offset = [(0, -1), (-1, 0), (0, 1), (1, 0)]
    while(True):
        possible = []
        for pos in offset:
            row = rowCurrent + pos[0]
            col = colCurrent + pos[1]
            block = field[(row, col)]
            if(row != 0 and row != rowMax-1 and col != 0 and col != colMax-1 and block == 0):
                blocks = nextCrossBlocks(field, pos, row, col)
                if(1 not in blocks):
                    possible.append((row, col))
        if(field[posGoalUp] == 1 or field[posGoalLeft] == 1):
            break
        if(len(possible) == 0):
            possible = findFork(field)
            pass
        r = random.randrange(len(possible))
        rowCurrent, colCurrent = possible[r]
        field[(rowCurrent, colCurrent)] = 1
        if(field[posGoalLT] == 1 and field[posGoalUp2] == 1):
            field[posGoalLeft] = 1
            break
        elif(field[posGoalLT] == 1 and field[posGoalLeft2] == 1):
            field[posGoalUp] = 1
            break
    pass

def findFork2(field):
    posList = list(zip(*np.where(field == 1)))
    offset = [(0, -1), (-1, 0), (0, 1), (1, 0)]
    rowMax, colMax = np.shape(field)
    possible = []
    for point in posList:
        for pos in offset:
            row = point[0] + pos[0]
            col = point[1] + pos[1]
            block = field[(row, col)]
            if(row != 0 and row != rowMax-1 and col != 0 and col != colMax-1 and block == 0):
                blocks = nextCrossBlocks(field, pos, row, col)
                if(1 not in blocks and 2 not in blocks):
                    possible.append(point)
                    break
    return possible

def makeFork(field):
    posGoal = list(zip(*np.where(field == 2)))[0]
    offset = [(0, -1), (-1, 0), (0, 1), (1, 0)]
    rowMax, colMax = np.shape(field)
    possible = findFork2(field)
    r = random.randrange(len(possible))
    rowCurrent, colCurrent = possible[r]
    count = 0
    while(True):
        possible = []
        for pos in offset:
            row = rowCurrent + pos[0]
            col = colCurrent + pos[1]
            block = field[(row, col)]
            if(row != 0 and row != rowMax-1 and col != 0 and col != colMax-1 and block == 0):
                blocks = nextCrossBlocks(field, pos, row, col)
                if(1 not in blocks and 2 not in blocks):
                    possible.append((row, col))
        if(len(possible) == 0):
            possible = findFork2(field)
            if(len(possible) == 0):
                break
            pass
        r = random.randrange(len(possible))
        rowCurrent, colCurrent = possible[r]
        field[(rowCurrent, colCurrent)] = 1
        count = count + 1
    pass


def makeField(field):
    makeLabyrinth(field)
    makeFork(field)
    return field


field01 = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 2, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

field02 = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

field03 = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0],
                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])


if __name__ == '__main__':
    makeField(field01)
    print(field01)

 

 

コードの解説

1、2行目の「import numpy as np」と「import random」は、盤面生成に必要なライブラリをインポートしています。

 

5行目~16行目の「nextCrossBlocks」は、道を作成する座標の周りの座標の状態を調べる関数です。(labyrinth03.pyと同じです。詳しい説明はPart1へ)

 

 

35行目~69行目の「makeLabyrinth」は、盤面を生成する関数です。

38行目~42行目の「posGoalUp」、「posGoalLeft」、「posGoalLT」、「posGoalUp2」、「posGoalLeft2」は、それぞれ「ゴールの1つ上」、「ゴールの1つ左」、「ゴールの左上」、「ゴールの2つ上」、「ゴールの2つ左」の座標を示しています。

55、56行目は、「ゴールの1つ上」か「ゴールの1つ左」に道が作成されたことを検出して、ループから抜け出します。

63行目~68行目は、labyrinth03.pyの2つ目の問題を解決するプログラムです。

 

71行目~86行目の「findFork2」は、分岐点を調べる関数です。後述するmakeFork(分岐路を作成する関数)で使われます。findForkとは1つ条件が異なります。

83行目の「if文」は、道とゴールが含まれていという条件です。分岐路を作成する関数です使われるためゴールも含まれないようにしています。

 

88行目~115行目の「makeFork」は、分岐路を作成する関数です。ほぼmakeLabyrinth関数と同じです。

92、93、94行目は、分岐点の中から現在の座標をランダムに指定しています。

104行目の「if文」は、道とゴールが含まれないという条件にしています。ゴールへの道はすでに作成されているため、ゴールにたどり着かない分岐路を作成したいためです。

106行目~110行目は、ループから抜け出すための条件です。

 

118行目~121行目の「makeField」は、盤面生成と分岐路生成を合わせた関数です。アプリケーションで使用します。

 

 

動作確認

正しく盤面と分岐路が生成されるか動作確認を行います。コマンドプロンプトを開き、ファイルを保存したフォルダまで移動します。移動したら以下のコマンドを入力し、実行します。

python labyrinth04.py

 

以下のように、ゴールまで道が生成されており、分岐路も生成されていれば、正しく動作しています。

[[0 0 0 0 0 0 0 0 0 0]
 [0 1 0 1 1 1 1 0 1 0]
 [0 1 0 1 0 0 1 0 1 0]
 [0 1 1 0 1 1 1 1 1 0]
 [0 0 1 1 1 0 0 0 1 0]
 [0 1 0 0 1 1 1 0 0 0]
 [0 1 1 1 1 0 1 1 2 0]
 [0 0 0 0 0 0 0 0 0 0]]

 

以上で動作確認は完了です。

 

 

まとめ

まとめの画像

今回は、labyrinth03.pyの問題点を解決するプログラムを紹介しました。また、盤面生成のプログラムを完成させました。

 

今回作成したコードを以下に示します。

 

いよいよ、次回からはアプリケーションの作成を始めます。

 

 

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


スポンサードサーチ