内容


Robocode 基本原理之坐标锁定

Comments

导论

前面我们了解了 Robocode 中的绝对方向,相对方向及整个方向系统。相信大家对此深有体会了。但是问题又来了,单知道方向似乎不能完全达到了解敌人的目的。怎样去探测敌人的距离?怎样精确的锁定目标呢?对于移动中的目标我们又如何处理 ? 在这里我们将利用 Java.lang 基本类库中的 Math 类及一些基本三角函数方法为你揭开这些迷雾。对于那些快被遗忘的三角几何知识在本文的最后 Skyala.Li 有比较详细的讲解。

坐标基本概念

首先我们还是来看看 Robocode API 中的一段文字翻译。

All coordinates are expressed as (x,y).

所有的坐标都用 x,y 来表示

All coordinates are positive.

所有的坐标都为正

The origin (0,0) is at the bottom left of the screen.

坐标原点 (0,0) 在屏幕的左下角

Positive x is right. X 的右边为正

Positive y is up. Y 的上面为正

图 1 显示了 Robocode 中的坐标系统,有关图的详细说明请看我们前面介绍的文章 “Robocode 基本原理之方向剖析”.

图 1
图 1
图 1

“动静机器人”测试法

好了,我们知道了 Robocode 整个坐标系统,一切问题都好办了。先让我们进行一些有趣的实验。我们仍以”动静机器人”的方法进行测试。这是个测试机器人方向,坐标参数的很好办法。见下说明:

设计两个机器人,任意取名为 Geny 和 GenyTrack。Geny 是个静止的机器人,它主要任务是打印自己的当前坐标,用来验证 GenyTrack 追踪它的位置是否正确。GenyTrack 顾名思义,它就是我们要研究的追踪目标机器人了。它在此负责锁定 Geny 的坐标,距离并打印出探测到的 Geny 机器人的 X,Y 坐标及距离 , 此处使用了 Java.lang 类库中的 Math.round 方法 , 四舍五入得到的 double 类型的数据,方便对比。最后用表格对比,以此来验证我们使用方法的正确性。

当然还有很多有趣的测试方法来等待着你的验证。如测速度,加速度时我们就可用”龟兔赛跑”的方法;测炮管,雷达坦克车旋转相互影响度可用”离心重力”的方法。相信从测试方法的名字聪明的你们就知道他的用法了。

在我们开始前,Skyala.Li 建议你们下载源码 ( resource) 先看看 GenyTrack 的表演。当然你也可参考文章内附加的辅助说明 Robocode 坐标系统的代码。

 Geny: 
 package test; 
 import robocode.*; 
 public class Geny extends AdvancedRobot 
 { 
    public void run () 
    { 
        while (true) 
        { 
            // round 对 get 到的数据进行四舍五入处理
            out.println("x:"+Math.round(getX())); 
            out.println("y:"+Math.round(getY())); 
        }       
    } 
 }

GenyTrack:

 package test; 
 import robocode.*; 
 
 public class GenyTrack extends AdvancedRobot 
 {    
    public void run () 
    { 
        while (true) 
        { 
            turnRadarRight(400); 
        } 
    } 
    public void onScannedRobot(ScannedRobotEvent e) 
    { 
        double bearing = (getHeading() + e.getBearing()) % 360; 
        double distance = e.getDistance(); 
        bearing = Math.toRadians(bearing); 
    
        double genyX = getX() + Math.sin(bearing) * distance; 
        double genyY = getY() + Math.cos(bearing) * distance; 
        
        out.println("genyX:"+ Math.round(genyX)); 
        out.println("genyY:"+ Math.round(genyY)); 
    } 
 }

注意这两个机器人我们都使用了 AdvancedRobot 的类,这可是高级机器人的说明了。有关高级机器人大家可以查找 Robocode API 的说明,也可看看 Sing Li 的 "Rock 'em, sock 'em Robocode: Round 2".

距离探测

要得到目标坐标我们首先得知道我们和目标之间的距离。这里的距离探测很简单,只要运用 GenyTrack 机器人 ScannedRobotEvent 事件中的 getDistance() 方法我们就可得到 Geny 机器人和你之间的距离差了。只是要注意一点,由于机器人存在着宽和高 , 可分别用 Robocode API 中的 getWidth() 和 getHeigth() 方法得到。而两个机器人的距离是以双方的中心点为终点。如图所示,L 才是它们的距离,A 的距离是错误的。

