初心者の家(問題1)クリア(#4)

少々時間が掛かってしまったが、これまで作ったプログラムを組み合わせてみる。

  1. マップ認識
  2. ルート計算
  3. キー制御

問題1については、初期のマムルは無視して無理やり階段にたどり着いてもクリアは可能なので、今回はモンスターを認識する&戦うという概念を実装する必要はない。
1ターン1ターン、セレクトボタンを押して暗転というのも少々ダサいのではあるが、階段を目指して、1歩1歩確実にゴールにたどり着くことが出来た。

f:id:ai_shiren:20201219022419p:plain

なお、問題1の場合は初心者の家の娘が以下の場所で、邪魔なメッセージを出してくるので、それを送る必要がある。
当初は娘の画像マッチングをしていたが、ターンの開始時にBボタンを連打でも問題もないので、いったんはそれで。

f:id:ai_shiren:20201219023450p:plain ⇒ f:id:ai_shiren:20201219023458p:plain

実のところ4問目までは階段を目指すだけでクリアは可能であるが、モンスターを認識して倒すという概念は作っておきたいので、もう少し問題1をすることにする。
先はだいぶ長い。

キー入力の制御(#3)

いよいよシレンを操作するぞと意気込むものの、Windows系OSでプログラミングをしたことがないので、正直Windows APIが何なのか全然分かっていない。
Windowハンドルの取得や、入力の構造体などは大変苦労した。以下は自分へのメモ。

// ウィンドウハンドルの取得 (steamシレンのウィンドウは以下で取れる)
HWND hwnd = FindWindow(L"ShirenTheWandererWindowClass", nullptr);

// 入力の直前でウィンドウは再度アクティブにすること!
SetActiveWindow(hwnd);
SwitchToThisWindow(hwnd, true);
// INPUT構造体の設定
INPUT input;
input.type = INPUT_KEYBOARD;
input.ki.wVk = VK_RIGHT; // 右キー

input.ki.dwFlags = 0; // キーを押す
SendInput(1, &input, sizeof(INPUT)); 

sleep(70); // 要調整 (あまりにも入力を終えるのが早いと認識しないため)

input.ki.dwFlags = KEYEVENTF_KEYUP; // キーを離す
SendInput(1, &input, sizeof(INPUT));

ウィンドウ制御と入力構造体の仕組みが分かった所で、基本的な操作を今回で全て定義しておく。

void moveUp(void)       { sendKey(VK_UP); } // 上移動
void moveLeftUp(void)   { sendKey(static_cast<int>('W'), VK_LEFT, VK_UP);   } // 左上移動
void openMenu(void)     { sendKey(static_cast<int>('S')); } // メニューを開く
.....

 
ところで起動時のランチャーにはキー入力がざっくりと書かれているが、これを見る限り、Dボタンの足踏みや、Cボタンのマップ見渡しなどは書かれていない。
詳しくは取扱説明書を見てくださいとは書いてあるものの、結構大事な操作なので標準で書いてて欲しいと思った。

   f:id:ai_shiren:20201206193126p:plain

きちんとプログラム通りシレンが動いた所は感動的であったのだが(これがプログラミングの醍醐味!)、これを動画にしてアップする技量がないので、その辺はまた今度にしたい。
中々時間が取れないが、次回は前回やったマップ読み込みと、今回の操作を組み合わせて、ようやく初心者の家の問題1をクリアだ。

階段へのルートを計算する(#2)

シレンを階段に向かわせるという何気ない単純操作が、実はコンピュータにとっての最短経路問題という超難問でいきなり心が折られそうになる。
最短経路問題は大体の場合、ダイクストラ法のアルゴリズムを使っていれば何とかなる。(ここでは理屈も実装に関しても詳細は書かないのググってほしい。)

複雑な地形をしているダンジョンではデバッグがしにくいので、まずは簡単な以下の仮想ダンジョンを用意。5の位置から1を通って4に辿り着けばよい。

| 99999 | => {9( 0), 9( 1), 9( 2), 9( 3), 9( 4)} 
| 95949 | => {9( 5), 5( 6), 9( 7), 4( 8), 9( 9)} 
| 91919 | => {9(10), 1(11), 9(12), 1(13), 9(14)} 
| 91119 | => {9(15), 1(16), 1(17), 1(18), 9(19)} 
| 99999 | => {9(20), 9(21), 9(22), 9(23), 9(23)} 
# 9が壁、1が通路、5がシレンの位置、4が階段の意味、⇒の右側のカッコ内の数値は配列のn番目を意味。

誰もしないデバッグ記録。

 0 :
 1 :
 2 :
 3 :
 4 :
 5 :
 6 : 11
 7 :
 8 : 13
 9 :
10 :
11 : 6 16
12 :
13 : 8 18
14 :
15 :
16 : 11 17
17 : 16 18
18 : 13 17
19 :
20 :
21 :
22 :
23 :
24 :
# コロンで区切られたものは (現在の位置 : 行ける先の番号)の意味。※現段階では壁の中への侵入はできないものとする。

6番目の位置からスタートすると、11(下マス)に行ける事を示す。11の位置からは6(上マス)と16(下マス)に移動が出来る。
最終的には以下の通り無事、経路を得られた

結果: 6 => 11 => 16 => 17 => 18 => 13 => 8

 
そして初心者の家に戻って、問題1の前回得られたマップ情報に適用する。

99999999999999999999
99999999999999999999
99951119999111114999
99911119999111111999
99911112222161116999
99911119999111111999
99999999999999999999
99999999999999999999
99999999999999999999
99999999999999999999
結果: 43 => 44 => 65 => 86 => 87 => 88 => 89 => 90 => 91 => 72 => 53 => 54 => 55 => 56

f:id:ai_shiren:20201205204853p:plain

無事にたどり着くことが出来た!(机上の空論上)
しかし、実際にシレンを動かすと、部屋に侵入した場合にマムルが起きるので、この想定通りには操作が出来ない事が容易に想像できる。

 
※なお、斜め移動する先は、今の位置からみた隣に壁があると移動が出来ないので、その辺を考慮して実装する必要があった。

f:id:ai_shiren:20201205201422p:plain  f:id:ai_shiren:20201205201418p:plain

次回はようやくシレンを動かす所を実装しようと思う。

マップを把握する(#1)

風来のシレンを遊ぶための重要な要素は色々あるが、まずはマップをきちんと把握するという事は避けて通れない。
いきなりストーリーダンジョンに挑むのは大変なので、『初心者の家』で必要最低限の知識を身に着けてから行くことにする。まずは問題1の「次へ進むには階段へ」。

f:id:ai_shiren:20201205145110p:plain  f:id:ai_shiren:20201205145440p:plain

メモリは読まないと制約を課したので、マップをビジュアルから読み取る方法を試す。画像認識等も使う予定なので実装はOpenCVC++

f:id:ai_shiren:20201205150053p:plain

画面上に補助線を引く。 (マップのみの黒背景はセレクトボタンを押してマップ表示のみにしたもの)

f:id:ai_shiren:20201205150436p:plain

なかなか良い感じに分割が出来た。今後、このクロスで囲まれた部分を1マスの単位として扱うようにする。

今はとりあえず雑に1マスの中心部の色をピクセル単位で読み、そのマスがどういう状況なのかを判定する。
今の所は、壁(黒)/通路(紫)/シレン(白)/敵(赤)/部屋(青)/階段(水色四角)のみしかないので以下の通りになる。

if (マス == "黒") {
  return 壁
} else if (マス == "紫") {
  return 通路
} else if (マス == "白") {
  return シレン
} else if (マス == "赤") {
  return 敵
} else if (マス == "青") {
  if (角のマス == "水色")
    return 階段
  else
    return 部屋
}
// のちに味方やアイテムの判定も必要になる

読み取った結果を部屋情報の2次元配列として読み込めば、マップ情報の取得が完了という事になる。

00000000000000000000
00000000000000000000
00011110000111119000
00011110000111111000
00011112225161116000
00011110000111111000
00000000000000000000
00000000000000000000
00000000000000000000
00000000000000000000
# 0:壁/1:部屋/2:通路/5:シレン/6:敵/9:階段(数字は変わる予定)

6の敵(マムル)は置いといて、5のシレンが9の階段に続く道が計算できれば、この問題はクリアできると思います。
まだ一歩も進めておらず前途多難だ。

このブログについて

 このブログは風来のシレン5plusの発売をずっと楽しみに待っていたあるおっさんが、steam版のコントローラ認識不良で斜め移動が出来ない事と、フリーズにぶち切れて始めたブログです。

 風来のシレンをキーボードでプレイしてると、やっぱりだんだんと操作が面倒臭くなってきたので、操作の全てを自動化にすることを思いつきました。

 目標は原始クリアです。ファイルやメモリなどのリバースエンジニアリング行為は行わず、画像認識等をメインに人と同条件でプレイすることにします。

 おそらく途方もない時間と労力がかかると思いますし、実装上の課題や難易度で途中で飽きてしまうかもしれませんが、その辺温かく見守ってください。よろしくお願いします。

 ※風来のシレン5plusはvita版で何百時間とプレイ済みなので、その経験ぐらいは活かせるだろうと思います。