見出し画像

【Poke-Controller】BDSP化石掘り自動化プログラムの紹介(裏側編)

はじめに

このページは、Poke-Controllerを使ったBDSP化石掘り自動化プログラムの中身(プログラムの内容)について記載します。
関数ごとに書きますので、プログラムを改造する際の参考にしてください。
もっと効率的なプログラムが作れたらぜひ公開してください。

使い方の説明が知りたい方は、こちらの記事を参照ください。

用語の説明

記事中に出てくる言葉は、公式用語ではないかもしれません。
分かりにくそうな用語を補足します。

探検時:たんけんセットを使って地下に入った状態を指します

探検時のイメージ

発掘時:化石掘りをしている状態を指します
カーソル:発掘時に表示される赤枠を指します

発掘時のイメージ、赤枠はカーソルと呼びます

お宝:探検時にRボタンを押して光った場所を指します

主人公の右上で光っているものをお宝と呼びます

岩、石、砂、スカ、ハズレ、当たり:発掘時のアイコンを指します。

岩、石、砂:まだ掘れる状態、
スカ:掘った結果何もなかった状態、
ハズレ:掘った結果かたい岩だった状態
当たり:上記以外のアイテムがある状態


プログラムの中身

定義した関数ごとに中身を記載します。
上から下まで全て読む必要はなく、知りたい関数だけ読んでください。

_init_(self, cam):インスタンスの初期設定をする処理

ここに定義したものはどの関数でも使えるみたいです。
複数の関数にまたがって使う変数を定義しました。

self.chara = 'chara0.png';	###主人公が男の場合、'chara0.png'を'chara1.png'に修正

self.cursorx = 0
self.cursory = 0
self.atari = False
self.atarix = 0
self.atariy = 0
self.bkcursorx = 0
self.bkcursory = 0
self.digcount = 0
  • self.chara:主人公の帽子の画像を指定。男主人公は'chara1.png'に変更。

男主人公の帽子、正面の特徴がなさすぎる問題


  • self.cursorx : 発掘時のカーソル位置を指定。初期値は0(左上)。
    self.cursory  

画像、初期値0だとカーソルはこの位置(左上)
右へ進むとself.cursorxが1ずつ増え、下へ進むとself.cursoryが1ずつ増えるイメージです。
  • self.atari:通常はFalse。 発掘時、カーソル内に当たりがあるとTrue。 カーソル内の当たり有無はCheckFossil()で判定。

  • self.atarix使ってません!没になった処理の名残です。
    self.atariy  当たりの位置を格納して上手いこと掘ろうとしたのかも。

  • self.bkcursorx
    self.bkcursory:発掘時、「当たりを探す処理」と「当たり周辺を掘る処理」の間を行き来するため、カーソルの位置を保存します。関数名では、前者がDigRoutineV2()、後者がDigAtari()です。

  • self.digcount:発掘時、「当たり周辺を掘る処理」で発掘済の当たりか、未発掘の当たりかを判定するのに使用。1回掘るごとに値が1増加して、処理中に値が増えなければ発掘済と判断して処理を省略します。

  • この仕様はいまひとつ。座標対応させた二次元配列に発掘済かのフラグ立てて判定する方が効率良いです。(作成中、実際の座標と、プログラム上の座標が一致しなかったときの名残です。スティック操作でなく十字キーで操作できることが分かり、座標のずれ問題は解消しました。)

  • じゃんきー様、質問に回答いただきありがとうございました…!


do(self):メイン処理

メイン処理です。「地下へ潜り、お宝を探して移動、発掘するかお宝がなければ地上へ戻る」という処理を1,000回繰り返します。

self.initButton()
for i in range(1000):
	print('############### ',i,'回目 ###############')
	self.ExplorerStart()
	self.Exolore_hakutai()
	self.RepeatA()
	self.ExplorerEnd()

self.initButton():最初の1回だけ実行。Poke-Controller認識のためB連打
self.ExplorerStart():探検セットを使って地下へ
self.Exolore_hakutai():レーダーでお宝を探しながら右へ進む
self.RepeatA():探検時に戻るまでA連打(発掘時を終わらせるため)
self.ExplorerEnd():探検セットを使って地上へ


ExplorerStart(self):探検セットで地下へ潜る処理

探検セットを使って地下へ潜ります。

print('################## 探検開始 ##################')
self.press(Button.PLUS, 0.5, 2.0)
self.press(Button.A, 0.1, 0.5)
self.press(Hat.BTM, 0.15, 0.2)
self.press(Hat.BTM, 0.15, 0.2)
self.press(Button.A, 0.1, 1.0)
self.wait(5.0)

下記の操作をします。
+ボタン、Aボタン、十字キーの下、十字キーの下、Aボタン、5秒待機


