214 lines
6.1 KiB
C#
214 lines
6.1 KiB
C#
using System.Collections.Generic;
|
||||
|
|
using System.Text;
|
|||
|
|
|
|||
|
|
namespace StandardScene.MagCarSimulator
|
|||
|
|
{
|
|||
|
|
public static class MapPathFinder
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 生成往返循环:起点→终点→起点(终点不重复,回到起点靠下标回绕)。
|
|||
|
|
/// </summary>
|
|||
|
|
public static List<int> BuildLoop(SimpleLiteMap map, int startSiteId, int endSiteId)
|
|||
|
|
{
|
|||
|
|
if (map == null)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException("尚未加载地图");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (startSiteId == endSiteId)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"起点和终点不能相同(站点 {startSiteId})");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!map.Sites.ContainsKey(startSiteId))
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"地图中没有起点 {startSiteId}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!map.Sites.ContainsKey(endSiteId))
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"地图中没有终点 {endSiteId}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var forward = FindPath(map, startSiteId, endSiteId);
|
|||
|
|
if (forward == null)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"找不到路径 {startSiteId} → {endSiteId}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var back = FindPath(map, endSiteId, startSiteId);
|
|||
|
|
if (back == null)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"找不到返回路径 {endSiteId} → {startSiteId}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var loop = new List<int>(forward.Count + back.Count);
|
|||
|
|
loop.AddRange(forward);
|
|||
|
|
for (var i = 1; i < back.Count - 1; i++)
|
|||
|
|
{
|
|||
|
|
loop.Add(back[i]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (loop.Count < 2)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException("循环路径至少需要两个站点");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return loop;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static List<int> NormalizeLoop(IEnumerable<int> siteIds)
|
|||
|
|
{
|
|||
|
|
var loop = new List<int>();
|
|||
|
|
if (siteIds == null)
|
|||
|
|
{
|
|||
|
|
return loop;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var id in siteIds)
|
|||
|
|
{
|
|||
|
|
if (id <= 0)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (loop.Count > 0 && loop[loop.Count - 1] == id)
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
loop.Add(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (loop.Count >= 2 && loop[0] == loop[loop.Count - 1])
|
|||
|
|
{
|
|||
|
|
loop.RemoveAt(loop.Count - 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return loop;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static List<int> ResolveLoop(SimpleLiteMap map, MagCarSimVehicleConfig config)
|
|||
|
|
{
|
|||
|
|
if (map == null)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException("尚未加载地图");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (config?.LoopSiteIds != null && config.LoopSiteIds.Count >= 3)
|
|||
|
|
{
|
|||
|
|
var loop = NormalizeLoop(config.LoopSiteIds);
|
|||
|
|
if (loop.Count < 3)
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"{config.Name} 环线站点不足");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var id in loop)
|
|||
|
|
{
|
|||
|
|
if (!map.Sites.ContainsKey(id))
|
|||
|
|
{
|
|||
|
|
throw new System.InvalidOperationException($"{config.Name} 环线站点 {id} 不在地图中");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return loop;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return BuildLoop(map, config.StartSiteId, config.EndSiteId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static string FormatPath(IReadOnlyList<int> siteIds, int maxShow = 0)
|
|||
|
|
{
|
|||
|
|
if (siteIds == null || siteIds.Count == 0)
|
|||
|
|
{
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var show = siteIds.Count;
|
|||
|
|
var truncated = false;
|
|||
|
|
if (maxShow > 0 && siteIds.Count > maxShow)
|
|||
|
|
{
|
|||
|
|
show = maxShow;
|
|||
|
|
truncated = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
for (var i = 0; i < show; i++)
|
|||
|
|
{
|
|||
|
|
if (i > 0)
|
|||
|
|
{
|
|||
|
|
sb.Append("→");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.Append(siteIds[i]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (truncated)
|
|||
|
|
{
|
|||
|
|
sb.Append("→…共").Append(siteIds.Count).Append("站");
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
sb.Append("→").Append(siteIds[0]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static List<int> FindPath(SimpleLiteMap map, int fromSiteId, int toSiteId)
|
|||
|
|
{
|
|||
|
|
if (fromSiteId == toSiteId)
|
|||
|
|
{
|
|||
|
|
return new List<int> { fromSiteId };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var queue = new Queue<int>();
|
|||
|
|
var prev = new Dictionary<int, int>();
|
|||
|
|
var visited = new HashSet<int> { fromSiteId };
|
|||
|
|
queue.Enqueue(fromSiteId);
|
|||
|
|
|
|||
|
|
while (queue.Count > 0)
|
|||
|
|
{
|
|||
|
|
var current = queue.Dequeue();
|
|||
|
|
if (!map.Adjacency.TryGetValue(current, out var nexts))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var next in nexts)
|
|||
|
|
{
|
|||
|
|
if (!visited.Add(next))
|
|||
|
|
{
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
prev[next] = current;
|
|||
|
|
if (next == toSiteId)
|
|||
|
|
{
|
|||
|
|
return Reconstruct(prev, fromSiteId, toSiteId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
queue.Enqueue(next);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static List<int> Reconstruct(Dictionary<int, int> prev, int fromSiteId, int toSiteId)
|
|||
|
|
{
|
|||
|
|
var path = new List<int>();
|
|||
|
|
var current = toSiteId;
|
|||
|
|
path.Add(current);
|
|||
|
|
while (current != fromSiteId)
|
|||
|
|
{
|
|||
|
|
current = prev[current];
|
|||
|
|
path.Add(current);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
path.Reverse();
|
|||
|
|
return path;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|