在塔防游戏中,有许多敌人向着同一目标前进。在很多塔防游戏当中,有一条或几条事先预定好的路径。在一些中,比如经典的《Desktop Tower Defense》,你可以将塔放在任何位置,它们充当障碍影响敌人选择的路径。试一试,点击地图来移动墙壁:

我们如何来实现这种效果?

像A*这样的图搜索算法经常被用来寻找两点之间的最短路径。你可以用这个来为每一个敌人找到前往目标的路径。在这种类型的游戏当中,我们有很多不同的图搜索算法来。这是一些经典方法

单源,单目标:

单源多目标或多源单目标

多源多目标

像《Desktop Tower Defense》这样的游戏会有很多个敌人(源)和一个共同的目的地。这使得它被归为多源单目标一类。我们可以执行一个算法,一次算出所有敌人的路径,而不是为每个敌人执行一次A*算法。更好的是,我们可以计算出每个位置的最短路径,所以当敌人挤在一块或者新敌人被创建时,他们的路径已经被计算好了。

我们先来看看有时也被称作“洪水填充法”(FIFO变种)的广度优先算法。虽然图搜索算法是适用于任何由节点和边构成的图,但是我还是使用方形网格来表示这些例子。网格是图的一个特例。每个网格瓦片是图节点,网格瓷砖之间的边界是图的边。我会在另一篇文章当中探讨非网格图。

广度优先搜索始于一个节点,并访问邻居节点。关键的概念是“边界”,在已探索和未开发的区域之间的边界。边界从原始节点向外扩展,直到探索了整张图。

边界队列是一个图节点(网格瓦片)是否需要被分析的列表/数组。它最开始仅仅包含一个元素,起始节点。每个节点上的访问标志追踪我们是否采访过该节点。开始的时候除了起始节点都标志为FALSE。使用滑块来查看边界是如何扩展的:

这个算法是如何工作的?在每一步,获得一个元素的边界并把它命名为current。然后寻找current的每个邻居,next。如果他们还没有被访问过的话,将他们都添加到边界队列里面。下面是一些python代码:

frontier = Queue()
frontier.put(start)
visited = {}
visited[start] = True
 
while not frontier.empty():
   current = frontier.get()
   for next in graph.neighbors(current):
      if next not in visited:
         frontier.put(next)
         visited[next] = True

现在已经看见代码了,试着步进上面的动画。注意边界队列,关于current的代码,还有next节点的集合。在每一步,有一个边界元素成为current节点,它的邻居节点会被标注,并且未被拜访过的邻居节点会被添加到边界队列。有一些邻居节点可能已经被访问过,他们就不需要被添加到边界队列里面了。

这是一个相对简单的算法,并且对于包括AI在内的很多事情都是有用的。我有三种主要使用它的办法:

1.标识所有可达的点。这在你的图不是完全连接的,并且想知道哪些点是可达的时候是很有用的。这就是我再上面用visited这部分所做的。 2.寻找从一个点到所有其他点或者所有点到一个点的路径。我在文章开始部分的动画demo里面使用了它。 3.测量从一个点到所有其他点的距离。这在想知道一个移动中的怪物的距离时是很有用的。

如果你正在生成路径,你可能会想知道从每个点移动的方向。当你访问一个邻居节点的时候,要记得你是从哪个节点过来的。让我们把visited重命名为came_from并且用它来保存之前位置的轨迹:

frontier = Queue()
frontier.put(start)
came_from = {}
came_from[start] = None
 
while not frontier.empty():
   current = frontier.get()
   for next in graph.neighbors(current):
      if next not in came_from:
         frontier.put(next)
         came_from[next] = current

我们来看看它看起来是怎样的:

如果你需要距离,你可以在起始节点讲一个计数器设置为0,并在每次访问邻居节点的时候将它加一。让我们把visitd重命名为distance,并且用它来存储一个计数器:

frontier = Queue()
frontier.put(start)
distance = {}
distance[start] = 0
 
while not frontier.empty():
   current = frontier.get()
   for next in graph.neighbors(current):
      if next not in distance:
         frontier.put(next)
         distance[next] = 1 + distance[current]

我们来看看它看起来是怎样的:

如果你想同时计算路径和距离,你可以使用两个变量。

这就是广度优先检索算法。对于塔防风格的游戏,我用它来计算所有位置到一个指定位置的路径,而不是重复使用A*算法为每个敌人分开计算路径。我用它来寻找一个怪物指定行动距离内所有的位置。我也是用它来进行程序化的地图生成。Minecraft使用它来进行可见性提出。由此可见这是一个不错的算法。

