北大侠客行MUD论坛

 找回密码
 注册
搜索
热搜: 新手 wiki 升级
查看: 91|回复: 5

C#版本的地图路径规划代码

[复制链接]
发表于 昨天 04:32 PM | 显示全部楼层 |阅读模式
C#的,代码比较简单,没什么优化和花活,加了适当注释,供参考。

计算分为3个部分

1.mapfile,放所有的房间/出口/飞行的信息

2.context,游戏中的当前状态上下文,包括的出口标签匹配,临时房间,出口等等

3.option 移动的选项,可以限制移动的最大的消耗,避免计算过远的路径,或者禁用飞行信息
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 昨天 04:34 PM | 显示全部楼层
路径规划器,能计算路径,动态规划遍历,动态规划顺序移动

https://github.com/hellclient-sc ... r/Helpers/Mapper.cs

  1. using HellMapManager.Models;
  2. using System.Collections.Generic;
  3. // 路径规划模块
  4. namespace HellMapManager.Helpers;

  5. // 规划中的每一个步骤
  6. public class WalkingStep
  7. {
  8.     //从 出口信息.上一步,出发点,移动消耗,移动总消耗生成一个新的规划步骤
  9.     //prev是上一个步骤,为空说明是第一步
  10.     //from是移动起点,因为exit可以在多个房间复用,并不包含起点信息
  11.     //exit 具体的房间出口信息
  12.     //cost 这个步骤有多少移动消耗
  13.     //totalcost 总的移动消耗,如果消耗超过Context中最大的移动消耗,路径规划就会失败
  14.     public static WalkingStep FromExit(WalkingStep? prev, string from, Exit exit, int cost, int TotalCost)
  15.     {
  16.         return new WalkingStep()
  17.         {
  18.             Prev = prev,
  19.             From = from,
  20.             To = exit.To,
  21.             Command = exit.Command,
  22.             Cost = cost,
  23.             TotalCost = TotalCost + cost,
  24.             Remain = cost - 1,
  25.         };
  26.     }
  27.     public Step ToStep()
  28.     {
  29.         return new Step(Command, To, Cost);
  30.     }
  31.     //上一个步骤,为空说明是第一步
  32.     public WalkingStep? Prev { get; set; } = null;
  33.     public string From = "";
  34.     //移动起点,因为exit可以在多个房间复用,并不包含起点信息
  35.     public string To { get; set; } = "";
  36.     //目的地
  37.     public string Command { get; set; } = "";
  38.     //移动消耗
  39.     public int Cost { get; set; } = 0;
  40.     //总移动消耗
  41.     public int TotalCost { get; set; } = 0;
  42.     //剩余步数,每个计算回合-1,为0说明这一步移动完成。
  43.     public int Remain { get; set; } = 0;
  44. }

  45. //规划类
  46. public class Walking(Mapper mapper)
  47. {
  48.     //已经移动过的房间信息
  49.     private Dictionary Walked = new();
  50.     //对应的mapper
  51.     public Mapper Mapper { get; } = mapper;
  52.     //根据最后成功的最后一步移动(last),生成移动结果。targets为目标列表,会在去除移动目标并在结果里记录剩余目标
  53.     private static QueryResult BuildResult(WalkingStep last, List targets)
  54.     {
  55.         var result = new QueryResult();

  56.         WalkingStep current = last;
  57.         //没有prev说明是第一步
  58.         while (current.Prev is not null)
  59.         {
  60.             result.Steps.Add(current.ToStep());
  61.             current = current.Prev;
  62.         }
  63.         //第一步也要加入
  64.         result.Steps.Add(current.ToStep());
  65.         //逆向一下,最早的步骤应该在最前面
  66.         result.Steps.Reverse();
  67.         result.From = current.From;
  68.         result.To = last.To;
  69.         //统计剩余目标(移动支持多目标)
  70.         foreach (var target in targets)
  71.         {
  72.             if (target != result.To)
  73.             {
  74.                 result.Unvisited.Add(target);
  75.             }
  76.         }
  77.         //计算消耗
  78.         result.Cost = last.TotalCost;
  79.         return result;
  80.     }
  81.     //在多个起点/终点之间规划一个最近路线
  82.     //from 起点列表
  83.     //target 重点目标
  84.     //initTotalCost 初始移动总消耗,用于缺点多步逼近规划里限制总消耗
  85.     public QueryResult QueryPathAny(List from, List target, int initTotalCost)
  86.     {
  87.         from.RemoveAll(x => x == "");
  88.         target.RemoveAll(x => x == "");
  89.         if (from.Count == 0 || target.Count == 0)
  90.         {
  91.             return QueryResult.Fail;
  92.         }
  93.         Walked = new();
  94.         //从列表转字典
  95.         var targets = new Dictionary();
  96.         List current;
  97.         List pending = [];
  98.         foreach (var t in target)
  99.         {
  100.             targets[t] = true;
  101.         }
  102.         //起点准备
  103.         foreach (var f in from)
  104.         {
  105.             //终点包含起点则直接到达
  106.             if (targets.ContainsKey(f))
  107.             {
  108.                 var result = new QueryResult();
  109.                 result.From = f;
  110.                 result.To = f;
  111.                 result.Cost = initTotalCost;
  112.                 foreach (var to in target)
  113.                 {
  114.                     if (to != f)
  115.                     {
  116.                         result.Unvisited.Add(to);
  117.                     }
  118.                 }
  119.                 return result;
  120.             }
  121.             //标记起点
  122.             Walked[f] = new WalkingStep()
  123.             {
  124.                 From = "",
  125.                 Command = "",
  126.             };
  127.             //加入移动中列表
  128.             Mapper.AddRoomWalkingSteps(null, pending, f, initTotalCost);
  129.         }
  130.         //移动一轮
  131.         while (pending.Count > 0)
  132.         {
  133.             current = pending;
  134.             pending = [];
  135.             //循环每个待移动步骤
  136.             foreach (var step in current)
  137.             {
  138.                 //确定这个步骤还需要计算。
  139.                 //如果目标已经到达,则放弃
  140.                 if (!Walked.ContainsKey(step.To))
  141.                 {
  142.                     
  143.                     if (step.Remain <= 1)
  144.                     {
  145.                         //移动到达
  146.                         if (targets.ContainsKey(step.To))
  147.                         {
  148.                             //到达终点
  149.                             return BuildResult(step, target);
  150.                         }
  151.                         Walked[step.To] = step;
  152.                         //还没走完,延迟到下一轮判断
  153.                         Mapper.AddRoomWalkingSteps(step, pending, step.To, step.TotalCost);
  154.                     }
  155.                     else
  156.                     {
  157.                         //移动中
  158.                         step.Remain--;
  159.                         pending.Add(step);
  160.                     }
  161.                 }
  162.             }
  163.         }
  164.         //计算失败
  165.         return QueryResult.Fail;
  166.     }
  167.     //膨胀,在给定的房间边上膨胀iterations个房间
  168.     //一般用于npc会多步随机移动时,遍历目标和目标周边房间
  169.     public List Dilate(List src, int iterations)
  170.     {
  171.         Walked = new();
  172.         List current;
  173.         List pending = [];
  174.         foreach (var f in src)
  175.         {
  176.             //将有效房间加入待计算列表
  177.             if (Mapper.GetRoom(f) is not null)
  178.             {

  179.                 Walked[f] = new WalkingStep()
  180.                 {
  181.                     From = "",
  182.                     Command = "",
  183.                 };
  184.                 Mapper.AddRoomWalkingSteps(null, pending, f, 0);
  185.             }
  186.         }
  187.         var i = 0;
  188.         while (pending.Count > 0 && i < iterations)
  189.         {
  190.             current = pending;
  191.             pending = [];
  192.             foreach (var step in current)
  193.             {
  194.                 //未到达过的新房间加入下一步计算列表
  195.                 if (!Walked.ContainsKey(step.To))
  196.                 {
  197.                     Walked[step.To] = step;
  198.                     Mapper.AddRoomWalkingSteps(step, pending, step.To, step.TotalCost);
  199.                 }
  200.             }
  201.             i++;
  202.         }
  203.         return [.. Walked.Keys];
  204.     }
  205.     //通过模拟多步行走的方式,动态计算全遍历路径
  206.     public QueryResult QueryPathAll(string start, List target)
  207.     {
  208.         target.RemoveAll(x => x == "");
  209.         if (target.Count == 0 || start == "")
  210.         {
  211.             return QueryResult.Fail;
  212.         }
  213.         var result = new QueryResult
  214.         {
  215.             From = start,
  216.             To = start,
  217.         };
  218.         //未走到的房间列表
  219.         var pending = target;
  220.         //循环移动,直到全部走完
  221.         while (pending.Count > 0)
  222.         {
  223.             //计算单步
  224.             var next = QueryPathAny([result.To], pending, result.Cost);
  225.             if (next.IsSuccess())
  226.             {
  227.                 // 累积移动结果,循环下一轮
  228.                 result.Steps.AddRange(next.Steps);
  229.                 result.Cost = next.Cost;
  230.                 result.Unvisited = next.Unvisited;
  231.                 //更新新的计算列表
  232.                 pending = result.Unvisited;
  233.                 result.To = next.To;
  234.             }
  235.             else
  236.             {
  237.                 //移动失败。结束
  238.                 pending = [];
  239.             }
  240.         }
  241.         //如果一步也没动过,就是完全失败
  242.         if (result.Steps.Count == 0)
  243.         {
  244.             return QueryResult.Fail;
  245.         }
  246.         return result;
  247.     }
  248.     //通过模拟多步行走的方式,动态计算顺序路径
  249.     //targets为带顺序的房间列表
  250.     //如果有部分房间无法进入,会跳过无法进入的房间。
  251.     //用于在有某些限制条件的情况下,是指最新的上下文,然后尽可能多的遍历原路径中的房间
  252.     public QueryResult QueryPathOrdered(string start, List target)
  253.     {
  254.         target.RemoveAll(x => x == "");
  255.         if (target.Count == 0 || start == "")
  256.         {
  257.             return QueryResult.Fail;
  258.         }
  259.         var result = new QueryResult
  260.         {
  261.             From = start,
  262.             To = start,
  263.         };
  264.         for (var i = 0; i < target.Count; i++)
  265.         {
  266.             var next = QueryPathAny([result.To], [target[i]], result.Cost);
  267.             //成功则记录路径,不成功则跳过房间,将失败的房间加入Unvisited,继续计算
  268.             if (next.IsSuccess())
  269.             {
  270.                 result.Steps.AddRange(next.Steps);
  271.                 result.Cost = next.Cost;
  272.                 result.To = next.To;
  273.             }
  274.             else
  275.             {
  276.                 result.Unvisited.Add(target[i]);
  277.             }
  278.         }
  279.         //如果一步也没动过,就是完全失败
  280.         if (result.Steps.Count == 0)
  281.         {
  282.             return QueryResult.Fail;
  283.         }
  284.         return result;
  285.     }
  286. }

  287. //地图计算器
  288. //mapfile为地图信息
  289. //context为移动上下文,多个移动可能使用相同的上下文
  290. //options为移动选项,每次移动都可能不一样
  291. public class Mapper(MapFile mapFile, Context context, MapperOptions options)
  292. {
  293.     public Context Context { get; } = context;
  294.     public MapFile MapFile { get; } = mapFile;
  295.     public MapperOptions Options { get; } = options;
  296.     //通过key获取有效房间信息
  297.     public Room? GetRoom(string key)
  298.     {
  299.         if (!Context.Rooms.TryGetValue(key, out var room))
  300.         {
  301.             if (!MapFile.Cache.Rooms.TryGetValue(key, out room))
  302.             {
  303.                 return null;
  304.             }
  305.         }
  306.         return room;
  307.     }
  308.     //更具上下文,获取出口的移动消耗
  309.     public int GetExitCost(Exit exit)
  310.     {
  311.         //判断上下文中是否有单独的出口消耗定义覆盖原始值
  312.         if (Context.CommandCosts.TryGetValue(exit.Command, out var costs))
  313.         {
  314.             if (costs.TryGetValue(exit.To, out var cost))
  315.             {
  316.                 return cost;
  317.             }
  318.         }
  319.         return exit.Cost;
  320.     }
  321.     //获取房间出口信息
  322.     public List GetRoomExits(Room room)
  323.     {
  324.         List result = [.. room.Exits];
  325.         //加入上下文中的临时出口
  326.         if (Context.Paths.TryGetValue(room.Key, out var list))
  327.         {
  328.             result.AddRange(list);
  329.         }
  330.         //判断是否禁用捷径(飞行)出口
  331.         if (!Options.DisableShortcuts)
  332.         {
  333.             //原始捷径信息
  334.             MapFile.Map.Shortcuts.ForEach(e =>
  335.             {
  336.                 //符合条件则加入列表
  337.                 if (ValueTag.ValidteConditions(room.Tags, e.RoomConditions))
  338.                 {
  339.                     result.Add(e);
  340.                 }
  341.             });
  342.             //上下文的中的临时捷径
  343.             Context.Shortcuts.ForEach(e =>
  344.             {
  345.                 //符合条件则加入列表
  346.                 if (ValueTag.ValidteConditions(room.Tags, e.RoomConditions))
  347.                 {
  348.                     result.Add(e);
  349.                 }
  350.             });
  351.         }
  352.         return result;
  353.     }
  354.     //验证出口
  355.     //start为出发房间
  356.     //exit是出口信息
  357.     //cost为移动消耗
  358.     public bool ValidateExit(string start, Exit exit, int cost)
  359.     {
  360.         //判断房间有效
  361.         var room = GetRoom(exit.To);
  362.         if (room == null)
  363.         {
  364.             return false;
  365.         }
  366.         //验证房间符合当前移动的上下文设置
  367.         if (!ValidateRoom(room))
  368.         {
  369.             return false;
  370.         }
  371.         //判断房间不在上下文中的拦截名单里
  372.         if (Context.IsBlocked(start, exit.To))
  373.         {
  374.             return false;
  375.         }
  376.         //判断出口不超时
  377.         if (Options.MaxExitCost > 0 && cost > Options.MaxExitCost)
  378.         {
  379.             return false;
  380.         }
  381.         //判断出口不超过整个移动的最大消耗
  382.         if (Options.MaxTotalCost > 0 && cost > Options.MaxTotalCost)
  383.         {
  384.             return false;
  385.         }
  386.         //判断出口的条件是否匹配当前上下文
  387.         if (!Context.ValidteConditions(exit.Conditions))
  388.         {
  389.             return false;
  390.         }
  391.         return true;
  392.     }
  393.     //验证房间
  394.     public bool ValidateRoom(Room room)
  395.     {
  396.         //验证房间是否在黑名单
  397.         if (Context.Blacklist.ContainsKey(room.Key))
  398.         {
  399.             return false;
  400.         }
  401.         //验证如果启用白名单的话,房间是否在白名单内
  402.         if (Context.Whitelist.Count > 0 && !Context.Whitelist.ContainsKey(room.Key))
  403.         {
  404.             return false;
  405.         }
  406.         //验证房间的标签是否匹配上下文中的房间条件
  407.         if (!ValueTag.ValidteConditions(room.Tags, Context.RoomConditions))
  408.         {
  409.             return false;
  410.         }
  411.         return true;
  412.     }
  413.     //验证路径
  414.     public bool ValidatePath(string start, Exit exit)
  415.     {
  416.         //验证路径是否在黑名单
  417.         if (Context.IsBlocked(start, exit.To))
  418.         {
  419.             return false;
  420.         }
  421.         return ValidateExit(start, exit, GetExitCost(exit));
  422.     }
  423.     //验证并转换路径
  424.     //如果路径无效,返回空
  425.     public WalkingStep? ValidateToWalkingStep(WalkingStep? prev, string from, Exit exit, int TotalCost)
  426.     {
  427.         if (exit.To == "" || exit.To == from)
  428.         {
  429.             return null;
  430.         }
  431.         var cost = GetExitCost(exit);
  432.         //验证出口
  433.         if (!ValidateExit(from, exit, cost))
  434.         {
  435.             return null;
  436.         }
  437.         //判断最大消耗
  438.         if (Options.MaxTotalCost > 0 && Options.MaxTotalCost < (cost + TotalCost))
  439.         {
  440.             return null;
  441.         }
  442.         //转换
  443.         return WalkingStep.FromExit(prev, from, exit, cost, TotalCost);
  444.     }
  445.     public void AddRoomWalkingSteps(WalkingStep? prev, List list, string from, int TotalCost)
  446.     {
  447.         var room = GetRoom(from);
  448.         if (room is not null)
  449.         {
  450.             foreach (var exit in GetRoomExits(room))
  451.             {
  452.                 var step = ValidateToWalkingStep(prev, from, exit, TotalCost);
  453.                 if (step is not null)
  454.                 {
  455.                     list.Add(step);
  456.                 }
  457.             }
  458.         }
  459.     }
  460. }