图 2
图 2

坐标探测

知道了对方的距离,知道了整个坐标系统。我们就来锁定我们的目标 Geny. 我们先来看看图 3 所示:

图 3
图 3
图 3
图 3
图 3
列表 1:
GenyGenyTrack
X:303genyX:303
Y:128genyY:128

列表 1 就是我们用”动静机器人”测试法得出的数据。你将会惊喜若狂,不错,我们成功的探测到了我们可怜的 Geny 的坐标。惊喜过后你就会不明白了 : 我们是怎样实现这一切的?为什么代码中使用到了非 Robocode 中的类库 Math,还似乎用到了正余弦求解,还有弧度?不错,这就是 Robocode: 处处都让我们惊奇,处处都让我们学习新的知识。如果你对中学时代的数学三角几何解法已经陌生,没关系,你将在我们本文最后的 三角函数基础中将学习到这些。它将勾起你中学时代的记忆。

现在让我们来分析分析我们 GenyTrack 到底做了些什么:

在 GenyTrack 的 ScanndeRobotEvent 事件中我们首先得到 Geny 的绝对角度 bearing,也即相对屏幕的角度。并从 ScannedRobotEvent 扫描事件中得到的大量信息分析中提炼出 Geny 和 GenyTrack 的距离为 distance。有了 Geny 的角度 , 有了 Geny 的距离我们再根据三角学基础(详见文 三角函数基础)就可求出 Geny 的精确坐标了。

又由于 Java 类库中的正弦函数 sin 余弦函数 cos 是以弧度制(详见文 三角函数基础)为角度的参数。所以我们利用了 Math.toRadians 方法把 Geny 的绝对角度转化为弧度。见列表 2

列表 2:
 double bearing = (getHeading() + e.getBearing()) % 360; 
        double distance = e.getDistance(); 
        bearing = Math.toRadians(bearing); 
    
        double genyX = getX() + Math.sin(bearing) * distance; 
        double genyY = getY() + Math.cos(bearing) * distance; 
        
        out.println("genyX:"+ Math.round(genyX)); 
        out.println("genyY:"+ Math.round(genyY));

注意三角函数的基础中:对边长 =sina * 斜边长,侧边长 =cosa *斜边长,但要记住 Robocode 中三角坐标系统中的 sin 和 cos 和我们数学中的三角坐标系统有一定差别,也即上面的 sina 和 cosa 要对换,对边长 =cosa* 斜边长。图 4 画出了 Geny 和 GenyTrack 之间角度和距离的关系以及 Robocode 所采用的三角坐标系统。

图 4
图 4
图 4

黑线条为 GenyTrack 的 X,Y 坐标 , 蓝线条以 Geny 的距离 distance 和绝对角度 bear 求得的 X,Y 坐标 , 两者相加得到的就是 Geny 的 X 和 Y 坐标。

至于 Math 类库的使用,我们就不详细说明了。读者也可从下面的 IBM Java 专区链接中找到很多有关的知识,也可参考一些 Java 类库书籍说明。当你设计高级 Robocode 机器人时你会发现,Math 类库是你不可缺少的一部分知识。此处我们只简单的介绍正弦函数及余弦函数的使用。

 Sin 
 public static double sin(double a) 
 Returns the trigonometric sine of an angle. 
 Parameters: 
 a - an angle, in radians. 
 Returns: 
 the sine of the argument

Sin 函数返回三角的正弦函数,参数 a 是一个以 double 类型以弧度表示的角度值,返回类型为 double.

 cos 
 public static double cos(double a) 
 Returns the trigonometric cosine of an angle. 
 Parameters: 
 a - an angle, in radians. 
 Returns: 
 the cosine of the argument

Cos 函数返回三角的余弦函数,参数 a 是一个以 double 类型的弧度表示的角值 , 返回类型为 double.

有人会问为什么不使用 ScanndeRobot 事件中的 getRadarHeadingRadians() 方法直接得到弧度。哦,你来看看 Robocode 中华联盟 iiley的一段说明:

public void onScannedRobot(ScannedRobotEvent event) { 
  enemyX=Math.sin(Util.standardMathDirRadians(getRadarHeadingRadians()))*event
	  .getDistance(); 
  enemyY=Math.cos(Util.standardMathDirRadians(getRadarHeadingRadians()))*event
	  .getDistance(); 
}

