本系列第四篇,展示優化前的 FindPath.cs 原始實作:使用 ushort 儲存距離標記,廣度優先搜尋 (BFS) 從目的地反向尋找工人,雙階段(標記+重建)設計。
namespace Skyiv.Ben.PushBox.Common
{
static class FindPath
{
static readonly Size[] Offsets = { new Size(0,1), new Size(1,0), new Size(0,-1), new Size(-1,0) };
static readonly Direction[] Dirs = { Direction.South, Direction.East, Direction.North, Direction.West };
// 公開介面:傳回移動方向佇列
public static Queue Seek(ushort[,] map, Point from, Point to)
{
var moveQueue = new Queue();
if (Seek(map, to, out int value)) // 階段一:標記
{
Point here = from;
// 階段二:依標記重建路徑
for (value--; value > 0; value--)
{
for (int i = 0; i < Offsets.Length; i++)
{
Point nbr = Fcl.Add(here, Offsets[i]);
if (Block.Value(map[nbr.Y, nbr.X]) == value)
{
moveQueue.Enqueue(Dirs[i]);
here = nbr;
break;
}
}
}
}
Block.CleanAllMark(map);
return moveQueue;
}
// 階段一:BFS 標記
static bool Seek(ushort[,] map, Point to, out int value)
{
var q = new Queue();
Block.Mark(ref map[to.Y, to.X], 1); // 目的地標記 1
Point nbr = Point.Empty;
for (;;)
{
value = Block.Value(map[to.Y, to.X]) + 1; // 下一層標記值 = 當前距離+1
for (int i = 0; i < Offsets.Length; i++)
{
nbr = Fcl.Add(to, Offsets[i]);
if (Block.IsMan(map[nbr.Y, nbr.X])) break; // 找到工人
if (Block.IsBlank(map[nbr.Y, nbr.X])) // 可通行
{
Block.Mark(ref map[nbr.Y, nbr.X], value);
q.Enqueue(nbr);
}
}
if (Block.IsMan(map[nbr.Y, nbr.X])) break; // 成功
if (q.Count == 0) return false; // 失敗
to = q.Dequeue(); // 廣度優先:取出下一層節點
}
return true;
}
}
}
to 開始,標記為 1。當前距離+1,入佇列。from 出發。目標標記-1 的方向,入佇列,移動至該鄰居。ushort = 16 bits,扣除 3 bits 狀態,餘 13 bits → 最大 8191 步。


原始實作邏輯清晰,但受限於 ushort 位元寬度。第 5 篇將展示如何透過標記值循環突破限制,並將地圖型別降為 byte。