【Kivy】Pythonで迷路開発Part3
こんにちは、にわこまです。
今回は、前回Part2で作成したlabyrinth03.pyの問題点とそれの解決方法を紹介します。今回で盤面生成のプログラムを完成させるため、問題点や解決方法が少し難しくなります。
誤字脱字や分からない点が、ございましたらご連絡お願いいたします。
スポンサードサーチ
【Kivy】Pythonで迷路開発Part3
今回のシリーズは、2Dの迷路を開発することを目的としています。(プレイヤーを操作してスタートからゴールまでの時間を競います。)
今回のシリーズの主な内容
1.迷路の盤面を生成するプログラムの開発
2.Kivyで迷路アプリケーション開発
迷路の盤面を生成するプログラムでは、手書きで迷路を作るような処理を、プログラムに直していきます。
Kivyでの開発は、プレイヤーと壁との当たり判定に気を付けて開発を行います。
今回は
今回は、1つ目の迷路の盤面を作成するプログラムを作成します。Part2で作成したlabyrinth03.pyの問題とその解決方法を紹介します。
問題提起
問題は2つあります。
1つ目は、以下の図のように1本道の盤面が生成されてしまう可能性があることです。
2つ目は、以下の図のようにゴールにたどり着く前に分岐をしすぎて結局ゴールにたどり着けなくなることです。
解決策
1つ目の問題は、盤面生成が終わった後に分岐点を見つけて分岐路を作る関数を作成することで解決します。
2つ目の問題は、2つの場合に分けて問題を解決します。
1つ目の場合は下の左図のように、「ゴールの左上のマス(ピンクのマス)」と「ゴールの2つ上のマス(ピンクのマス)」に道が作成された場合、「ゴールの1つ左のマス(黄色のマス)」に道を作成し、盤面生成を終了します。
2つ目の場合は下の右図のように、「ゴールの左上のマス(ピンクのマス)」と「ゴールの2つ左のマス(ピンクのマス)」に道が作成された場合、「ゴールの1つ上のマス(黄色のマス)」に道を作成し、盤面生成を終了します。
スポンサードサーチ
盤面生成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へ)
18行目~33行目の「findFork」は、分岐点を探すための関数です。後述するmakeLabyrinth関数で使われます。(labyrinth03.pyと同じです。詳しい説明はPart2へ)
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の問題点を解決するプログラムを紹介しました。また、盤面生成のプログラムを完成させました。
今回作成したコードを以下に示します。
【Kivy】Pythonで迷路開発Part3 コード
いよいよ、次回からはアプリケーションの作成を始めます。
最後までお読みいただきありがとうございます。
スポンサードサーチ