ExplorerEnd(self):探検セットで地上へ戻る処理

探検セットを使って地上へ戻ります。

print('################## 探検終了 ##################')
self.press(Button.Y, 0.1, 2.5)
self.press(Hat.BTM, 0.15, 0.5)
self.press(Button.A, 0.1, 1.0)
self.press(Button.A, 0.1, 1.0)
self.wait(5.0)

下記の操作をします。
Yボタン、十字キーの下、Aボタン、Aボタン、5秒待機



Exolore_hakutai(self):お宝を探しながら右へ進む処理

「レーダーでお宝を探す処理」を呼びつつ右へ進み、お宝があれば「お宝の方向を確認する処理」を呼びます。関数名では、前者がRaderCheck()、後者がWhereFossil()です。

print('############### 探検画面モード ###############')
#self.camera.saveCapture()
#まずは少し右に移動してレーダー
self.press([Direction.RIGHT], 0.5, 0.1)
echeck = self.RaderCheck()
if echeck:
	self.WhereFossil(0)
	return
#移動ルートの定義(0:右、1:左、2:上、3:下)
list1 = [
	0,0,0,0,0,0,0
]
for i in range(len(list1)):
	print(i,'回目の移動')
	if list1[i] == 0:
		self.press([Direction.RIGHT], 1.0, 0.1)
	elif list1[i] == 3:
		self.press([Direction.DOWN], 1.0, 0.1)
	elif list1[i] == 2:
		self.press([Direction.UP], 1.0, 0.1)
	else:
		self.press([Direction.LEFT], 1.0, 0.1)
	#a = 0
	#while a < i:
	echeck = self.RaderCheck()
	if echeck:
		self.WhereFossil(list1[i])
		return
	#a += 1

最初に少し右へ移動してからRaderCheck()を呼びます。
今回は上下どちらかの壁にお宝があることを想定しているため、左の壁にお宝があった場合、上手く動きません。
左の壁あるお宝をレーダーで見つけないよう、少し右へ移動します。

その後は、list1に定義した番号に対応する方向へ移動します。(0:右、1:左、2:上、3:下)
list1の番号に対応する方向へ移動した後、RaderCheck()を呼ぶ」処理をlist1に定義した数字の数だけ繰り返します。
今回は右へ7回移動したいので、0を7回定義しました。

list1の数字を変えるだけで、自由に移動する方向を変更可能です。
ただし、上下方向へ移動して左右の壁にお宝があった場合、お宝の場所へ上手く移動できません。下記のプログラム修正が必要です。
1.MoveToFossilX()を参考に、MoveToFossilY()を作成
2.WhereFossil()内でposition <= 1の時はMoveToFossilX()、それ以外の時はMoveToFossilY()を呼ぶよう修正


initButton(self):1秒間B連打する処理

Poke-Controller認識のため1秒間、B連打します。

initime = time.time()
endtime = time.time()
while ((endtime - initime) < 1.0):
	self.press(Button.B, 0.1, 0.1)
	endtime = time.time()

1秒間、Bボタンを連打します。
Poke-Controllerをスイッチにコントローラーとして認識させるために必要です。


RaderCheck(self):レーダーでお宝を探す処理

Rボタンでレーダーを使った後、2秒間かけてお宝があるか画像認識で判別します。お宝があればTrue、なければFlaseを返します。

self.press(Button.R, 0.1, 0.3) # 化石サーチ
#反応チェック(光が瞬くので2秒間かけて観測)
initime = time.time()
endtime = time.time()
while not (self.isContainTemplate('BDSP_Fossil/shine.png', threshold=0.85,show_value=True)):
	self.wait(0.1)
	endtime = time.time()
	if (endtime - initime) > 2.0:
		break
if (endtime - initime) > 2.0:
	print('お宝反応なし')
	return False
else:
	print('お宝反応あり!')
	return True

お宝の光は動くため、1回の画像認識では判別困難です。

強く光ったり、弱くひかったり

そのため、2秒かけて画像認識を繰り返し、1度でも閾値を超えたらお宝あり、それ以外はお宝なしと判断しました。判別に使う画像は下記です。

お宝の判別に使う画像:shine.png

ただし、この画像認識で、地下の灯りをお宝の光と誤検知してしまうことがあります。なぜかハクタイシティでは起こりにくいですが、他のエリアだとよく起こります。
お宝の判別方法は改善の余地ありです。
(閾値を上げる?画像や処理を変える?)

地下の灯りをお宝と誤検知するため改善したいです
何か良い方法があれば教えてください


WhereFossil(self, position):お宝の方向を確認する処理

