AI-RCJ编程入门之一--编写C 足球机器人
大姜 发表于 - 2006-4-11 0:46:00
| 作者: Administrator |
|
在这里向大家介绍如何用C语言编写机器人。在这个过程中,将向大家介绍如何用AIROBOT 配套的代码编辑器来快速的创建机器人,并穿插讲解一些AIROBOT中的基本概念。
AI-RCJ编程入门之一--编写C 足球机器人
创建机器人 通过工具-〉创建机器人项目 可以创建一个新的机器人。选择该菜单项后将出现创建机器人对话框。在第一个页面中可以指定项目类型(编程语言),项目名字等信息。  这里我将这个机器人项目命名为First,项目类型选择C(Normal)。保留其他设置,点击完成按钮,这样一个新的机器人项目就创建好了。(具体细节可参考AIROBOT使用指南里面的说明) 项目创建完后将打开代码编辑器(CodeCanvas),在这里可以直接编辑和编译机器人。该代码编辑器特别适用于AIROBOT的初学者,它与AIROBOT很好的集成在了一起,降低了初学者创建和编译机器人的难度,使得大家可以更快的编写出自己的机器人。  在代码编辑器左边的项目列表面板中将出现刚刚建立的机器人项目。沿着机器人项目的树形结构,你将看见自动生成的机器人文件First.c。系统自动生成了机器人程序的框架代码,您只要在相应的地方添加自己的代码,就可以轻松的编写机器人了。 让机器人动起来 onTick函数是First.c中定义的一个函数,只要在onTick中加入相应的控制语句,就可以让机器人完成相应的动作。在onTick函数中加入这样一条语句: void onTick(struct TickAction* action) { move(8); } 在编辑完机器人源代码后,必须对源文件进行编译和连接,选择 构建-〉构建项目(F7),这样对源文件的编译和连接就完成了。代码编辑器下方的控制台会输出相应的编译信息,如果有语法错误的话,在这里可以看到相应的提示,对代码进行修改。 在AIROBOT中新建一场比赛,在包列表中选择first,然后在机器人列表中选择 First,选择我们刚刚编写的机器人参加比赛(如果没有看到我们刚刚编写的机器人,请按F5刷新机器人列表)。  你会发现机器人会一直向前走,一直走到场地边缘。这是由于onTick函数中的move(8)语句造成的,move(8)的意思就是设置机器人移动的速度为8,这样机器人就会以这个速度向前移动。相反,如果把8改为 -5,机器人就会以5的速度向后移动。 系统的执行是离散的,在每个时钟周期,系统都会调用机器人的onTick函数,意思是让机器人执行当前的操作。拿刚才的例子来说,在每个时钟周期,机器人的onTick函数都会被系统调用,那么onTick中的move(8)也就是每个时钟周期执行一次。这样就相当于每个时钟周期机器人都以8的速度向前移动,我们看到的效果就是机器人一直向前移动。 当你再次打开代码编辑器的时候,可以打开以前创建的机器人项目。方法是 项目-〉打开项目,然后选择与你的机器人对应的项目配置文件文件就可以了,例如First.participant.xml(以participant.xml为后缀)。 robots目录是系统默认的存放机器人的目录(开发目录),刚才生成的机器人文件存放在robots\First目录下,在该目录下有First.c文件,这就是你的机器人源文件,First.dll是First.c编译的结果,这个.dll是 AIROBOT所能识别的机器人。 让机器人踢球 现在的机器人只会一直向前走,走到场地边缘就停止了,让我们来修改机器人的代码,实现让机器人去踢球。 修改过的First.c如下: …… public void onTick(struct TickAction* action) { moveTo(getBallX(), getBallY()); } …… moveTo函数的作用是控制机器人朝指定的坐标点移动,getBallX和getBallY函数返回球的X和Y坐标。在每个单位时间,机器人都向球所在的位置运动,机器人就会去撞球,这就达到了让机器人踢球的效果。当然,这个机器人可能会将球踢进自己的球门,我们必须加入一些逻辑,根据机器人的进攻方向来控制机器人(getAttack函数返回机器人的进攻方向),才能避免机器人将球踢进自己的球门。 Action处理函数 我们可以看到,First.c中定义了一系列以on开头的函数,这些函数叫作Action处理函数。这些函数在特定的时刻由系统调用,例如onRoundBegin函数在一轮比赛开始的时候由系统调用,onHitWall函数在机器人撞倒场地边缘的时候由系统调用。 系统的运行是离散的。系统内部有一个时钟,机器人可以通过getTime函数得到当前的时间,系统根据这个时钟调度来机器人运行。在每一个时钟周期,系统会根据当时的情况有选择的调用机器人的这些Action 处理函数,让机器人执行各种操作。正如前面的例子所演示的那样,我们可以在这些Action处理函数中加入控制机器人的各种语句,指挥机器人做各种动作(如前进,转动),在这些Action处理函数结束后,系统就会根据机器人所执行的动作更新机器人的状态(如方向,坐标)。 机器人状态的变化也是离散的,拿机器人的方向来说,如果机器人的转动速度是10度/单位时间,现在的方向是10度,那么在下一个单位时间机器人的方向才会发生变化,变成20度,之所以这是离散的是因为方向没有连续的变化,而是直接从10度变到了20度。 onTick函数是一个比较特殊的Action处理函数,这个函数在每个时钟周期都会由系统调用,而其他的Action处理函数并不是每个时钟周期都会由系统调用的,拿onHitWall来说,只有当机器人撞倒场地边缘的时候才会由系统调用。在一个时钟周期当中,可能会有多个Action处理函数被系统调用,在普通情况下,只有一个onTick函数会被系统调用。当有多个Action处理函数被系统调用的时候,onTick总是最后一个被调用的。由于onTick函数的这些特点,在一般的情况下,都是把控制机器人的各种语句(如move)放在onTick函数中,其他的Action处理函数只是起到辅助的作用。 机器人编程接口简介 编写c/c++机器人时所使用的头文件存放在c/include中。 其中最主要的是airobot/c/SimpleRobot.h文件,这个头文件声明/间接包含了一系列常用的函数。 常用的控制函数:move(控制机器人移动),turn(控制机器人转动)等。 常用的获取信息的函数:getTime(得到当前的时间),getX(得到自己当前的X坐标) getHeading(得到自己当前的方向)等。 在机器人编程的过程中会用到很多数学知识,在commons/Math.h中声明了一系列常用的数学函数。如计算两点间的距离,两点连线的方向等。 用户可以通过阅读C编程接口来获得更加详细的信息。 实现一个绕墙走的机器人 要使机器人绕墙走,最简单的方法就是当机器人撞倒墙的时候,先使它停止移动,转动90度,然后再接着移动。这个机器人叫Walls。 控制机器人转动的函数是void turn(double),参数是机器人转动的速度。比如执行turn(turnVelocity)语句,那么机器人将以turnVelocity的角速度旋转。注意,系统使用的是弧度制,机器人转动的最大角速度是10度/单位时间,参数大于零则逆时针旋转,小于零则顺时针旋转,如果参数大于机器人转动的最大转动角速度的话,机器人则以最大角速度旋转。 在这里我们将用到MathUtils.h中定义的double bearing(double heading, double base)函数,该函数返回heading相对base的夹角。用这个函数可以方便的计算出要转动到某个方向时需要转动的度数。 一种错误实现方案 按照一般的思维逻辑,很容易采用类似下面将要介绍的实现方案。由于转向的动作是在撞墙的时候发生的,所以最直接的方案就是在onHitWall函数中让机器人转弯。 Wall.c的代码如下所: void onRoundBegin(struct RoundBeginAction* action) { move(8); } void onHitWall(struct HitWallAction* action) { //headingTo表示要转动到的方向 double headingTo = getHeading()+PI/2; //停止移动 move(0); while(1) { //计算要转动的角度 double angle = bearing( getHeading(), headingTo); //执行转动 turn(angle); //当转到headingTo时退出循环 if(fabs(angle)<0.0001) break; } //继续向前移动 move(8); } onRoundBegin函数在一轮战斗开始的时候被调用,该函数中的move(8)使得机器人一开始就一直向前走,直到撞倒墙。 在onHitWall函数中,首先用一个变量headingTo记录要转动到的度数,也就是当前的方向加上PI/2,getHeading()返回机器人的当前方向。然后执行move(0)停止机器人的移动。接着的一个循环就是使机器人转动到headingTo这个方向上。最后执行move(8)让机器人继续向前移动。 在循环中,首先用bearing函数计算要转动的度数,接着执行turn()操作,让机器人转动。注意,turn函数的参数是转动的速度,而不是转动的角度,这里将bearing计算出的角度直接传给turn函数,是因为当要转动的度数大于机器人转动的最大角速度时,把要转动的度数传入turn函数将使机器人以最快的速度转动,当要转动的度数小于机器人转动的最大角速度时,把要转动的度数传入turn函数将使机器人精确的转动指定的度数。当要转动的度数为0时(理论上是用angle==0来比较,不过考虑到浮点数的误差和编译器的实现,使用fabs(angle)<0.0001来处理),则说明已经完成了转动,就可以退出循环了。 从逻辑上说这种方案是没错的,代码也可以顺利的通过编译,在运行的时候,一开始机器人运动没问题,可是当机器人撞倒墙时就不正常了,机器人突然死亡。在控制台中可以看到系统输出的提示信息,机器人超时,被强行中止了。 这是由于onHitWall中的循环造成的。前面已经提到过,在每个单位时间特定的Action处理函数会由系统进行调用。这种运行方式的前提是机器人的Action处理函数必须在一个单位时间内返回,只有这样系统才能在每个单位时间对机器人Action处理函数进行调用,然后更新机器人的状态。在onHitWall的循环中,由于转动90度需要9个单位时间,所以onHitWall没有在规定的时间结束,这违反了系统的运行规则,机器人被系统强行中止了。 请大家记住这一点,不要在Action处理函数中,试图在完成超过一个单位时间的操作后再返回,这将使机器人被系统强行结束。 一种正确的解决方案 Walls.c的代码如下所: //记录要转动到的方向 double headingTo; void onTick(struct TickAction* action) { double angle = bearing(headingTo, getHeading()); if(fabs(angle)>0.0001) { move(0); turn(angle); } else { turn(0); move(8); } } void onHitWall(struct HitWallAction* action) { headingTo += PI/2; } 在class Walls中定一个变量 headingTo,记录要转动到的方向。在onHitWall函数中,只是将这个变量headingTo加上PI/2,以此来修改移动的方向。 在onTick函数中,首先计算机器人的当前方向和headingTo的夹角angle,如果fabs(angle)>0.0001,则说明机器人还没有转动到指定的方向上,则让机器人停止移动,执行转动操作。反之则说明机器人已经转动到了指定的方向上,则让机器人停止转动,继续向前移动。这样就实现了绕墙走的目的。 一些经验 机器人主要由各种Action处理函数组成,一种比较好的编程方式是在除onTick函数以外的各种Action处理函数中收集所需的各种信息,然后在onTick函数中对这些信息进行加工处理,最后根据当前所掌握的信息来指挥机器人的运动。以这种方式编写的机器人结构比较清晰,容易对其进行扩展。 println函数是调试机器人的有力工具,可是println函数不能接受像double, float, int, long等类型的参数,不过我们可以用sprintf函数来完成数值类型的输出,做法如下: char buffer[50]; //字符串缓冲 sprintf(buffer, “my heading is : %lf”, getHeading()); println(buffer); 首先要申明一个字符串缓冲buffer,然后用sprintf将要输出的数值格式化到buffer中,这样数值就转变成了字符串,当然还可以加入一些说明性的信息。然后把buffer作为参数调用println就可以了。 | |