看起来好像正确的,但是你实践一下会发现他很不准确,为什么呢?原因在于 getRadarHeadingRadians() 函数,当你调用此函数的时候实际上雷达已经不在刚刚扫描到敌人的那个角度了,他已经转过了十几度甚至更多。雷达默认转动速度是 45 度 /robocode 单位时间,实际上一般来说你用 getRadarHeadingRadians() 得到的值总是 45 度的整数倍。(一些情况除外,比如说你用了 turnRadarLeft(11) 类似的语句以后)。

Robocode 也遵循数学应用中的基本法则用两种方法来表示方向的角度:角度制和弧度制,本文的代码及以前文章中的代码我们一直用的是角度制。另外一种方法就是利用 ScannedRobotEvent.getBearingRadians()+robot.getHeadingRadians() 得到敌人以弧度表示的方向,这个方法在本文章中没有说明了,有兴趣的朋友可以自己试试用 Java.util 类库来实现 . 也可参考文档 "精确计算敌人的坐标"。大家也可比较两种方法各自特点,这将是个很有意思的过程。

移动锁定

当然,即使是最简单的机器人也不会坐在那一动不动等着你来消灭。它会躲避你的进攻以及扫描,当你向它原来坐标处开火,说不定它已经跑得老远了,当然这一切都不是我们所希望看到的。 我们的目的是要消灭它 : 不管他是移动或静止的。下面我们就结合方向系统与坐标系统,来锁定我们移动的目标。创造一个我们自己的高级扫描机器人。建议你在此处下载源代码 ( resource) 并看看演示效果再回到我们的文章中来。显示如图 5:

图 5
图 5
图 5
图 5
图 5

对比一下上面的数据,不管目标 GenyMove 在哪 GenyRadar 都能得到它精确的坐标。是不是有一种成就感!是的,敌人已经完全在我们的掌握之中。即使它在移动中也无法摆脱我们雷达的扫描控制。这里只是很简单举了一些例子,GenyMove 在每一个时间周期(有关时间周期的说明见的 Rock 'em, sock 'em Robocode: Round2)移动自己的位置并打印出移动后的坐标,而 GenyRadar 扫描系统不停的扫描目标,并一直追踪,同时打印出扫描到的 GenyMove 方位。关键部分在我们的 ScannedRobotEvent 事件如列表 3