「お宝の位置を取得する処理」と「主人公の位置を取得する処理」を呼び、位置関係を把握した後、「お宝の方向へ移動する処理」を呼びます。
発掘時の画面に遷移したら「発掘して当たりを探す処理」を呼び、遷移しなければ処理を中断します。
関数名は順に、FossilPosition()CharaPosition()MoveToFossilX()DigRoutineV2()です。

引数のpositionは使っていません!没になった処理の名残です。Exolore_hakutai()で左右に移動していたか、上下に移動していたかを判別するために受け取ります。(左右と上下で主人公が移動する方向が変わります)
今回は右にしか移動しないため、判定をやめました。

num=0
fossiltmp = self.FossilPosition()
fossilxy = tuple(itertools.chain.from_iterable(fossiltmp))
#お宝位置の取得に失敗した場合は処理終了
if fossilxy[0] < 0:
	return
#print(fossilxy)
charatmp = self.CharaPosition()
charaxy = tuple(itertools.chain.from_iterable(charatmp))
#print(charaxy)
charanum = len(charaxy)//2
print('主人公:(',charaxy[0],', ',charaxy[1],')')
fossilnum = len(fossilxy)//2
#print('お宝:(',fossilxy[0],', ',fossilxy[1],')')
#print(fossilnum)
if charaxy[0] > fossilxy[0]:
	if charaxy[charanum] > fossilxy[fossilnum]:
		print('左上にお宝あり')
		#横方向へ移動中の場合
		#if position <= 1:
		self.MoveToFossilX(0)
	else:
		print('右上にお宝あり')
		self.MoveToFossilX(1)
else:
	if charaxy[charanum] > fossilxy[fossilnum]:
		print('左下にお宝あり')
		self.MoveToFossilX(2)
	else:
		print('右下にお宝あり')
		self.MoveToFossilX(3)
#発掘画面へ遷移するまで待つ
while not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
	self.press(Button.B, 0.1, 1.0)
	num += 1
	if num > 20:
		print('アラート:発掘画面に進めないため戻ります')
		return
self.wait(2.0)
self.DigRoutineV2()

FossilPosition()を呼び、お宝の座標が入った配列を受け取ります。
受け取ったお宝の座標が負の値の場合、座標取得に失敗しているため処理を中断します。
CharaPosition()を呼び、主人公の座標が入った配列を受け取ります。

お宝の座標と主人公の座標を比較して移動すべき方向を判断します。
(お宝、主人公の座標は、最初の値を使います)
移動すべき方向を引数にして、MoveToFossilX()を呼びます。

MoveToFossilX()の処理が終わると、発掘時の画面に遷移するまで、Bボタンを連打して待機します。Bボタンを20回押しても発掘時の画面に遷移しなければ処理を中断します。

発掘時の画面に遷移したら2秒間待機してからDigRoutineV2()を呼びます。
判別に使う画像は下記です。

発掘時かどうかの画像認識に使う画像:digtime.png

ピンとこないと思うので、全体の画像も貼ります。見つけてみよう。

画像認識に使う画像はどこにあるでしょうか

正解は右側の真ん中あたりでした。


CharaPosition(self):主人公の位置を取得する処理

画像認識で主人公の位置を取得して、その座標を返す処理です。