下一步

  • 我有python和c++代码的实现。

  • 如果你想要找到从一个点出发而不是到达一个点的路径,只需要在检索路径的时候翻转came_from指针。

  • 如果你想要知道一些点而不是一个点的路径,你可以在图的边缘为你的每个目标点添加一个额外的点。额外的点不会出现在网格中,但是它会表示在图中的目标位置。

  • 提前退出:如果你是在寻找一个到达某一点或从某一点出发,。我在A*算法的文章当中描述了这种情况。

  • 加权边:如果你需要不同的移动成本,广度优先搜索可以替换为为Dijkstra算法。我在A*算法的文章当中描述了这种情况。

  • 启发:如果你需要添加一种指导寻找目标的方法,广度优先算法可以替换为最佳优先算法。我在A*算法的文章当中描述了这种情况。

  • 如果你从广度优先算法,并且加上了提前退出,加权边和启发,你会得到A。如你所想,我在A算法的文章当中描述了这种情况。

知乎上最近有个很热门的讨论,话题就是“二十几岁是不是人生最艰难的时候?”。下面是一些我觉得挺有意思的回答。

“本人女,28岁,硕士毕业四年,刚遭遇在一起七年的丈夫孕期出轨,离婚,自己带着个不到一岁的女儿生活。曾经的小女人,现在要扛起两个人的人生。艰难得他妈的无法想象。但这肯定不是我最艰难的时候。以后面对的困难我自己也没有办法想象,也不想去想太多,自己该尽的责任要尽,该做的事情要做,有种生就有种养。要快乐,珍惜亲人,享受生活。遇到合适的男人还要嫁,不要怕。高兴自己还能去折腾,去扛起来,还有人值得我去爱。男人们怕什么呢?”

“20多岁,没钱,没房,没车,要考虑自己的前途,考虑给另一半幸福,考虑父母。出去做事又没有地位,都有比你年龄大,级别高的人,一边教训你一边抢你的女朋友。这日子真艰难,过了20多岁就好了。这是我25岁前的想法。今年我26岁,我依然没钱,没房,没车,依然担心自己的前途,依然要考虑所有要考虑的事情。只是,我想明白了。这些虽然难,但绝不是最难的时刻。” “最艰难的时刻,是你等到人生走完大半,才发现自己的内心挣扎,才跌落到艰辛的生活,才遭遇到人生的重大变故和失败打击。因为,那个时候,你的希望,你的可能,你的变数,已然不多了。”

“最艰难的时刻应该是放弃自己,放弃希望的那一刻,可能会发生在人生任何一个时刻,而不仅仅是二十多岁的时刻。如果你没有放弃,那么最艰难的时刻就不会到来,听起来有点像鸡汤。反正经历过很多那种坑爹的事情之后,很多都会看淡,没想象中的那么恐怖。”

“26-30是一段苦闷的历程,离成功仿佛太远,却又过了推倒重来的年岁,不妨放低自己的心态,踏踏实实、不计得失的做好手头的事。痛苦、挣扎是件好事儿,说明你还没有麻木。”

“昨晚夜游回家有幸踩了一坨狗屎,我的狗屎运也快来了。”

“借用叔本华的解释:我们在早年主要是通过诗歌、小说,而不是通过现实来认识生活。我们处于旭日初升的青春年华,诗歌、小说所描绘的影像,在我们的眼前闪烁;我们备受渴望的折磨,巴不得看到那些景象成为现实,迫不及待地要去抓住彩虹。年轻人期望他们的一生能像一部趣味盎然的小说。他们的失望也就由此而来。”

“七十岁疾病缠身的你会不会羡慕二十岁健康的我。——不会,因为七十岁的我可以是七十岁,可以是二十岁,也可以是十岁。本以为快乐不会长久,哪知一天比一天快乐。人生只不过是不断的选择,想办法玩得开心。”

二十几岁的年纪,我们羞愧于自己的一无所有,望着比自己年长一些的人们,家庭事业双双得意,总是羡慕不已。却不知那些比我们年长一些的人,数着早生的白发跟眼角不和谐的皱纹,忧心着孩子的课业,挂念着双亲的健康,心中怀念的却是十多年前球场边那个意气风发的少年。而事实上多少人感觉到的那份艰难,是源自于面对强大对手时实力悬殊而造成的恐惧,却并非是残酷现实所带来的无力感。毕竟在艰难这个借口下,有太多人一味埋怨,却逃避改变。 二十多岁的时候,我们以为这种日子会持续很久,以为眼前这个难关重要到决定生死。到后来,我们曾经以为根本就不可能迈不过去的门槛最终还是迈过去了,虽然也许是一路磕磕绊绊的。我们一直以为最艰难的总是当下,却发现人生从来不曾有最艰难,只会有更艰难。唯一还值得庆幸的是,所有打不倒你的都将使你变得更强大,所有打倒了你的也并没将你彻底击垮。

