1. 前文

先说SLG是什么,SLG=Simulation Game,策略类游戏。现特指回合制策略游戏以及即时SLG。有别于SIM(Simulation)类“生活“模拟游戏,SLG虽然也是缩写的simulation(模拟但与经营类意思不同),却是“战争策略“模拟游戏的总称。

而本文要说的是SLG游戏中的一种分类,国产手游中比较具有代表性的有:率土之滨、三国志战略版、宏图之下,由于我们是要介绍A*算法相关内容,所以我们贴几张关于战场的图,以方便我们有一个理解。

以下三个游戏由发行时间先后顺序展示:

  1. 率土之滨

  2. 三国志战略版

  1. 鸿图之下

那么有上述三个游戏,其沙盘布局如下:

传统的A*算法就是用率土之滨的数据结构,而随着沙盘游戏的不断发展地图的嵌入方式发生了变化,三国志战略版错开了50%,鸿图之下则采用正六边形的方式展示。

2. 演示代码准备

以下使用C#+WPF,VS2019进行的代码演示。

3. 深度优先和广度优先

深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中。

3.1 深度优先

/// <summary>
/// 深度优先
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnDFS_Click(object sender,RoutedEventArgs e)
{dicCache = new Dictionary<string, bool>();Tuple<int, int> pStartIndex = pStartShapeSquare.Tag as Tuple<int, int>;    DFS_WayFinding(pStartIndex.Item1, pStartIndex.Item2);
}private bool DFS_WayFinding(int index1, int index2)
{if (index1 < 0 || index1 >= CrosswiseNodeCount){return false;}if (index2 < 0 || index2 >= LengthwaysNodeCount){return false;}string strTag = $"{index1},{index2}";if (dicCache.ContainsKey(strTag)){return false;}else{dicCache.Add(strTag,true);}ShapeSquare shapeSquare = PlotShapeSquare[index1, index2];if (shapeSquare is ShapeSquare_BlockingPoint){return false;}else if (shapeSquare == pEndShapeSquare){return true;}shapeSquare.Fill = Brushes.BurlyWood;Thread.Sleep(10);System.Windows.Forms.Application.DoEvents();if (DFS_WayFinding(index1 - 1, index2)){return true;}else if (DFS_WayFinding(index1, index2 - 1)){return true;}else if (DFS_WayFinding(index1 + 1, index2)){return true;}else if (DFS_WayFinding(index1, index2 + 1)){return true;}return false;
}

3.2 广度优先

private void btnBFS_Click(object sender, RoutedEventArgs e)
{dicCache = new Dictionary<string, bool>();Tuple<int, int> pStartIndex = pStartShapeSquare.Tag as Tuple<int, int>;BFS_WayFinding(pStartIndex);
}/// <summary>/// 广度优先/// </summary>/// <param name=""></param>/// <returns></returns>
private bool BFS_WayFinding(Tuple<int, int> pIndex)
{Queue<Tuple<int, int>> BFSQueue = new Queue<Tuple<int, int>>();BFSQueue.Enqueue(pIndex);string strTag = $"{pIndex.Item1},{pIndex.Item2}";dicCache.Add(strTag, true);while (BFSQueue.Count!=0){Tuple<int, int> pShapeSquareIndex= BFSQueue.Dequeue();ShapeSquare shapeSquare = PlotShapeSquare[pShapeSquareIndex.Item1, pShapeSquareIndex.Item2]; if (shapeSquare == pEndShapeSquare){return true;}shapeSquare.Fill = Brushes.BurlyWood;Thread.Sleep(10);System.Windows.Forms.Application.DoEvents();if (IsRange(pShapeSquareIndex.Item1-1, pShapeSquareIndex.Item2)){BFSQueue.Enqueue(new Tuple<int, int>(pShapeSquareIndex.Item1 - 1, pShapeSquareIndex.Item2));}if (IsRange(pShapeSquareIndex.Item1 , pShapeSquareIndex.Item2 - 1)){BFSQueue.Enqueue(new Tuple<int, int>(pShapeSquareIndex.Item1, pShapeSquareIndex.Item2 - 1));}if (IsRange(pShapeSquareIndex.Item1 + 1, pShapeSquareIndex.Item2)){BFSQueue.Enqueue(new Tuple<int, int>(pShapeSquareIndex.Item1 + 1, pShapeSquareIndex.Item2));}if (IsRange(pShapeSquareIndex.Item1 , pShapeSquareIndex.Item2 + 1)){BFSQueue.Enqueue(new Tuple<int, int>(pShapeSquareIndex.Item1, pShapeSquareIndex.Item2 + 1));}}return false;
}private bool IsRange(int index1, int index2)
{if (index1 < 0 || index1 >= CrosswiseNodeCount){return false;}if (index2 < 0 || index2 >= LengthwaysNodeCount){return false;}ShapeSquare shapeSquare = PlotShapeSquare[index1, index2];if (shapeSquare is ShapeSquare_BlockingPoint){return false;}string strTag = $"{index1},{index2}";if (dicCache.ContainsKey(strTag)){return false;}else{dicCache.Add(strTag, true);}return true;
}