self.press(Direction.UP, 0.1, 0.3)
self.press(Direction.DOWN, 0.1, 0.3)
threshold = 0.88
temppath = './Template/BDSP_Fossil/'
template = cv2.imread(temppath + self.chara, 0)
h, w = template.shape[:2]
#パターンチェック
img = self.camera.readFrame()
img = np.asarray(img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(max_val)		#cv2.imwrite('testimg1.png',img)
#一致個所を格納
loc = np.where(res >= threshold)
print('主人公:', len(loc))
print('主人公の座標:', loc)
return loc

最初にスティックの上下で主人公を移動させ、正面を向くようにします。
(レーダーを使ってお宝の光を観測している間、主人公が背伸びをしたり動き出すので、それを止める効果もあります)

その後、画像認識をして主人公の位置を取得します。
画像認識は通常、isContainTemplate()を使いますが、この処理は戻り値がTrue/Falseのため、一致した座標を受け取ることができません。
そのため、同様の処理をプログラム内に記述します。
画面を取得して比較画像とモノクロで比較、閾値を超えた座標を配列に格納、最後に配列を返します。
判別に使う画像は下記です。

主人公位置判別の画像認識に使う画像


FossilPosition(self):お宝の位置を取得する処理

画像認識でお宝の位置を取得して、その座標を返す処理です。

temppath = './Template/BDSP_Fossil/'
template = cv2.imread(temppath + 'shine.png', cv2.IMREAD_COLOR)
h, w = template.shape[:2]
#閾値を超えるまでパターンチェック
threshold = 0.88
max_val = 0
initime = time.time()
endtime = time.time()
while max_val < threshold:
	img = self.camera.readFrame()
	img = np.asarray(img)
	#img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
	cv2.imwrite('testimg1.png',img)
	res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
	min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
	print(max_val)
	self.wait(0.05)
	endtime = time.time()
	if (endtime - initime) > 3.0:
		print('お宝座標の取得失敗:')
		loc = np.array([[-1],[-1]]) 
		return loc
#一致個所を格納
loc = np.where(res >= threshold)
#画像の保存
#cv2.imwrite('testimg1_after.png', img)
#print('お宝:', len(loc))
print('お宝の座標:', loc)
return loc

処理はCharaPosition()とほぼ同じです。共通部分の説明は割愛します。
違うのは、お宝座標の取得失敗時に、負の値を返す点です。
主人公は必ず画面内にいるので失敗しませんが、お宝はRaderCheck()の誤検知によって取得失敗することがあります。
取得の成功/失敗が分かるよう、失敗時には負の値を返すようにしました。

また、画像認識はモノクロはなくカラーにしました。
カラーの方が精度良い気がします。ただモノクロの方が処理は速いです。


MoveToFossilX(self, direction):お宝の方向へ移動する処理

引数に対応する方向へ、主人公を移動させる処理です。
対応する方向(0:左上、1:右上、2:左下、3:右下)へ、Aボタンを連打しながら移動します。
発掘時の画面に遷移すれば処理を終了、12秒経って発掘時の画面に遷移しない場合も処理を終了します。

moveflg = False
if direction == 0:
	#いったん右上に移動してから左方向へお宝を探す
	self.press([Direction.UP_RIGHT], 1.0, 0.01)
	initime = time.time()
	endtime = time.time()
	while not (self.isContainTemplate('BDSP_Fossil/pickel.png', threshold=0.85,show_value=True)):
		self.press([Direction.UP_LEFT], 0.3, 0.1)
		self.press([Button.A], 0.1, 0.01)
		endtime = time.time()
		if (endtime - initime) > 12.0:
			break
			
elif direction == 1:
	#いったん左上に移動してから右方向へお宝を探す
	self.press([Button.A, Direction.UP_LEFT], 1.0, 0.01)
	initime = time.time()
	endtime = time.time()
	while not (self.isContainTemplate('BDSP_Fossil/pickel.png', threshold=0.85,show_value=True)):
		self.press([Direction.UP_RIGHT], 0.3, 0.01)
		self.press([Button.A], 0.1, 0.01)
		endtime = time.time()
		if (endtime - initime) > 12.0:
			break
elif direction == 2:
	#いったん右下に移動してから左方向へお宝を探す
	self.press([Button.A, Direction.DOWN_RIGHT], 1.0, 0.1)
	initime = time.time()
	endtime = time.time()
	while not (self.isContainTemplate('BDSP_Fossil/pickel.png', threshold=0.85,show_value=True)):
		self.press([Direction.DOWN_LEFT], 0.3, 0.01)
		self.press([Button.A], 0.1, 0.01)
		endtime = time.time()
		if (endtime - initime) > 12.0:
			break
else:
	#いったん左下に移動してから右方向へお宝を探す
	self.press([Button.A, Direction.DOWN_LEFT], 1, 0.1)
	initime = time.time()
	endtime = time.time()
	while not (self.isContainTemplate('BDSP_Fossil/pickel.png', threshold=0.85,show_value=True)):
		self.press([Direction.DOWN_RIGHT], 0.3, 0.01)
		self.press([Button.A], 0.1, 0.01)
		endtime = time.time()
		if (endtime - initime) > 12.0:
			break

最初のmoveflg使ってません!
何に使う予定だったかも思い出せません。ミステリー。

WhereFossil()から受け取った引数に対応する方向へ移動します。
(0:左上、1:右上、2:左下、3:右下)

例:direction=0(主人公の左上にお宝がある場合)
最初に右上の方向に移動します。
その後、左上の方向へ移動しながらAボタンを連打します。
(真上にあるお宝を取り逃さないようにするため)

右上に移動してから左上を目指すことで、お宝が真上にあっても取り逃さない

移動は12秒かけて行い、発掘時の画面に遷移すれば処理を終了、
12秒経って発掘時の画面に遷移しなくても処理を終了します。
(画面遷移できない場合は、座標の取得が正しくできていないのかも)

発掘時の画面に遷移するかは下記の画像で判定します。

pickel.png

digtime.png」でも同じ判定ができるので、この画像は不要でした。
(最初の方に作った処理なのでわりと作りが適当です)


CheckFossil(self):カーソル位置に何があるかを判断する処理

発掘時、カーソル位置に何があるかを画像認識で判別して、対応する値を返す処理です。

self.atari = False
val = 0
val_num = -1
mythreshold = 0.87
TEMPLATEPATH = './Template/BDSP_Fossil/'
template_path = ['hori0_iwa.png','hori1_iwa_hibi.png','hori2_ishi.png',
				'hori3_ishi_hibi.png','hori4_suna.png','hori5_suna_hibi.png',
				'hori6_suka.png','hori7_hazure.png','hori8_atari1.png'
				]
template_length = len(template_path)
src = self.camera.readFrame()
#src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY)
for i in range(template_length):
	#template = cv2.imread(TEMPLATEPATH + template_path[i], 0)
	template = cv2.imread(TEMPLATEPATH + template_path[i], cv2.IMREAD_COLOR)
	res = cv2.matchTemplate(src, template, cv2.TM_CCOEFF_NORMED)
	_, max_val, _, max_loc = cv2.minMaxLoc(res)
	#print('画像番号:',i,'、最大値:',max_val)
	if max_val > val:
		val_num = i
		val = max_val
if  mythreshold > val:
	val_num = 8
#print('一番近い画像番号:',val_num,'、最大値:',val)
if val_num == 0:
	print('岩があります')
elif val_num == 1:
	print('ひび割れた岩があります')
elif val_num == 2:
	print('石があります')
elif val_num == 3:
	print('ひび割れた石があります')
elif val_num == 4:
	print('砂があります')
elif val_num == 5:
	print('ひび割れた砂があります')
elif val_num == 6:
	print('スカでした')
elif val_num == 7:
	print('ハズレでした')
else:
	print('当たりかも!')
	self.atari = True
return val_num

画像認識を使い、template_pathの中に定義した画像ファイルと比較して、最も類似度の高い画像ファイルの状態だと判断します。
なお、画像認識はカラーで行います。
(モノクロにすると、スカとハズレを誤認識することがあるため)
画像認識に使う画像は下記です。

岩、石、砂:まだ掘れる状態、
スカ:掘った結果何もなかった状態、
ハズレ:掘った結果かたい岩だった状態
当たり:上記以外のアイテムがある状態

岩~ハズレのどれにも当てはまらない場合、当たりと判断します。
(画像認識の結果がmythreshold = 0.87未満の場合)
この処理で判別可能なことが多いですが、化石のはしっこなど、当たりと判別できないアイテムもあります。

ひみつのコハクのはしっこ。スカと誤認識します。
bkフォルダに入っているので、認識したい場合は修正が必要。

判別できないアイテムを判別したい場合は、修正が必要です。
画像ファイルを追加して、template_pathにそのファイル名を追加してください。
ただし、画像ファイルが増えると処理が遅くなるため、必要最小限に絞った方が良いです。
今回は、なぞのかけらを取得したいので、「hori8_atari1.png」を追加して、なぞのかけら(L)を取り逃しにくくしました。

「hori8_atari1.png」なぞのかけら(L)の左上の部分


CursorZero(self):カーソルを左上へ移動する処理

発掘時、カーソルの位置を左上(原点)に移動する処理です。

self.press([Direction.UP_LEFT], 2.0, 0.2)
self.cursorx = 0
self.cursory = 0
print('現在位置:(',self.cursorx,', ',self.cursory,')')

スティックを左上に長押しして、カーソル位置を左上に戻します。
作成中、実際の座標と、プログラム上の座標が一致しなかったことがあり、この処理をはさむことで、ずれを最小限にしていました。
スティック操作でなく十字キーで操作できることが分かり、座標のずれ問題は解消したため、この処理は使わない方が早く動きます


CursorMove(self, x, y):カーソルを相対移動する処理

発掘時、カーソルの位置を現在位置から、引数で指定したx方向、y方向だけ相対移動します。
座標の範囲(0<=x<12、0<=y<9)を超えた移動はできないため、処理をスキップします。

print('現在位置:(',self.cursorx,', ',self.cursory,')')
print('x方向に',x,', y方向に',y,'、移動します)')
#x軸移動
if x > 0:
	while x > 0 and self.cursorx < 12:
		self.press(Hat.RIGHT, 0.14, 0.3)
		self.cursorx = self.cursorx + 1
		x -= 1
else:
	while x < 0 and self.cursorx > 0:
		self.press(Hat.LEFT, 0.13, 0.3)
		self.cursorx = self.cursorx - 1
		x = x + 1
#y軸移動
if y > 0:
	while y > 0 and self.cursory < 9:
		self.press(Hat.BTM, 0.15, 0.2)
		self.cursory = self.cursory + 1
		y -= 1
else:
	while y < 0 and self.cursory > 0:
		self.press(Hat.TOP, 0.15, 0.2)
		self.cursory = self.cursory - 1
		y += 1
print('現在位置:(',self.cursorx,', ',self.cursory,')')


Digging(self):1か所を最後まで掘る処理

カーソルのある場所を掘り続ける処理です。
スカ、ハズレ、当たりのどれかが出るまでAボタンを押して掘り続けます。

val_num = self.CheckFossil()
while val_num < 6:
	self.press(Button.A, 0.1, 1.0)
	self.digcount += 1
	val_num = self.CheckFossil()
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break

発掘時、カーソルの当たっている箇所でAボタンを1回押して掘ったあと、「カーソル位置に何があるかを判断する処理」を呼ぶ処理を繰り返します。
判断結果が岩、石、砂のまだ掘れる状態であれば処理を繰り返します。(最終的には、スカ、ハズレ、当たりのどれかの状態になるはず)
画像認識で発掘時の画面にいないと判断した場合、処理を中断します。
呼ぶ処理の関数名は、CheckFossil()です。

判別に使う画像は下記です。

発掘時かどうかの画像認識に使う画像:digtime.png

掘りすぎて壁が崩れると画面が暗転するため、発掘時の画面にいないと判断できます。


DigRoutineV1(self):(没)発掘して当たりを探す処理

使ってません!没になった処理です。
中心から外側へ、一定間隔で掘り進めます。蚊取り線香のように。

self.cursorx = 6
self.cursory = 4
list1 = [
	[2, 0],
	[0, 2],
	[-2, 0],
	[0, -2]
]
self.Digging()
#listの数
for j in range(len(list1)):
	print(j,'方向へ移動')
	a = 0
	while a < i:
		self.CursorMove(list1[j][0], list1[j][1])
		self.Digging()
		a += 1
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break
self.finish()

没になった処理のため説明は割愛。


DigRoutineV2(self):発掘して当たりを探す処理

発掘時、左上から右下へ蛇行しながら一定間隔で掘り進める処理です。

掘るルートに関しては、改良の余地がありそうです。
もっと良い移動ルートがあれば教えてください。

print('##################発掘画面モード##################')
self.CursorZero()
list1 = [
	[1, 1],
	[3, 0],
	[4, 0],
	[3, 0],
	[-2, 2],
	[-3, 0],
	[-3, 0],
	[-2, 2],
	[3, 0],
	[4, 0],
	[3, 0],
	[-2, 2],
	[-3, 0],
	[-3, 0],
	[-2, 1],
	[4, 0],
	[3, 0],
	[3, 0]
]
#listの数
for i in range(4, -1, -2):
	for j in range(len(list1)):
		print('探索:',j,)
		self.CursorMove(list1[j][0], list1[j][1])
		val_num = self.CheckFossil()
		if val_num >= i:
			self.Digging()
			if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
				return
			if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
				return
			if  self.atari:
				self.bkcursorx = self.cursorx
				self.bkcursory = self.cursory
				self.DigAtari()
				self.CursorZero()
				self.CursorMove(self.bkcursorx, self.bkcursory)
	self.CursorZero()
#ここまで掘っても終わらなければ強制終了

最初にCursorZero()でカーソルを左上に移動します。
次に、list1に定義した番号に対応する方向へ移動します。
例:1行目は[1, 1]なので、CursorMove(1,1)で現在地からx方向へ1、y方向へ1だけ移動します。
list1の数字を変えることで、移動する方向を変更可能です

list1の内容に沿って移動:左上から右下へ一定間隔で穴を掘る

移動後、CheckFossil()でカーソル位置の内容を確認します。
前述の判断基準(※)に従って掘るかどうかを判断し、掘る場合はDigging()でスカ、ハズレ、当たりのどれかになるまで掘り続けます。

(※)判断基準
1周目は1回で掘れる場所(砂)を掘ります。
2周目は2回で掘れる場所(石)を掘ります。
3周目は3回で掘れる場所(岩)を掘ります。

掘った結果が当たりの場合は、現在のカーソル位置をself.bkcursorxself.bkcursoryに保存した後、self.DigAtari()で当たりの周辺を掘ります。

掘ったあと、カーソル位置をself.bkcursorxself.bkcursoryに戻し、再びlist1に定義した番号に対応する方向へ移動する処理を繰り返します。

画像認識で発掘時の画面にいないと判断した場合、処理を中断します。
判別に使う画像は下記です。

発掘時かどうかの画像認識に使う画像:digtime.png
掘りすぎて壁が崩れたときの表示

掘りすぎて壁が崩れると画面が暗転するため、発掘時の画面にいないと判断します。

また、テキストウィンドウが表示された場合、処理を中断します。
判別に使う画像は下記です。

掘りつくした時の判定に使う画像:end.png
掘りつくした時の画面

掘りつくした場合、発掘時の画面にテキストウィンドウが表示されるため、ウィンドウの模様で判別にしました。これがあれば、「digtime.png」の画像認識は不要なのでは?


DigAtari(self):当たり周辺を掘る処理

当たり周辺を掘り、アイテムを発掘します。
考え方としては、アイテムの中心座標を探し、そこから上下左右の方向へアイテムがない場所まで掘り進めれば、アイテムを発掘できるだろうというものです。
しかし、この考え方では、ねっこのカセキは掘れません!
画像が手元にないので紹介できませんが、形が独特すぎます。

当たりを掘る処理に関しては、改良の余地がありそうです。
もっと良い移動方法があれば教えてください。

print('################ 当たり発掘モード ################')
self.digcount = 0
xr = [0, 0]
xl = [0, 0]
yu = [0, 0]
yd = [0, 0]
md = [0, 0]
len = 0
#x軸、左右を発掘
self.DigPX()
self.CursorMove(-1, 0)
xr[0] = self.cursorx
xr[1] = self.cursory
self.DigMX()
self.CursorMove(1, 0)
xl[0] = self.cursorx
xl[1] = self.cursory
#x軸の中心へ
md[0] = (xr[0] + xl[0])/2
self.CursorMove(md[0]-xl[0], 0)
#y軸、上下を発掘
self.DigMY()
self.CursorMove(0, 1)
yu[0] = self.cursorx
yu[1] = self.cursory
self.DigPY()
self.CursorMove(0, -1)
yd[0] = self.cursorx
yd[1] = self.cursory
#y軸の中心へ
md[1] = (yu[1] + yd[1])/2
self.CursorMove(0, md[1]-yd[1])
#2週目以降は既に掘ったあたり箇所はスルーする
if self.digcount == 0:
	return
#改めてx軸、左右を発掘
self.DigPX()
self.CursorMove(-1, 0)
xr[0] = self.cursorx
xr[1] = self.cursory
self.DigMX()
self.CursorMove(1, 0)
xl[0] = self.cursorx
xl[1] = self.cursory
#x軸の中心へ
md[0] = (xr[0] + xl[0])/2
#x軸とy軸を比べて大きい軸を中心に掘る
if (xr[0] - xl[0]) > (yd[1] - yu[1]):
	len = xr[0] - xl[0] + 1
	print('x軸で掘る:',len)
	self.CursorZero()
	self.CursorMove(xl[0]-1, md[1])
	for i in range(len):
		self.CursorMove(1, 0)
		self.DigMY()
		self.DigPY()
		self.CursorMove(0, -1)
		if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
			break
		if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
			break
else:
	len = yd[1] - yu[1] + 1
	print('y軸で掘る:',len)
	self.CursorZero()
	self.CursorMove(md[0], yu[1]-1)
	self.DigMX()
	self.DigPX()
	for i in range(len):
		self.CursorMove(0, 1)
		self.DigMX()
		self.DigPX()
		self.CursorMove(-1, 0)
		if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
			break
		if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
			break

「+x方向へ当たりが出なくなるまで掘り続ける処理」と「-x方向へ当たりが出なくなるまで掘り続ける処理」を呼び、当たりの左端と右端の座標を記録します。左端と右端のx座標の差から横幅を求めます。

当たりの横幅の中心に位置する座標へ「カーソルを相対移動する処理」で移動した後、「-y方向へ当たりが出なくなるまで掘り続ける処理」と「+y方向へ当たりが出なくなるまで掘り続ける処理」を呼び、当たりの上端と下端の座標を記録します。上端と下端のy座標の差から縦幅を求めます。

当たりの縦幅の中心に位置する座標へ「カーソルを相対移動する処理」で移動した後、改めて「+x方向へ当たりが出なくなるまで掘り続ける処理」と「-x方向へ当たりが出なくなるまで掘り続ける処理」を呼び、当たりの左端と右端の座標を記録します。左端と右端のx座標の差から横幅を求めます。

当たりの縦幅と横幅を比較して、大きい方の軸で移動しつつ、当たりが出なくなるまで掘り続けます。
例:縦幅>横幅の場合
座標(当たりの左端のx座標, 当たりの上端と下端のy座標の平均)に移動して、「-y方向へ当たりが出なくなるまで掘り続ける処理」と「+y方向へ当たりが出なくなるまで掘り続ける処理」を呼び、上下の端まで掘り進めます。
カーソルのx座標が当たりの右端のx座標になるまで、上下の端まで掘り進める処理を繰り返すします。

画像認識で発掘時の画面にいないと判断した場合、またはテキストウィンドウが表示された場合、処理を中断します。


DigPX(self):+x方向へ当たりが出なくなるまで掘り続ける処理

カーソルを1つ右へ移動した後、その場所を最後まで掘る処理を、当たりが出なくなるまで繰り返し実行します。
画像認識で発掘時の画面にいないと判断した場合、またはテキストウィンドウが表示された場合、処理を中断します。

#val_num = self.CheckFossil()
while self.cursorx < 12:
	self.CursorMove(1, 0)
	self.Digging()
	val_num = self.CheckFossil()
	if val_num < 8:
		break
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break
	if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
		break


DigMX(self):-x方向へ当たりが出なくなるまで掘り続ける処理

カーソルを1つ左へ移動した後、その場所を最後まで掘る処理を、当たりが出なくなるまで繰り返し実行します。
画像認識で発掘時の画面にいないと判断した場合、またはテキストウィンドウが表示された場合、処理を中断します。

while 0 < self.cursorx:
	self.CursorMove(-1, 0)
	self.Digging()
	val_num = self.CheckFossil()
	if val_num < 8:
		break
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break
	if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
		break


DigPY(self):+y方向へ当たりが出なくなるまで掘り続ける処理

カーソルを1つ下へ移動した後、その場所を最後まで掘る処理を、当たりが出なくなるまで繰り返し実行します。
画像認識で発掘時の画面にいないと判断した場合、またはテキストウィンドウが表示された場合、処理を中断します。

#val_num = self.CheckFossil()
while self.cursory < 9:
	self.CursorMove(0, 1)
	self.Digging()
	val_num = self.CheckFossil()
	if val_num < 8:
		break
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break
	if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
		break


DigMY(self):-y方向へ当たりが出なくなるまで掘り続ける処理

カーソルを1つ上へ移動した後、その場所を最後まで掘る処理を、当たりが出なくなるまで繰り返し実行します。
画像認識で発掘時の画面にいないと判断した場合、またはテキストウィンドウが表示された場合、処理を中断します。

#val_num = self.CheckFossil()
while 0 < self.cursory:
	self.CursorMove(0, -1)
	self.Digging()
	val_num = self.CheckFossil()
	if val_num < 8:
		break
	if not self.isContainTemplate('BDSP_Fossil/digtime.png', threshold=0.85,show_value=True):
		break
	if self.isContainTemplate('BDSP_Fossil/end.png', threshold=0.85,show_value=True):
		break


RepeatA(self):探索時の画面に戻るまでA連打する処理

探索時の画面が表示されるまで、A連打します。
探索時の画面が表示されると、2秒間待機します。
50回A連打しても探索時の画面が表示されない場合、異常終了します。

num = 0
while not self.isContainTemplate('BDSP_Fossil/under.png', threshold=0.85,show_value=True):
	self.press(Button.A, 0.1, 1.0)
	num += 1
	if num > 50:
		print('エラー:大洞窟画面に戻れません')
		self.finish()
self.wait(2.0)

判別に使う画像は下記です。

探索時の画面かを判断する画像:「under.png」

ピンとこないと思うので、全体の画像も貼ります。見つけてみよう。

画像認識に使う画像はどこにあるでしょうか

正解は左上の地図の隣あたりでした。


DigPXL(self):(没案)当たりを見つけてから当たりがなくなるまで掘り続ける処理(+x方向)

使ってません!没になった処理です。
カーソルを左端に移動させた後、カーソルが右端へ行くまで当たりを探し、当たりを見つけたらそこから当たりがなくなるまで右方向へ掘り続けます。
1行に複数の当たりがあった場合に掘り逃すので没にしました。

self.press(Direction.LEFT, 2.0, 0.2)
self.cursorx = 0
#self.CursorMove(1, 0)
val_num = self.CheckFossil()
#右端まで繰り返す
while self.cursorx < 12:
	if val_num == 8:
		spot = True
	else:
		spot = False
		#あたりの場所までカーソル移動
		while self.cursorx < 12:
			self.CursorMove(1, 0)
			val_num = self.CheckFossil()
			if val_num == 8:
				spot = True
				break
	#あたりがなくなるまで掘り続ける
	if spot:
		while self.cursorx < 12:
			self.CursorMove(1, 0)
			self.Digging()
			val_num = self.CheckFossil()
			if val_num < 8:
				break


改善ポイント

これまで記載した内容を踏まえて改善したいポイントをまとめました。

  • 探検時、地下の灯りをお宝と誤認識しないようにしたい

地下の灯りをお宝と誤検知するため改善したいです
何か良い方法があれば教えてください
  • 発掘時、より効率的にアイテムを掘れるようにしたい

画像認識の回数を減らしつつ、アイテムの掘り逃しを減らす方法がきっとあるはず


おわりに

自分でも引くくらい記事が長くなりました。
これを読んだあなたは、私よりもこのプログラムに詳しいです。
どうかプログラムの改良に役立ててください。

以上です。最後まで読んでくださりありがとうございました。

この記事が気に入ったらサポートをしてみませんか?