电影《这个杀手不太冷》里,小萝莉Mathilda问Léon:“ Is life always this hard, or is it just when you’re a kid? ”,Léon回答说:“Always like this.”你看吧,就算人生已经如此的艰难,有些事情还是需要揭穿。

  1. 我们在颠覆世界,颠覆传统,颠覆XX,实际情况自己都还没有颠覆

  2. 已经获多少投资,如果是美元,直接按人民币看待,如果是人民币,请减半,比如拿到100w美元,实际拿到100w人民币已经不错了,如果拿到1000w人民币,实际也就拿到500w左右,公司估值其实情况差不多

  3. 我们是80团队,或者90团队,其实是一帮大叔在装嫩,实际情况可能有一个是80后,或者90,像这样的团队,直接错一个年代来看,比如说90后团队,可能大部分是80后

  4. 知名机构投资,其实是个很小的投资机构,甚至连机构都不是,个人出资,如果是知名的机构,直接会标明哪家机构,和有关部门一样神秘

  5. 我们是很有激情的团队,这个怎么说,就是让你拼命干活

  6. 我们创业是经过深思熟虑,不是一时的头脑发热,但你会突然发现某天,CEO在公共场合讲创业故事时,可能会说洗澡时灵光一闪,小便时突然有了灵感,还有可能是大便时,泡妞时等等剧本

  7. 这个产品是我们坚定不移一定要做的,但你看一下CEO资历,连续创业者

  8. 反对加班,不打卡,上下班自由,弹性工作,首先创业公司不可能不加班,通宵加班,第二天可以休息,美其名曰弹性工作,如果规定6点下班,每晚加班到10点,老板自己觉得都不好意思,直接把下班点干掉,你永远不知道什么时候下班,如果你早点离开,说明你不能融入团队,直接弹到你一周七天上班

  9. 团队有很多萌妹子,什么是萌妹子?如果有很多美女的话,直接说美女了,还说什么萌妹子,萌妹子就是长的不好看,只能靠萌的妹子

  10. 加入团队期权,创业团队九死一生,期权有个毛用,万一成功了,有无数种方式稀释你得股权,只不过是前期给你开低工资的说辞

  11. 希望你有创业精神,就是说,你是不是不拿工资也愿意干的主

  12. 福利待遇海外假期,出国旅游,这玩意能信用吗?创始人都没有出过国,每天拿着鸡蛋灌饼坐地铁

  13. 有极客精神,就是说,加班到深夜也无所谓

  14. 免费咖啡,零食,靠,加班到深夜谁不困啊,提供咖啡刺激你,夜间很多饭店都关门了,饿了只能吃点零食

  15. 统一配置mac 本,可能现实情况真的是每人一个,但一般进入团队前会问你,你有mac本吗?上班带上

  16. 我们只招全栈工程师,牛人,在CEO眼中的全栈工程师,能android ,能ios ,能运维,能设计,能后台,能前端,美工,要不然什么是全栈工程师啊

  17. 我们团队是互补的团队,看到着,开发人员眼中以为应该有美工,移动端开发,前端,实际情况开发就一人,有CEO,可能有CFO,可能有运营

  18. 我们崇尚硅谷文化,崇尚敏捷开发,其实他们自己也不知道要啥,随时改,随时变,经常拍脑袋,够敏够捷

  19. 每天用各种谎言安慰自己,安慰团队,安慰投资人,试问连最基本的诚信都有问题的团队,能成功吗?他向你承诺的谁能保证不是谎言呢

  20. 我们是豪华团队,来自知名互联网公司,各种Title,其实就是在创业前,用wordpress搭建一个网站,就是xx创始人+CEO了,搭建网站的就是CTO了,不几天豪华Title都有了,直接开外挂

  21. 包婚恋,这是一种幽默,创始人都还单着呢

  22. 无学历,无经验要求,你应该这么理解,工作3年以上,搞过大站,搞过大数据,各种精通

  23. 期权奖励拿到手软,希望能弥补你看到基本工资后的脚软

  24. 有活力的技术团队,一帮工作不到2年的,只剩活力了

  25. 千亿市场的探索者,实际就是不知道市场到底有多大

  26. 典型欧美创业工作环境,你可以想象一下,地下车库,毛坯房,民宅

  27. 全方位成长机会,因为全方位缺人,你会成为超人的

  28. 各种福利,但每样细说太伤感情

  29. 提供住宿、班车、两餐,全方位压榨你的时间,如果你只耗电该多好

  30. 在中关村创业大街的咖啡馆内,听到最多的就是,我们就是10年前的阿里巴巴,在座的各位就是18罗汉,大家不久就是千万富翁了,先花一周把电商网站开发出来,不出半年就会超过淘宝,每次听到,我就想到以前的大跃进,梦想可以很伟大,但也要脚踏实地的前行,你以为电商就一个页面啊,阿里背后几千位开发都是吃素的,是磨磨唧唧开发的10年

  31. 你会发现创业故事都那么玄乎,那么美,其实在创业前剧本都已经想好了,直接照剧本做,如果没有剧本后期再补,你甚至发现,他们不是在创业,而是在制造故事,比如说和投资人小便时拿下多少融资,操,实际情况,不知道找投资人谈了多少次了,最终都已经签了协议,然后投资人说,我去小便一下,ceo直接屁颠屁颠跟着去了,然后跟投资人说,我能说这次融资是在厕内谈得吗?有引爆点,容易宣传。

  32. 创业团队的工资,有的团队根本没有工资,甚至给2000,你打发叫花子呢,让你丫放弃上万工资的工作,加入团队后,这些工资损失会按入股吗?前期这些小钱能占很大比例,成功了,大头全是你们的,失败了,什么都没有,你连工资都发不起还创业,还说你没有创业精神,不是码农们不愿意付出,什么可以一年不要任何工资,但一年后,你会不离不弃吗?公司发展起来了,难道不会招更多有背景的开发人员吗?这些前期的码农如何安排,任何前期加入的成员,请CEO认真对待,他们放弃的不是你眼前看到的,那是一种信任和寄托。

  33. 不缺钱的团队,这样的团队确实存在,有3中形式的团队,超豪华MVP团队,可以轻松拿到融资的,碰到这样的团队,即使打酱油也加入吧,死也是站着的,还有一种团队在前期就拿到融资,这样的团队,烧钱特别疯狂,根本不是自己的钱,who care?,但这样的公司死的也快,还有一种就是土豪或者有土豪的朋友,其实他们就是玩玩,失败了对于他们来说,就是小钱,但码农伤不起