列表 3:
 public void onScannedRobot( ScannedRobotEvent e ) 
    { 
        double heading = e.getBearing() +getHeading(); 
       double distance = e.getDistance(); // 求得距离
       double ager_bearing = Math.toRadians(heading % 360); // 角度转为弧度
       double genyX = getX() + Math.sin(ager_bearing) * distance; 
       double genyY = getY() + Math.cos(ager_bearing) * distance; 
       out.println("genyX:"+ Math.round(genyX)); 
       out.println("genyY:"+ Math.round(genyY)); 
    
        if( heading >= 360 ) 
            heading = heading - 360; 
        if( heading < 0 ) 
            heading = heading +360; 
        double bearing = getRadarHeading() - heading; 
        double radar_degree; 
        boolean radar_direction; 
        if( 0 <= bearing && bearing <= 180 ) 
        { 
            radar_direction = LEFT; 
        } 
        else if( bearing <= -180 ) 
        { 
            radar_direction = LEFT; 
            bearing = ( 360 + bearing ); 
        } 
        else if( bearing < 0 ) 
        { 
            radar_direction = RIGHT; 
            bearing =( -bearing ); 
        } 
        else 
        { 
            radar_direction = RIGHT; 
            bearing = (360 - bearing); 
        } 
        radar_degree = bearing * 1.3 ; // 加大每一时间周期 (tick) 的扫描范围
              
        if( radar_direction == RIGHT ) 
        { 
            setTurnRadarRight( radar_degree ); 
           execute(); 
        } 
        else 
        { 
            setTurnRadarLeft( radar_degree ); 
           execute(); 
        }

我们在代码中首先求得 GenyMove 的绝对角度,然后用扫描时雷达的绝对角度减去目标 GenyMove 的角度求得两者的角度差也即我们雷达要旋转的角度。最后利用一个小技巧 radar_degree = bearing * 1.3 使雷达在目标的范围左右摆动以扩大雷达扫描区域 . 这样不管目标往哪边移动都在自己的雷达扫描区内。

在此没有进行很详细的讲解了,我想凭你学到的方向及坐标知识很快能明白个中原理并设计出自己的高级扫描机器人来。 聪明的你可能会高兴的想,哈,我的炮管用相同的办法锁定目标,这样敌人不就没办法跑了,被我追着打。答案是错误的,雷达的扫描是条长线能直接定位到目标上 ,它到目标的时间差几乎为零,并且雷达的扫描范围比炮管大且精确。而炮管每时间周期只有 20 度,它定位目标是依靠着子弹 , 只有子弹打中了目标,才能说炮管的计算坐标是精确的。但是由于子弹 到达目标位置时需要一定的时间差,子弹本身又有速度值(20-3*power),所以要想炮管锁定目标并让子弹击中目标,我们还得经过精确的计算 , 并要预测目标可能的行动:是直线前进,还是做圆周运动,还是随机运动等等。 这些都是我们要充分考虑的因素。是不是很有挑战性 ! 这一切都在 Robocode 的世界中等待着您的创造!

三角函数基础

下面我们只是很简单的介绍了一下与 Robocode 相关的三角函数知识,要想了解详细的,大家可从家中高中代数与几何书中得到这一切。

1 .角的概念

在平面内,角可以看作一条射线绕着它的端点旋转而成的图形。如图,一条射线由原来的位置 OA,绕着它的端点 O 按逆时方向旋转到另一位置 OB,就形成角 a. 旋转开始时的射线 OA 叫做角 a 的始边,旋转终止时的射线 OB 叫做角 a 的终边,射线的端点 O 叫做角 a 的顶点。习惯上,我们把按逆时针方向旋转而成的角叫做正角;按顺时针方向旋转而成的角叫做负角 . 所有与 a 终边相同的角包括 a 在内,可以用式子表示:a+K*360 度 , 对应到 Robocode 的方向系统中,只要我们以机器人的 heading 方向做射线,延长到与屏幕交点处的角度就是我们机器人的 heading 角度。

2. 直角三角函数

在△ ABC 中,∠ a 为直角,我们把锐角 A 的对边与斜边的比叫做∠ A 的正弦,记作 sina;锐角 a 的邻边与斜边的比叫做∠ a 的余弦,记作 cosa,即

sina= 对边 BC/ 斜边 AB

cosa= 邻边 AC/ 斜边 AB

3. 单位圆和三角函数线

半径为 1 的圆叫做单位圆。设单位圆的圆心与坐标原点重合,则单位圆与 x 轴的交点分为别为 A(1,0)、A ′ (-1,0),与 y 轴的交点分别为 B(0,1)、B ′ (0,-1)。设角 a 的顶点在圆心 O,始点与 x 轴的正半轴重合,终边与单位圆相交于点 P,过点 P 作 PM 垂直 x 轴于 M,则由直角三角函数的定义可知 :OM=cosa,MP=sina ,点 P 的坐标为 (cosa,sina),即 P(cosa,sina)。其中 cosa = OM*1,sina = MP*1。Robocode 中所有有关的坐标都可用这种方法求得。

4 .弧度制

用度做单位来度量角的制度叫做角度制。数学和其他科学研究中常用另一种度量角的制度―弧度制。以角的顶点为圆心,以任意长的半径作圆把这个角所对的弧长与半径的比来衡量角的制度叫做弧度制 . 长度等于半径的弧长叫 1 弧度。这段弧所对的圆心角的大小也是 1 弧度。通常单位“弧度”省略不写。例:弧长为 1.3325。单位就是弧度。由角度和弧度两种单位之间的关系得到 :2 π弧度 =360 度 ,2/3 π弧度 =270 度 , π弧度 =180 度 ,1/2 π弧度 =90 度 , 并可推出 1 弧度 = 360 度 /2 π = 57 °即 1 弧度 = 角度 *180/Math.PI.

一般规定:正角的弧度数为正数,负角的弧度数为负数,零角的弧度数为零。这样角的集合与实数集合的元素就建立起了“一一对应”的关系。

哦,终于完了。到此为止,我们学习了 Robocode 基本原理中的方向系统和坐标系统,当你对这两个概念有一定的了解,你就可以创建自己的机器人,在战争的洗礼中不断完善自己的机器人,并适当的在你的机器人加入一些经典的战斗策略。它将越来越完美。不过不要忘了,一定要加入机器人的智能学习能力 , 只有具有超强学习能力的机器人才能无敌于天下,在下一篇我们将会看到一个高智能的机器人。


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=53119
ArticleTitle=Robocode 基本原理之坐标锁定
publish-date=11072002