3.3 特点

如果深搜是一个人,那么他的性格一定倔得像头牛!他从一点出发去旅游,只朝着一个方向走,除非路断了,他绝不改变方向!除非四个方向全都不通或遇到终点,他绝不后退一步!因此,他的姐姐广搜总是嘲笑他,说他是个一根筋、不撞南墙不回头的家伙。

深搜很讨厌他姐姐的嘲笑,但又不想跟自己的亲姐姐闹矛盾,于是他决定给姐姐讲述自己旅途中的经历,来改善姐姐对他的看法。他成功了,而且只讲了一次。从那以后他姐姐不仅再没有嘲笑过他,而且连看他的眼神都充满了赞赏。他以为是自己路上的各种英勇征服了姐姐,但他不知道,其实另有原因……

深搜是这样跟姐姐讲的:关于旅行呢,我并不把目的地的风光放在第一位,而是更注重于沿路的风景,所以我不会去追求最短路,而是把所有能通向终点的路都走一遍。可是我并不知道往哪走能到达目的地,于是我只能每到一个地方,就向当地的人请教各个方向的道路情况。为了避免重复向别人问同一个方向,我就给自己规定:先问北,如果有路,那就往北走,到达下一个地方的时候就在执行此规定,如果往北不通,我就再问西,其次是南、东,要是这四个方向都不通或者抵达了终点,那我回到上一个地方,继续探索其他没去过的方向。我还要求自己要记住那些帮过他的人,但是那些给我帮倒忙的、让我白费力气的人,要忘记他们。有了这些规定之后,我就可以大胆的往前走了,既不用担心到不了不目的地,也不用担心重复走以前的路。

如果广搜是一个人,那么她一定很贪心,而且喜新厌旧!她从一点出发去旅游,先把与起点相邻的地方全部游览一遍,然后再把与她刚游览过的景点相邻的景点全都游览一边……一直这样,直至所有的景点都游览一遍。

3.4 讲解与备忘

上文中深度优先我们使用的是递归的方式而广度优先使用的是队列。而在网络上标准写法是深度优先采用的,广度优先采用的队列,两者采用两个数据结构的不同特点,保证遍历节点的优先性。比如的特点是后进先出,从而保证其每一次遍历到的子节点都先遍历。而队列的特点是先进先出,从而保证每一次遍历的子节点都在后面排队,而队列前方的先遍历,从而保证同级子节点的遍历的优先性

4. A星算法