复制代码


北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 昨天 04:35 PM | 显示全部楼层
移动上下文

游戏中和移动计算相关的动态信息

https://github.com/hellclient-sc ... r/Models/Context.cs

  1. public class Context
  2. {
  3.     //标签列表
  4.     public Dictionary Tags = [];
  5.     //房间条件列表
  6.     public List RoomConditions = [];
  7.     //临时房间
  8.     public Dictionary Rooms = [];
  9.     //白名单,为空忽略
  10.     public Dictionary Whitelist = [];
  11.     //黑名单
  12.     public Dictionary Blacklist = [];
  13.     //临时捷径列表
  14.     public List Shortcuts = [];
  15.     //临时出口列表
  16.     public Dictionary> Paths = [];
  17.     //拦截路径列表
  18.     public Dictionary> BlockedLinks = [];
  19.     //临时指令消耗列表
  20.     public Dictionary> CommandCosts = [];
  21. }
复制代码


北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 昨天 04:36 PM | 显示全部楼层
移动选项

单次计算移动的信息

https://github.com/hellclient-sc ... els/MapperOption.cs

  1. public class MapperOptions
  2. {
  3.     //最大单步消耗,为0不限制
  4.     public int MaxExitCost = 0;
  5.     //最大总消耗,为0不限制
  6.     public int MaxTotalCost = 0;
  7.     //是否禁用捷径
  8.     public bool DisableShortcuts = false;
  9. }
复制代码


北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 昨天 04:38 PM | 显示全部楼层
https://github.com/hellclient-scripts/hellmapmanager.ts

转写的ts版本的

https://github.com/hellclient-sc ... c/helpers/mapper.ts

https://github.com/hellclient-sc ... c/models/context.ts

https://github.com/hellclient-sc ... els/mapperoption.ts

通过npm能编译成js版本的和lua版本的

js版本的正在稳定性和功能测试

lua的……暂时没空和没脚本测试。
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
 楼主| 发表于 昨天 04:46 PM | 显示全部楼层
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|北大侠客行MUD ( 京ICP备16065414号-1 )

GMT+8, 2025-8-26 12:29 PM , Processed in 0.011297 second(s), 14 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表