地図/探索/経路探索

全マスに波を広げる — 幅優先とフローフィールド

ゴールから波を広げると、壁を回り込んだ本当の距離が測れる。ゴールを 0 として、隣を 1、その隣を 2、と一段ずつ等距離の輪を太らせていく。各マスには最初に届いた波だけが残り、そのマスへの最短歩数になる。

幅優先探索(BFS, breadth-first search)。始点から近い順にマスを訪れる探索。訪問待ちのマスをキュー(先入れ先出し)に並べ、取り出したマスの未訪問の隣をキューの末尾に足す。これで同じ距離のマスが必ずまとめて処理され、各マスに最初に届く経路が辺の本数で見た最短になる。辺の重みがすべて等しいグラフでの最短経路がこれで求まる。

壁のない盤で波だけを見る。各マスに「ゴールまで何歩か」を覚える dist を持ち、ゴールを 0、未到達を -1 で埋めておく。front がいまの波面で、フレームごとにその外側を一段ぶん塗る。シミュレーションは N×N の格子配列で持ち、描画は盤面を cellW × cellH の矩形セルで全面に敷く。

中心から明るい四角の輪が太っていく。輪のかどが菱形なのは、上下左右の4方向にしか進まないから。横と縦の差を足したマンハッタン距離の菱形と、輪の形が一致する。壁がなければ BFS の距離はマンハッタン距離に重なる。

波が一段進むときは、波面の各マスから4つの隣を覗き、まだ塗っていないマスだけに「自分 + 1」を書き込む。

const ni = idx(nx, ny)
if (dist[ni] !== -1) continue // 先に塗られたマスには触れない
dist[ni] = dist[cur] + 1 // 親より1歩遠い
next.push(ni)

dist[ni] !== -1 で一度塗ったマスを二度塗らないので、各マスには最初に届いた波、最短の歩数だけが残る。隣の条件をひとつ足すと壁が効く。wall[ni] === 1 のマスを塞ぐと、波はそこへ入れず、開いた隙間だけを通って裏へ回り込む。

明るい輪が壁の角でぐにゃりと曲がりながら裏へ回り込む。塗り終えると、各マスの濃淡がそのまま「ゴールまで本当に何歩か」になる。wall の密度 0.22 を上げると通れる隙間が痩せ、波が細い廊下を伝う。

距離だけでなく「どの隣から来たか」も覚えておくと、各マスに「ゴールへ近づく向き」が一本ずつ焼ける。その向きを矢印として読むのがフローフィールド。

フローフィールド。ゴールから一度だけ BFS(または重みつきのダイクストラ)で全マスの距離場を作り、各マスから距離の小さい隣へ向く単位ベクトルを焼き込んだ地図。動く点はこの矢印を読むだけで壁を回り込んでゴールへ集まる。経路を点ごとに解き直さないので、ゴールが一つで動く点が大群のとき、一匹ずつの A スターより計算が安い。RTS の群衆移動で使われる。

距離場の各マスで、最も距離の小さい隣の方向を vx, vy に焼く。点はその場の矢印を読み、向きへ一歩進むだけで壁を避けてゴールへ寄っていく。

点の大群が同じ距離場を読みながら、壁を回り込んでゴールの一点へ流れ込む。経路を一匹ずつ解いてはおらず、全員が同じ一枚の矢印地図を共有している。wall の密度を上げて隙間を狭めても、距離場が裏の通路を通る向きを各マスに持っているので、点は袋小路に入らず迂回路へ吸い寄せられる。