前言 Robocode在短短的时间内风靡全球,全世界的robocode爱好者设计出了大量的优秀智能机器人,他们都拥有各自的运动方式,有的很容易被击中,有的却很难射击。设计一个好的运动方式是优秀robocode机器人取胜的关键。上届世界中级组冠军Fermat就是靠他让敌人难以琢磨的运动躲过敌人一发发的子弹而取得胜利。(当然,他的优秀的瞄准射击也是取胜的关键)怎样的运动才能不被敌人击中,让敌人琢磨不透呢?这里我把常见的机器人运动方式分为4类来详解。
明显有规律的主动运动 刚开始玩robocode的很多都会有这种感觉,Samples里面Walls最强,谁都打不到他。Wall就是一种很典型的明显有规律的主动运动,他总是直线绕墙走,如图1:
图1 因为它几乎总是在动,而Sample里面的机器人的射击方法几乎都是直接射击敌人的当前位置,由于子弹到达目标需要一定的时间,当子弹飞过去的时候,Wall已经不在原来那个位置了,所以它们总是打不到它,因此在刚开始时它看起来是那么的强大。但是,Walls并不能算一个优秀的机器人,它仅是作为一个例子来介绍robocode机器人的制作方法,稍厉害一点的机器人都能很得心应手的射击它,有的机器人甚至能枪枪必中的打它。他们大多运用了提前量的算法计算出子弹到达Walls的时候Walls大概走的距离,然后攻击Walls下一步将要行走的地方。至于怎样编码实现,已经超出了这篇文章的范围,你可以参考Predictive targeting。
采取这一类运动方式的机器人很多,它们规律很明显,很容易被掌握,像SpinBot总是做圆周运动(圆周运动的射击方法可以参见圆周瞄准),Corners总是躲在角落不动……你会发现它们都是很容易对付的角色,是不是要写出优秀的机器人就不能用这样的策略呢?当然不是,在人眼看起来有规律的运动,机器人未必会认为有规律(这要取决于你的机器人的分析方法)。特别是在群战的时候,你要顾及大量的敌人,你不可能只关注一个敌人的运动,你要同时关注A或关注B的运动,因此即使A作了规律很明显的运动,你也很难察觉。
典型的例子就是David McCoy的PrairieWolf,你看它群战的时候经常待在角落做一种绕角落来回运动,但是你却未必能很容易的射击他,还有就是Paul Evans的SandboxLump,它不仅是在角落来回,而且还夹杂着很多的弧线运动,如图2:
图2 即使是单挑的时候,你的机器人也很难分析出SandboxLump的具体规律,所以要击中它并不容易,确切地说那实在是太难了。这里我要重要介绍一种被广泛采用的来回运动方式,如图3:
图3 假如机器人R1在A,B之间作直线来回运动,某一时刻机器人R1和机器人R2在如图所示位置,R1目前是直线运动。如果R1的摆幅小于R1到R2的距离,R2用直线提前量的射击方法,射击点在B点右边的C点,它发射了子弹,但是R1运动到B点的时候突然反向向A行驶,到达A后又返回向B行驶,如此反复,R2的子弹就总是打在A点偏左或者B点偏右的地方。这就是来回运动的迷惑性,哈哈,R1能迷惑敌人了,它很强了吧?不,如果R1的摆幅大于R1与R2的距离。如图所显假设R2在R2’的位置,它计算的射击点C’在AB之间,这样的话则可以击中敌人,所以来回运动也不一定总能使敌人打偏,靠你比较近的敌人就显得非常危险。还有优秀的机器人一般能识别来回运动的敌人,它能计算出你来回的距离,这样你可能就被别人百发百中了。所以如果你要采用这样的方法的话,可以添加一些其他因数,比如说弧线来回运动,来回运动随机距离等等。
随机性很强的主动运动 当你掌握了对付Wall和SpinBot的射击方法后,你是否又觉得Sample里面的Crazy也是个令人头疼的家伙?你用分别用对付Corner,Walls和SpinBot的射击方法跟他对战,你会发现这三种方法都能打中他,但是命中率都没有打Corner,Walls和SpinBot他们高,这里我做了个测试:分别用三种方法来对付Crazy,测试结果如下表1:
子弹参数
射击方法 命中 未中 命中率
对付Corner的当前位置射击方法 5 40 %11.11
对付Walls的直线提前量射击方法 8 24 %25
对付SpinBot的圆周提前量射击方法 7 17 %29.16
他虽然总是做弧线运动,但是这次弧线运动停止后又会开始另一个方向的弧线运动。可能你看了Crazy的源代码后你会怀疑,代码里面一个类似Math.random()的语句都没有,怎么称这种运动是随机性很强的运动呢?这里的随机性是相对于你的机器人的运动分析程序的:由于他总是时而转动,时而停止换一个方向,时而向前,时而向后,撞到墙又会改变方向。一般的机器人都难以分辨这种改变,所以通常也称它为随机的运动。
虽然他的随机性很强,但是用对付SpinBot的圆周提前量射击方法也达到%29.16的好成绩,显然这样的运动也不是很理想。更明显的随机运动是在代码里加入了类似ahead(Math.random()*200),turnLeft(Math.random()*360)这样的代码,这样随机性就更强,连此机器人的作者也不知道它的下一步会采取怎样的运动,你又如何提前预知呢?那么……这样运动的机器人是否就打不中呢?你从对付Crazy的射击命中率表可以看出,虽然我采取对付另一种有规律运动方式的的射击策略来对付一种我不知道的运动方式是不合理的,但是我却往往也能打中它,而且命中率并不低。为什么呢?我举个例子吧,假设我采取对付Wall的直线提前量射击方法射击Crazy,如图4:
图4 图5 我认为它走的是直线,这显然是错误的,但是在我离它比较近的时候,虽然射击的是如图的B’点而R1运动到了C点,考虑到机器人有一定的高宽度,R2也仍然能打到它。
对于另外一种直线随机距离来回运动的机器人(前面提到过的一个建议Random),你可以看图5。你计算到如果你射击B的话,子弹到达B点的时候敌人R1也恰好到达B点处,但是R1随时都有可能改变方向返回,这取决于random()方法。如果刚开始ahead(Math.random()*200)中Math.random()返回一个很大的值,大到足以使R1运动到B点甚至超过B点,那么你将击中敌人;如果这个返回值很小,使R1还没到达B处就返回了,这样你这发子弹就浪费了。所以说你仍然有机会击中它,它并不是难以掌握的,只是什么时候能击中什么时候不能击中,谁都说不清楚。
对瞄准有干扰性的主动运动 看了上面两种运动方式的分析,也许在你心头有这样的想法,我先以一种很明显的规律运动,等敌人误认为我是那种方式运动后立刻改变为另一种规律,然后等敌人意识到现在的运动规律后我又改变为原来那种,这样敌人是否就被我迷惑了呢?呵呵,的确,这样是一种很不错的方法,许多优秀的机器人的运动有不同程度的干扰迷惑性。我还是先举个例子来说说吧。假如一个机器人一开始不动,你会采取怎样的射击方法呢?一般都会用对付Corner的当前位置射击方法吧?但是当你发射子弹后,它又开始直线运动了,这时如果你的炮管冷却度为0的话,你采取第二次射击,你怎么办?用对付Corner的当前位置射击方法?你显然打不中它,因为它在动,但是如果用对付Walls的直线提前量射击方法的话,你能肯定它不会在下一个周期(滴答)停下?这是一种典型的对瞄准有干扰性的主动运动,优秀的机器人Wolverine就是采用了这种方法,它能探测敌人什么时候发弹,在敌人发弹的一瞬间改变运动方式如图6:(具体请见躲避子弹)
图6 这样的运动方式通过代码已经很难掌握它的具体变化规律,因此在Wolverine刚出来的时候没有几个机器人能打败Wolverine。后来优秀的机器人不断进展,有的能计算敌人停留了多久就会动,动了多远又会停止,虽然不能很精确,但是总能时而击中它了。
更先进的干扰运动有先小距离来回运动,然后走一段比较长的距离(比如xieming的CX1.33,文件名为cx.MinixHT_1.33);还有先往一个方向直线运动,估算敌人子弹快要击中自己的时候改变运动方向,下一个子弹快击中时又改变方向(如Glyn Davies的Mooserwirt2)等等。很多优秀的机器人都采用了类似的运动方式,怎样才能不被干扰?怎样才能识别干扰?没有一种识别所有干扰的万能方法,因此只能针对特定一种或某一类干扰进行处理,比如CX1.33的干扰,你可以记下它的较长运动距离平均(这里用L表示)是多少?当他运动距离超过了小距离来回运动的那个距离的时候,你就不要以为他还会掉头回去或者就一直走下去,它应该大概在走了L距离后返回。对于众多的运动方式,怎样识别一个干扰又是一个难题,所以在射击优秀的机器人时,命中率一般不会太高。
怎样让别人不断的被干扰成为设计一个优秀运动的重点,也是乐趣所在。当你设计的运动方式不断的迷惑敌人的时候,你是否有种嬉笑欢快的感觉呢!:)
依据对方发弹或者运动而采取的被动运动
前面我们讲到的Wolverine在敌人发弹后才开始运动,是否他也属于这一类呢?的确,它的运动方式也应该同时属于依据对方发弹或者运动而采取的被动运动。
还有一些优秀的机器人很类似Wolverine,它们也是在敌人发弹后才开始运动,但是他们又有很多不同,如果