什么是jsonp

为了便于客户端使用数据,逐渐形成了一种非正式传输协议,人们把它称作JSONP,该协议的一个要点就是允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。

例子

客户端写法

这里借用了前端jquery框架对jsonp的支持

var ajaxUrl = "http://192.168.8.141:9092/project/rest/team/matchResult/1/20/1/1000";
 
 
function localHandler(data) {
    console.log("fengshu")
    console.log(data);
}
 
var ajaxParam = {
    async: false,
    url: ajaxUrl, 
    type: "GET",
    dataType: 'jsonp',//非正式跨域传输协议
    jsonp: 'localHandler',
    success: function (json) {
        //回调数据在localHandler处理
    }
};
$.ajax(ajaxParam);

服务器端写法

@RequestMapping("/matchResult/{page}/{pageSize}/{type}/{matchId}")
public  void getMatchResult(HttpServletResponse response, @PathVariable("page") int page,
        @PathVariable("pageSize") int pageSize, @PathVariable("type") String type,@PathVariable("matchId") String matchId) {
    EntityList<MatchResult> datas = dddService.getTeamRanks(matchId,type,page,pageSize);
    ObjectMapper objectMapper = new ObjectMapper();
    String jsonResult=null;
    try {
        jsonResult = objectMapper.writeValueAsString(datas);
    } catch (JsonGenerationException e1) {
        e1.printStackTrace();
    } catch (JsonMappingException e1) {
        e1.printStackTrace();
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    PrintWriter out=null;
    response.setCharacterEncoding("UTF-8");
    try {
        out = response.getWriter();
         
    } catch (IOException e) {
        e.printStackTrace();
    }  out.println( "localHandler("+jsonResult + ")");
            out.close();
}

总结

目前在浏览器端使用json传输数据jsonp传输协议是解决跨域问题的首选方案

山东青岛一户平凡普通的家庭。清晨六点钟,天蒙蒙亮,小孩睁开惺忪的睡眼,见窗外昏暗,又继续倒头睡觉。当他再次醒来时,发现窗外已然大亮,想再赖床但是被妈妈掀开被子后慌忙起身穿衣洗漱,踉跄地来到餐桌前。摆在他面前的是一碗稀松平常的挂面汤,小孩没有什么胃口,可是又无法违抗母亲的命令,而饭桌的另一边,爸爸埋头看报纸,事不关己的样子。于是乎他只得老老实实坐下吃面。与此同时,母亲也扯来一份报纸研读,突然发现一条关于早餐健康饮食的新闻,考虑到孩子的营养极度不均衡,妈妈又迅速增加了早餐的份额,令男孩苦不堪言……

你有多久没有好好坐到餐桌前吃早餐了呢?又有多久没有吃到家人做的早餐了呢?