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;
|
||
}
|
||
}
|
||
}
|