Robocode入门.docx
Robocode入门一.Robocode入门 在Robocode越来越火爆的今天,仍然有许多人不了解Robocode是什么,是用来做什么的,怎么使用它。对于这些问题在下文中将详细介绍,本文通俗易懂,让你一看就明白。 什么是Robocode? 有人提议为对学习更多的XP方法学知识感兴趣的开发人员构建一个工作间。把一群人凑到一起,分成小组研究该方法学。 然后要有一个经验丰富的XP教练对工作间进行管理,帮助参加者理解其原理。而建议各小组采用的项目就是创建Robocode机器人。 其实我对机器人一直很感兴趣。在我还是个孩子是时候,我记得Aplle 上有一个程序,我们用它来编写简单的机器人程序,然后相互作战。 Robocode就是这样一个东西,但是更好一些。它是一个基于Java的机器人作战游戏。 其代码的编写和建模都不错,玩起来也很有趣。 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,调出Robot编辑器。要创建机器人,你可以选择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的不同表现在许多方面。首先,你可以选择调用无阻碍、炮以及雷达都可以单独旋转,也就是说,在任何时刻,机器人坦克车、炮以及雷达都可以转向不同的方向。缺省情况下,这些方向是一致的,都指向坦克车运动的方向。 附:Robot 命令 Robocode 机器人的命令集都收录在 Robocode API Javadoc 中。这些命令都是 robocode.Robot 类的公共方法。 移动机器人、炮和雷达 移动机器人及其装备的基本命令: turnRight(double degree) 和 turnLeft(double degree) 使机器人转过一个指定的角度。 ahead(double distance) 和 back(double distance) 使机器人移动指定的像素点距离;这两个方法在机器人碰到墙或另外一个机器人时即告完成。 turnGunRight(double degree) 和 turnGunLeft(double degree) 使炮可以独立于坦克车的方向转动。 turnRadarRight(double degree) 和 turnRadarLeft(double degree) 使炮上面的雷达转动,转动的方向也独立于炮的方向。 这些命令都是在执行完毕后才把控制权交还给程序。此外,转动坦克车的时候,除非通过调用下列方法分别指明炮的方向,否则炮的指向也将移动。 setAdjustGunForRobotTurn(boolean flag):如果 flag 被设置成 true,那么坦克车转动时,炮保持原来的方向。 setAdjustRadarForRobotTurn(boolean flag):如果 flag 被设置成 true,那么坦克车转动时,雷达会保持原来的方向。 setAdjustRadarForGunTurn(boolean flag):如果 flag 被设置成 true,那么炮转动时,雷达会保持原来的方向。而且,它执行的动作如同调用了 setAdjustRadarForRobotTurn(true)。 获取关于机器人的信息 getX 和 getY 可以捕捉到机器人当前的坐标。 getHeading、getGunHeading 和 getRadarHeading 可以得出坦克车、炮或雷达当前的方向,该方向是以角度表示的。 getBattleFieldWidth 和 getBattleFieldHeight 可以得到当前这一回合的战场尺寸。 射击命令 一旦掌握了移动机器人以及相关的武器装备的方法,我们就该考虑射击和控制损害的任务了。每个机器人在开始时都有一个缺省的“能量级别”,当它的能量级别减小到零的时候,我们就认为这个机器人已经被消灭了。射击的时候,机器人最多可以用掉三个能量单位。提供给炮弹的能量越多,对目标机器人所造成的损害也就越大。 fire(double power) 和 fireBullet(double power) 用来发射指定能量的炮弹。调用的 fireBullet 版本返回 robocode.Bullet 对象的一个引用,该引用可以用于高级机器人。 事件 每当机器人在移动或转动时,雷达一直处于激活状态,如果雷达检测到有机器人在它的范围内,就会触发一个事件。作为机器人创建者,我们有权选择处理可能在战斗中发生的各类事件。基本的 Robot 类中包括了所有这些事件的缺省处理程序。但是,们可以覆盖其中任何一个“什么也不做的”缺省处理程序,然后实现自己的定制行为。下面是一些较为常用的事件: ScannedRobotEvent。通过覆盖 onScannedRobot 方法来处理 ScannedRobotEvent;当雷达检测到机器人时,就调用该方法。 HitByBulletEvent。通过覆盖 onHitByBullet 方法来处理 HitByBulletEvent;当机器人被炮弹击中时,就调用该方法。 HitRobotEvent。通过覆盖 onHitRobot 方法来处理 HitRobotEvent;当您的机器人击中另外一个机器人时,就调用该方法。 HitWallEvent。通过覆盖 onHitWall 方法来处理 HitWallEvent;当您的机器人撞到墙时,就调用该方法。 很多研究Robocode的 玩家都被其中的方向及坐标弄糊涂了。整个屏幕哪个是0度角,整个是坐标原点呢? 顺时针与逆时针的方向如何区分? 一段英文的翻译及说明: heading - absolute angle in degrees with 0 facing up the screen, positive clockwise. 0 <= heading < 360. bearing - relative angle to some object from your robots heading, positive clockwise. -180 < bearing <= 180 heading:是机器人方向与屏幕正上方的角度差,方向在0到360之间. bearing:是机器人的某个部件如雷达发现的目标与方向的角度差,顺时针为正角度在-180到180之间 几个在Robocode中很重要的概念: 坐标系:Robocode整个坐标系都是战场屏幕以左下角为原点 绝对方向系:Robocode中不管机器人在哪个方向都是以静态战场屏幕为参照的绝对角度,正上方为0度角。也即不管是Robot,Gun,Radar向北为0,向东为90,向南为180,向西为270。 相对方向系:相对方向是Robot,Gun,Radar以机器人的动态heading角度为参照的角度差不再以整个静态屏幕为参照了,叫它相对因为机器人的heading是随着机器人移动而不停的在改变,heaing只是个相对物体。 顺时针和逆时针是看另一机器人是在你的Heading角度的(0,180)还是之间。 再次提醒:Heading是个静态角度,正上方总为0.不管是取Heading,还是取方向。Bearing是个角度差值,是由参照的Heading和发现时的Heading的差值。好了,方向的问题就说到这,欢迎大家来讨论。 昨天看了Robocode的基础知识,自己写了个bot,放到BattleField上缺是屡战屡败伤心ing。 Bot对于周围环境的了解非常有限。它可以知道其它机器人的距离、方位、方向、速度和能量等级。但是,它看不到子弹。怎么才可以有效的躲避对方的子弹呢? Bot虽然看不到子弹,但是对方的能量等级还是可以scan到了。对方只要发射子弹就会耗损能量,并且耗损的能量介于0和3之间。根据这些线索,如何发现其它机器人正向它开炮对于“笨笨”的Bot不就易如反掌了? _ 当Bot检测到对方发射子弹的信息时,向左或向右移动一小步,嘿嘿,子弹就打不到咯并且大多数Bot的瞄准方法是要么直接向目标开炮,要么试着根据Bot的速度和方向来推算位置。如果我的Bot不移动,两种算法都会正好冲着这个Bot的当前位置开炮。哈哈哈,这时我的Bot再移动,不就全部都打不到啦。 下面是部分代码和注释: double previousEnergy = 100; /初始状态对方能量为100 int movementDirection = 1; /移动方向 int gunDirection = 1; /炮管方向 /* * 当检测到对方Bot,触发事件 * param e */ public void onScannedRobot(ScannedRobotEvent e) /调整自己和对方之间的角度 setTurnRight(e.getBearing+90-30*movementDirection); /如果对方的能量损耗一定值,进行躲避动作 double changeInEnergy = previousEnergy - e.getEnergy; if (changeInEnergy>0 && changeInEnergy<=3) /躲避! movementDirection = -movementDirection; /和上次的躲避方向相反 setAhead(e.getDistance/4+25)*movementDirection); /将炮管指向对方当前位置 gunDirection = -gunDirection; setTurnGunRight(99999*gunDirection); /射击 fire(1); /重新设置对方能量 previousEnergy = e.getEnergy; 是不是很简单?这个技巧还存在问题。子弹一发射,我的Bot就移动,所以它最终可能会移回炮弹轨迹之内。最好是在估计子弹要到达时再移动。 我有个更大胆的假设:因为现在我的Bot命中率还不高,那么如果我的Bot一直不开火,只是躲避对方的子弹的话,能不能拖到对方的能量为0呢? 上一次我所提到的躲避方法,确实存在一点问题。对方子弹一发射,我的Bot就移动,并且这个移动是规律的来回移动。如果移动距离短了,就可能在回来的时候撞到对方的子弹;如果移动距离长了,就等于做一个直线运动,对方很容易计算得到Bot的运动轨迹。还有一个问题,躲避的时候很有可能撞到墙上 针对以上的问题,我另写了一个Bot。代码如下: import robocode.*; public class HanicBot extends AdvancedRobot private double eDist; /对方的距离 private double move; /移动的距离 private double radarMove = 45; /雷达移动的角度 private double dFirePower; /火力 /* * main func run */ public void run eDist = 300; while(true) /每过一个周期,运动随机的距离 double period = 4*(int)(eDist/80); /周期;敌人越接近,周期越短,移动越频繁 /周期开始,则移动 if(getTime%period = 0) move = (Math.random*2-1)*(period*8 - 25); setAhead(move + (move >= 0) ? 25: -25); /避免撞墙 double heading = getHeadingRadians; /取得bot方向的弧度数 double x = getX + move*Math.sin(heading); /移动move后将要达到的x坐标 double y = getY + move*Math.cos(heading); /移动move后将要达到的y坐标 double dWidth = getBattleFieldWidth; /战场的宽度 double dHeight = getBattleFieldHeight; /战场的长度 /当(x,y)超过指定的范围,则反向移动move if(x < 30 | x > dWidth-30 | y < 30 | y > dHeight-30) setBack(move); turnRadarLeft(radarMove); /转动雷达 /end run /* * 当检测到对方Bot,触发事件 * param e */ public void onScannedRobot(ScannedRobotEvent e) eDist = e.getDistance; /取得对方距离 radarMove = -radarMove; /设置雷达 double eBearing = e.getBearingRadians; /取得和对方相对角度的弧度数 /将bot转动相对的角度,以后bot的运动将是以对方为圆心的圆周运动 setTurnLeftRadians(Math.PI/2 - eBearing); /转动炮管指向对方 setTurnGunRightRadians(robocode.util.Utils.normalRelativeAngle( getHeadingRadians + eBearing - getGunHeadingRadians); /根据对方距离射击 dFirePower = 400/eDist; if (dFirePower > 3) dFirePower = 3; fire(dFirePower); 首先,为了迷惑对方,不让对方容易的得到Bot的移动规律,Bot就要在一定的时间内做出随机的运动,这个很容易办到。并且,我给Bot的运动改变时间规定了周期。这个周期随离对方的距离改变,敌人越接近,周期越短,移动越频繁。 double period = 4*(int)(eDist/80); if(getTime%period = 0) move = (Math.random*2-1)*(period*8 - 25); setAhead(move + (move >= 0) ? 25: -25); 其次,Bot的运动不是呈直线的。而是以对方为圆心的圆周运动。 setTurnGunRightRadians(robocode.util.Utils.normalRelativeAngle( getHeadingRadians + eBearing - getGunHeadingRadians); 最后是如何避免撞墙。这里要用到点三角函数-_-! 原理就是,计算Bot一次运动后将要达到的坐标是不是位于规定的危险区域。如果是,则立即反方向运动。 double heading = getHeadingRadians; double x = getX + move*Math.sin(heading); double y = getY + move*Math.cos(heading); double dWidth = getBattleFieldWidth; double dHeight = getBattleFieldHeight; if(x < 30 | x > dWidth-30 | y < 30 | y > dHeight-30) setBack(move);