ここに快適に収まるには少し長すぎる (187 行) C++ ソルバーを作成したので、代わりにペーストビンに入れました: http://pastebin.com/3G4dfTjR。プログラムは、最適な (最小可能な移動数) ソリューションを計算するか、不可能であると報告します。
使用法
プログラムをracetrack startX startY goalX goalY [circleX circleY radius]として実行します。
プログラムは、100x100 のグリッドを想定しています。このグリッドには、オプションで、中心と半径を指定した単一の円形障害物が含まれる場合があります。さらに、車の初期位置と 1 つのゴール位置を指定する必要があります。これらの制約は幾分制限的ですが、コードを見れば、一般にアルゴリズムを制限していないことが明らかになるはずです。関連するすべてのロジックはisMoveValid()
およびisGoalState()
ルーチンにカプセル化されているため、より一般的なバージョンのこれらのルーチン (たとえば、ユーザーがグリッド位置のビットマップを指定できるようにする、および/または複数のゴール位置を指定できるようにする) を使用すると、問題なく組み込むことができます。
わずかな複雑さは、ゴールの場所を開始場所と同じ (または近くにあるが、「反対側」) にすることです。これは、トラックをサーキットにしたい場合に必要なものです。この場合、ソルバーが単純に車を方向転換させたり、すぐに停止させたりするのを避けるために、目に見えない「スタート ライン」を指定し、isMoveValid()
このラインを横切る「後方」移動を禁止するように変更する必要があります。
使い方
各移動のコストは正確に 1 であるため、4D 状態空間で幅優先探索を使用して最適解を見つけることができます。dx と dy が (x, y) に到達するために使用した速度ベクトルである 4 タプル (x, y, dx, dy) で構成される特定の状態 s にアクセスするときはいつでも、9 つの状態すべてを考慮します。 s から一手で届く。まだ見られていないそのような状態 t について、BFS は常にルートからの最小距離の順にノードを訪問するため、t へのこのパス (つまり、s 経由) は最適であることが保証されます。状態の最適なパスを決定するたびに、前の状態を記録し、最後に完全なパスのトレースバックを生成できるようにします。
BFS はダイクストラのアルゴリズムや A* 検索よりも単純で、おそらく高速です。これらは、移動にさまざまなコストを持たせることができる、より一般的なアルゴリズムです。柔軟性はここでは必要ありません。ヒューリスティックを混乱させる障害がほとんどない場合、A* はより高速になる可能性がありますが、各ステップで最小コスト ノードを検索する必要があり、これは通常ヒープを使用して行われますが、BFS の場合、最小コスト ノードは常に行列の先頭。
例
ストップウォッチ競馬場 30 3 90 10
Starting at (30, 3).
Goal is (90, 10).
Grid size is 100*100 (W*H).
No obstacle.
11-step solution:
(90, 10) (dx=10, dy=4)
(80, 6) (dx=9, dy=3)
(71, 3) (dx=8, dy=2)
(63, 1) (dx=7, dy=1)
(56, 0) (dx=6, dy=0)
(50, 0) (dx=5, dy=0)
(45, 0) (dx=5, dy=0)
(40, 0) (dx=4, dy=0)
(36, 0) (dx=3, dy=-1)
(33, 1) (dx=2, dy=-1)
(31, 2) (dx=1, dy=-1)
(30, 3) (dx=0, dy=0)
128113 states were examined in the process.
stopwatch: Terminated. Elapsed time: 343ms
stopwatch: Process completed with exit code 0.
ストップウォッチ競馬場 30 3 90 10 50 20 25
Starting at (30, 3).
Goal is (90, 10).
Grid size is 100*100 (W*H).
A circular obstacle of radius 25 is centred at (50, 20).
22-step solution:
(90, 10) (dx=5, dy=-8)
(85, 18) (dx=5, dy=-7)
(80, 25) (dx=4, dy=-6)
(76, 31) (dx=4, dy=-5)
(72, 36) (dx=5, dy=-4)
(67, 40) (dx=6, dy=-3)
(61, 43) (dx=7, dy=-2)
(54, 45) (dx=8, dy=-1)
(46, 46) (dx=7, dy=0)
(39, 46) (dx=6, dy=1)
(33, 45) (dx=5, dy=2)
(28, 43) (dx=4, dy=3)
(24, 40) (dx=3, dy=4)
(21, 36) (dx=2, dy=5)
(19, 31) (dx=1, dy=6)
(18, 25) (dx=0, dy=6)
(18, 19) (dx=-1, dy=5)
(19, 14) (dx=-2, dy=4)
(21, 10) (dx=-3, dy=3)
(24, 7) (dx=-3, dy=2)
(27, 5) (dx=-2, dy=1)
(29, 4) (dx=-1, dy=1)
(30, 3) (dx=0, dy=0)
949565 states were examined in the process.
stopwatch: Terminated. Elapsed time: 3076ms
stopwatch: Process completed with exit code 0.
円形の障害物がグリッドの底をずっと越えて伸びているため、ここでの最適な解決策は、最初に「2 倍に戻る」必要があることに注意してください。
小さなバグ: ゴールの場所を最初の場所と同じに設定すると、投稿されたコードは短い (ただし長さがゼロではない!) 回答を返します。明らかに、これは特別なケースとしてチェックできますが、これに気付いたとき、私はすでにコードをペーストビンに入れていました... :)