Robocode基本原理之坐标锁定
2009-12-21 00:00:00 来源:WEB开发网导论
前面我们了解了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
“动静机器人”测试法
好了,我们知道了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
坐标探测
知道了对方的距离,知道了整个坐标系统。我们就来锁定我们的目标Geny.我们先来看看图3所示:
图3
列表1:
Geny | GenyTrack |
X:303 | genyX:303 |
Y:128 | genyY: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
黑线条为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
对比一下上面的数据,不管目标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基本原理中的方向系统和坐标系统,当你对这两个概念有一定的了解,你就可以创建自己的机器人,在战争的洗礼中不断完善自己的机器人,并适当的在你的机器人加入一些经典的战斗策略。它将越来越完美。不过不要忘了,一定要加入机器人的智能学习能力,只有具有超强学习能力的机器人才能无敌于天下,在下一篇我们将会看到一个高智能的机器人。
更多精彩
赞助商链接