书接上文,我们降到了深度优先和广度优先,上述两个算法都是穷举式算法。而(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是许多其他问题的常用启发式算法。A*算法作为Dijkstra算法(后续再用篇幅来阐述Dijkstra算法)的扩展,因其高效性而被广泛应用于寻路及图的遍历。

名词解释:

  • 直接搜索算法:直接在实际地图上进行搜索,不经过任何预处理;
  • 启发式算法:通过启发函数引导算法的搜索方向;
  • 静态图搜索算法:被搜索的图的权值不随时间变化(后被证明同样可以适用于动态图的搜索)。

公式表示为: f(n)=g(n)+h(n)

其中,

  • f(n) 是从初始状态经由状态n到目标状态的代价估计,
  • g(n) 是在状态空间中从初始状态到状态n的实际代价,
  • h(n) 是从状态n到目标状态的最佳路径的估计代价(欧几里(斜边的长度)/曼哈顿距离(x的距离+y的距离差))。

少逼逼,我们来分析算法。

结合上文,我们说过深度优先广度优先都是穷举算法而A星算法是属于启发式算法,那么是通过什么做到启发的呢!

鉴于视频比博客更加有代入感以及过程性,推荐大家看: https://www.bilibili.com/video/BV147411u7r5?from=search&seid=14263924862056244840

4.1 基本原理

A星寻路算法的基本原理就是不停的找自己周围的点,选出一个新的点作为起点再次循环

4.2 A星算法的详细原理

  1. 寻路消耗公式:

    f(寻路消耗)=g(离起点的距离)+h(离终点的距离)

g(离起点的距离):代表离起点的距离:

g ( n ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 g(n)=\sqrt{(x1-x2)^2+(y1-y2)^2} g(n)=(x1−x2)2+(y1−y2)2 ​

h(曼哈顿距离):图上数格子

h ( n ) = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ h(n)=|x1-x2|+|y1-y2| h(n)=∣x1−x2∣+∣y1−y2∣

  1. 开启列表:

    当前起点我们需要寻找的列表

  2. 关闭列表:

    已经寻找完毕的列表

  3. 格子对象的父对象:

    每一次寻找的格子节点的父对象,如F1的父对象为起点,F2的父对象为F1,以此类推。

    最后基于关闭列表中的集合,通过格子的父对象从节点开始逆推我们可以找到一条路径。

4.3 节选代码

using GraphBaseFramewark;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;namespace GraphAStarAlgorithm
{/// <summary>/// A星寻路算法/// </summary>class AStarAlgorithm{ShapeSquare[,] PlotShapeSquare = null;int CrosswiseNodeCount = 0;int LengthwaysNodeCount = 0;public AStarAlgorithm(ShapeSquare[,] plotShapeSquare, int crosswiseNodeCount,int lengthwaysNodeCount){PlotShapeSquare = plotShapeSquare;CrosswiseNodeCount = crosswiseNodeCount;LengthwaysNodeCount = lengthwaysNodeCount;}/// <summary>/// 关闭列表/// </summary>Dictionary<ShapeSquare, ShapeSquare> dicClose = new Dictionary<ShapeSquare, ShapeSquare>();/// <summary>/// 算法运行/// </summary>/// <param name="pStartShapeSquare"></param>/// <param name="pEndShapeSquare"></param>public void AlgorithmRun(ShapeSquare pStartShapeSquare, ShapeSquare pEndShapeSquare){Tuple<int, int> pStartIndex = pStartShapeSquare.Tag as Tuple<int, int>;dicClose.Add(pStartShapeSquare, null);FindWayInfo(pStartIndex, pStartShapeSquare, pEndShapeSquare);ShapeSquare pWayShapeSquare = pEndShapeSquare;while (pWayShapeSquare!= pStartShapeSquare){pWayShapeSquare.Fill = Brushes.YellowGreen;Thread.Sleep(10);System.Windows.Forms.Application.DoEvents();pWayShapeSquare = dicClose[pWayShapeSquare];}}private bool FindWayInfo(Tuple<int, int> pStartIndex, ShapeSquare pStartShapeSquare, ShapeSquare pEndShapeSquare){ConcurrentDictionary<ShapeSquare, double> dicOpen = new ConcurrentDictionary<ShapeSquare, double>();IsRange(pStartIndex.Item1-1, pStartIndex.Item2-1,1.4, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1-1, pStartIndex.Item2,1, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1-1, pStartIndex.Item2+1,1.4, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1, pStartIndex.Item2+1,1, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1+1, pStartIndex.Item2+1,1.4, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1+1, pStartIndex.Item2,1, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1+1, pStartIndex.Item2-1,1.4, pStartShapeSquare, dicOpen);IsRange(pStartIndex.Item1, pStartIndex.Item2-1,1, pStartShapeSquare, dicOpen);Tuple<int, int> pEndIndex = pEndShapeSquare.Tag as Tuple<int, int>;foreach (var item in dicOpen.Keys){Tuple<int, int>  pItemIndex = item.Tag as Tuple<int, int>;dicOpen[item] += Math.Abs((pEndIndex.Item1 - pItemIndex.Item1)) + Math.Abs((pEndIndex.Item2 - pItemIndex.Item2));if (item == pEndShapeSquare){return true;}}List<KeyValuePair<ShapeSquare, double>> listSortOpen =dicOpen.OrderBy(a => a.Value).ToList();foreach (var item in listSortOpen){Tuple<int, int> pIndex = item.Key.Tag as Tuple<int, int>;if (FindWayInfo(pIndex, item.Key, pEndShapeSquare)){return true;}}return false;}private bool IsRange(int index1, int index2,double dStartLength,ShapeSquare pStartShapeSquare, ConcurrentDictionary<ShapeSquare,double> dicOpen){if (index1 < 0 || index1 >= CrosswiseNodeCount){return false;}if (index2 < 0 || index2 >= LengthwaysNodeCount){return false;}ShapeSquare shapeSquare = PlotShapeSquare[index1, index2];if (shapeSquare is ShapeSquare_BlockingPoint){return false;}if (dicClose.ContainsKey(shapeSquare)){return false;}else{dicClose.Add(shapeSquare, pStartShapeSquare);dicOpen.TryAdd(shapeSquare, dStartLength);shapeSquare.Fill = Brushes.BurlyWood;Thread.Sleep(10);System.Windows.Forms.Application.DoEvents();}return true;}}
}

5 结语

由于这个算法是一个启发式算法,所以需要处理一些特殊情况,如周围点的f(n)相同时,顺序问题等等。

这个时候我们再回过头来看看国产SLG游戏中,三款游戏由时间顺序的发展,地图的变化,在算法上它在解决什么问题呢?

从国产SLG手游来说A星寻路算法相关推荐

  1. SLG手游Java服务器的设计与开发——架构分析

    微信公众号[程序员江湖] 作者黄小斜,斜杠青年,某985硕士,阿里 Java 研发工程师,于 2018 年秋招拿到 BAT 头条.网易.滴滴等 8 个大厂 offer,目前致力于分享这几年的学习经验. ...

  2. SLG手游Java服务器数据管理方案

    前言 这一年左右的时间,我参与并完成了一款SLG手游的研发,我负责游戏的服务端研发.这是一款以三国为题材的游戏,除了有三国名将的卡牌养成.多种多样装备养成.PVE,玩家竞技场等常见玩法外,我们的游戏的 ...

  3. 一些关于SLG手游的想法

    目前,市面上RPG手游产品已处于成熟的阶段,不管是游戏中的战斗模块还是其他玩法,在RPG手游中变化不大,因而同质化现象比较严重.相比之下,SLG手游还处于成长阶段,各个方面都还有很大的提升空间,比如战 ...

  4. 从《乱世王者》看腾讯SLG手游如何搭建完整安全服务

    WeTest 导读 <乱世王者>是由腾讯旗下天美工作室群自主研发的一款战争策略手游,在经历了2015年-2017年的SLG品类手游的爆发之势下,于2017年11月21日正式公测. < ...

  5. SLG手游的战斗抽象

    说明:本文仅讨论王国纪元.乱世王者这类的SLG手游战斗. 战争策略游戏的核心追求点 1. 玩家通过进行兵种选择和分配,能够战胜对方 2. 玩家通过进行兵种选择和分配,在战胜对方的基础上,以自身最小的损 ...

  6. 从《乱世王者》看腾讯 SLG 手游如何搭建完整安全服务

    作者:WeTest小编 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. 原文链接:https://wetest.qq.com/lab/view/453.html WeTest 导读 & ...

  7. 用Python从零复现A星寻路算法 | 手撕代码#1

    用Python从零复现A星寻路算法 |

  8. A星寻路算法(A* Search Algorithm)

    你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...

  9. 【解析】A星寻路算法介绍

    你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢? 如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它! 在网上已经有很多篇关于A星寻路算法 ...

最新文章

  1. python中and与or的执行顺序-python之执行顺序随记
  2. Java 线程池详解及实例代码
  3. mysql死锁查询_Mysql 查看死锁,解除死锁 方式
  4. 作者:纪珍(1982-),女,中国科学院国家空间科学中心副研究员
  5. Android 使用dagger2进行依赖注入(基础篇)
  6. 最新中烟新商盟JS逆向分析实战教程
  7. python爬虫怎么赚钱-利用Python爬虫轻松挣外快的几个方法(值得收藏)
  8. 代数结构在计算机科学中的应用,代数结构
  9. c语言字符串转16进制及16进制转字符串
  10. 如何下载免费版的PDF编辑器
  11. ​ZMC运动控制器SCARA机械手应用快速入门
  12. 如何在Apache-Maven官网下载到自己想要的版本
  13. 计算机考研英语一和英语二的区别,考研英语一和英语二的区别,考研党知道了吗?...
  14. [GDOI2016][树链剖分+主席树]疯狂动物城
  15. 手机客户端添加设备时需要扫描二维码,如何查找二维码
  16. Java-二维码生成与识别(二)
  17. 用PyTorch实现图像聚类
  18. 【水滴石穿】SpringBoot 集成Swagger
  19. 服务器里那个文件是地图的爆率,dnf数据芯片哪里出的多 dnf数据芯片爆率最高地图介绍...
  20. html特殊符号不显示,HTML特殊符号显示技巧

热门文章

  1. ios中html怎么横屏,iOS如何实现强制转屏、强制横屏和强制竖屏的实例代码
  2. 几款优质cms管理系统推荐
  3. Java 简单控制台项目之客户信息管理软件 --- 凌宸1642
  4. 做好提前准备,把面试当作一次友好的有意思的战斗
  5. flexble.js自适应布局
  6. 谈谈android中的内存泄漏
  7. 从零开始实现C++ TinyWebServer 全过程记录
  8. Ubuntu16.04版本下搜狗拼音打不出汉子
  9. 【月月复盘】2021年8月29日_8月份工作复盘
  10. cad中简单流程图制作,cad流程图制作教程