只是为的乐趣??Robocode(用Java编写自己的机器人,然后“厮杀”)
下面这篇文章最初发表在2002年1月15日发布的Winfield Allen 软件开发时事通讯(Newsletter)上,这个时事通讯是我在Winfield Allen工作期间编写的。
什么是Robocode?
最近我在阅读一个关于极限编程(XP)的邮件列表,有人提议为对学习更多的XP方法学知识感爱好的开发人员构建一个工作间。这位作者还建议,把一群人凑到一起,分成小组研究该方法学。 然后要有一个经验丰富的XP教练对工作间进行治理,帮助参加者理解其原理。
而建议各小组采用的项目就是创建Robocode机器人。
我停下来,把这段话重新看了一遍。 其实我对机器人一直很感爱好。在我还是个孩子是时候,我记得Aplle Ⅱ上有一个程序,我们用它来编写简单的机器人程序,然后相互作战。
Robocode就是这样一个东西,但是更好一些。它是一个基于Java的机器人作战游戏。 其代码的编写和建模都不错,玩起来也很有趣。
假如你阅读这个时事通讯比较久的话,你可能会问你自己“他会Java吗?”。是的,我会。虽然我经常抱怨Java,但我并非真正讨厌Java这一语言。我所讨厌的只是该语言与生俱来的平台和可升级性问题。 我总认为,作为一种语言,Java是目前最优越的轻量级的C++,这种想法直到我发现C#之后才改变,而这又是另外一个故事了。
Robocode的API
Robocode是由IBM的Alphaworks项目成员Matthew Nelson编写的。从第一个版本开始,Matthew 已对该软件的API做了相当多的改进,我以为该软件为机器人的编写提供了一套完美的框架。 用于创建机器人的基类称作Robot。你的机器人都继续自这个类。 Robot类提供了所有与游戏进行交互所需的方法函数。
在细究API之前,让我们先来了解一些术语。机器人基本上是一些小型的坦克。这些坦克可以旋转,向前或向后移动。它们的顶部有一个用于射击的火炮。在火炮上面还有一个雷达系统,用于侦测其它机器人。机器人的底盘、火炮和雷达系统都可以独立运动,也可以相互“锁定”,一起移动。换句话说,假如将雷达向下锁定,那么它将随火炮一起移动;假如火炮向下锁定,它将随机器人的底盘一起移动。有意思吧?
下面是一些你在编写机器人之初用的最多的方法函数:
ahead(), back() ??向前和向后移动
fire() ??开火
setAdjustGunForRobotTurn() ?? 控制火炮是否向下锁定
setAdjustRadarForGunTurn() ??控制雷达是否向下锁定
turnRight(), turnLeft() ?? 实现机器人的旋转
turnGunRight(), turnGunLeft() ?? 旋转火炮
turnRadarLeft(), turnRadarRight() - 旋转雷达反射镜
当然,这并不是全部的API,但却是你创建第一个机器人所需的最重要的方法。到现在为止,我们仅仅讨论了你可以触发的动作而已。 我们还未讨论如何去发现你的机器人四周所发生的事。Robot类提供了多个事件,你可以利用这些事件来判定你的机器人是否用雷达扫描到了其它机器人,是否被击中,是否与另外一个机器人向撞以及是否与墙向撞等。你可以覆盖下列任一方法函数来为你的机器人提供事件处理函数。
onHitByBullet() ?? 当你的机器人被击中时
onHitRobot() ??当你的机器人与其它机器人向撞时
onHitWall() ?? 当你的机器人撞到墙上时
onScannedRobot() ??当用雷达扫描到其它机器人时
通过上面谈到的简单的事件和动作,可以编写出我们的第一个机器人。(可从Robocode附带的文档中获得完整的API。)
编写你的第一个机器人
预备好了吗?启动Robocode,调出Robot编辑器。(实际上,我并不使用Robocode自带的编辑器,这里用只是为了解释起来轻易一些。)
要创建机器人,你可以选择File菜单的New Robot命令。这时,系统会要求你输入机器人的名称。你可以输入SimpleRobot并点击OK按钮。然后,你需要输入一个package名,Robocode建议使用
姓名的首字母缩写,不过我们在这里使用“newsletter”。之后,会打开一个包含新机器人初始代码的窗口。
花一点时间阅读一下这些自动生成的代码。 这个机器人并非世界上最聪明的机器人,但是它却对如何组装一个Robocode机器人作了很好的演示。
我们的机器人中定义的第一个方法是public void run()。这是机器人的心脏所在。游戏引擎最初启动之时将调用这个方法。一般,你可以用一个无穷的While循环来定义机器人的动作。
机器人SimpleRobot中的while循环做四件事情:
ahead(100); 向前移动100个象素
turnGunRight(360); 将火炮向右旋转360度
back(100); 向后移动100个象素
turnGunRight(360); 将火炮向右旋转360度
这些命令简单的说就是“向前移动100个象素并四面查看一番,再后退100象素,四面查看一番。然后,不断重复”。
注重我们从未明确执行一个雷达扫描动作。那是因为默认情况下,雷达反射镜与火炮一起移动,一直处于扫描状态中。所以,转动火炮可以实现雷达的扫描。 当雷达侦测到目标后,会调用我们的机器人中定义的第二个方法函数public void onSannedRobot(...)。
这个事件处理函数也不做什么事情。只执行“射击”动作。 由于雷达与火炮的方向始终保持一致,所以这个函数被调用时,我们应该正好瞄准了目标。
唯一的另一个方法是onHitByBullet事件处理函数。这个函数在我们被其它机器人击中时调用。代码的作用是旋转机器人与子弹来向成90度角,这样,当我们前后移动时,就可避开子弹第二次射来的方向。
保存并选择compile命令编译你的机器人。因为我们没有对代码做任何改动,所以应该不会有任何错误。
同其它机器人决斗
你的第一场战争
你的机器人已经预备就绪,是开战的时候了!Robocode附带了大量可用来举行战斗的样本机器人,Target是其中最简单的一个。
假设你想在Target和我们刚才创建的机器人之间开展一场战斗。 那么关闭Robot编辑器,在主窗口中选择Battle菜单的New命令。 在New Battle对话框中找到“newsletter.SimpleRobot”并点击Add按钮。然后找到“sample.Target”,再一次点击Add按钮。对话框里还有其它选项,不过我们先不管。点击finish开始战斗。
几秒钟后,你将会看到两个机器人出现在屏幕上。等待的时间主要取决于电脑的速度。很快,战斗开始了。首先,我们的机器人向前移动一小段距离,然后将火炮旋转一周。当它发现Target时,就会向其射击。几次被击中后,Target就会避开。 我们的机器人向后移动并进行扫描,直到发现Target为止。然后继续射击。
Target不会射击,所以我们将最终获得这场战斗的胜利(输给不会射击的机器人会让我很懊恼)。也因此,我们的onHitByBullet事件应该永远不会被调用。战斗结束后,可以再试试与其它样本机器人作战。 不过别指望用这个简单机器人把它们都打败。
比赛
我不推荐一开始就加入比赛,但是最终你会认为,你的机器人足够的好了,可以加入比赛了。
有大量不同的团体组织比赛。你可以在网上找到他们。假如你不喜欢参加公开的比赛,也可以找几个朋友组织自己的比赛。
比赛的形式主要有两种。混合比赛是两个以上的机器人进行战斗;一对一比赛就是一个机器人对付另一个机器人,就象马上枪术比赛一样。
通常,混合比赛战斗多并且赢家通吃,而一对一的比赛战斗较少,要么一方胜出,要么两败俱伤。
高级机器人
Robocode API还定义了另一个称做AdvancedRobot的基类。AdvancedRobot与Robot的不同表现在许多方面。首先,你可以选择调用无阻碍(non-blocking)运动。当你调用ahead(100)时,机器人就立即移动100个象素。该动作完成后才执行下一行代码。这样一来就无法实现曲线运动了。不过现在有了ahead()和back()函数的无阻碍版本-即setAhead()和setBack(),能够立即返回执行。可以把它们想象成向堆栈里加入一条指令,而堆栈里的指令将不会被执行,除非你进行一个类似fire()的函数调用
这将引起堆栈中的指令的执行。另外有一个叫做execute()方法函数可以明确的运行堆栈指令。
基于AdvanceRobot的机器人具有更加高级的智商。不再是简单的“移动,扫描,再移动,再扫描”。 你做出的机器人可以对整个战场进行侦察,会思考下一步行动,然后执行合适的指令。这种“侦察、思考、行动”的模式能够使你的机器人具有适应性和学习性。
举个例子来说,假如你的机器人能连续不断的侦察整个战场并记录其它机器人的位置和方向,那么你就可以通过模式分析来获得某个特定机器人的移动方式。假如敌方以可预知的方式向左或向右躲避的话,那么你就可以对此进行猜测,向敌人将要出现的地方射击。这样威力会更加强大,当然也更加复杂。
结论
这篇文章还运不足以成为如何编写最优机器人的完全指南,但是应该能帮助你入门。当你写了几个简单的机器人后,就会找到一些新的感觉。也许你会参加比赛,或者从互联网上下载其它的机器人。这样,你就有机会看到别人是如何编写机器人的。
Robocode不仅是一个游戏,也是一个有趣的磨练编程技巧的方法。你可以实验不同的设计和模式,可以用JUnit创建测试包。实际上,这个游戏的限制只在于你的想象力。