RoboCup智能机器人足球教程(二)
运行方式
RoboCup2D仿真平台通过一个服务端,若干客户端联系而成,同时通过监视器进行画面播放。当启动服务端后,客户端通过改写程序内部的client.cpp文件来实现球员逻辑,编译后即可与服务端进行连接,从而进行比赛。
因此我们要做的主要工作就是改写client.cpp,以实现球员逻辑。该文件位于Robocup/rcssserver-15.2.2/src/client.cpp,当改动完毕后,进入rcssserver-15.2.2文件夹,打开终端,执行
make
即可把客户端修改编译成功,从而进行比赛。
函数介绍
在client.cpp中,我们需要修改的,是以下几个函数,其他函数均不用修改。
main()
该函数为主函数,主要功能是接收参数和初始化client类,下面着重讲一些重要的代码片段,其他代码均不需要过多修改。
std::string server = "localhost";
int port = 6000;
这两条语句是设定服务器在本机上,端口号为6000,在测试阶段,可以不做修改,如果比赛中服务器不在本机上,那么就需要修改为相应的IP地址了。
for (int i = 0; i < argc; ++i) { // 进行id分配
if (std::strcmp(argv[i], "-server") == 0) {
if (i + 1 < argc) {
server = argv[i + 1];
++i;
}
} else if (std::strcmp(argv[i], "-port") == 0) {
if (i + 1 < argc) {
port = std::atoi(argv[i + 1]); // 端口号
++i;
}
}
if (std::strcmp(argv[i], "-id") == 0) { // id
if (i + 1 < argc) {
iPlayerId = std::atoi(argv[i + 1]);
++i;
}
}
if (std::strcmp(argv[i], "-sidel") == 0) { // 是左边的还是右边的
iSide = 1;
}
if (std::strcmp(argv[i], "-sider") == 0) {
iSide = 2;
}
}
这些代码是解析参数,在参数中获得队员的ID和所属队伍,也可以修改比较条件来创造自己的参数。
client = new Client(server, port);
client->run();
初始化客户端并执行。
run()
该函数是用来初始化球员位置,和开启消息循环,注意,一个客户端只能初始化一个球员,要想初始化11个球员,需要打开11个客户端。
if (iSide == 1) { // 初始化所有球员
if (iPlayerId == 1) {sprintf(command, "(init team1 (goalie))");}// 第一个球员是守门员
else {sprintf(command, "(init team1)");}
} else if (iSide == 2) {
if (iPlayerId == 1) {sprintf(command, "(init team2 (goalie))");}
else {sprintf(command, "(init team2)");}
} else {return;}
if (!sendCmd(command)) return;
if (iPlayerId == 1) {sprintf(command, "(move -45 0)");} // 球员的放置,守门员单独放到一个地方
else {sprintf(command, "(move -3 0)");} // 其他的都放到一个地方
if (!sendCmd(command)) return;
上面代码是根据ID来判断是否生成守门员或者其他球员(守门员单独用一个逻辑,其他球员用同一种逻辑。),并将球员放置在球场上合适位置。
sendCmd()函数的作用是把命令发送给服务器,从而实现球员的各种动作,这是RoboCup程序的运作方式,当球员要执行某种动作的时候,只需要将相应的字符串发送给服务器即可,例如奔跑的命令为(dash 100),踢球的命令为(kick 100),所有命令的解释在百度网盘的文件夹下,地址在这里
messageLoop();
本代码段为开启消息循环,开启消息循环后,将接收来自服务器和本地终端上的信息,通过解析这些信息,从而编写球员逻辑。
parseMsg()
最重要的函数,用来实现球员逻辑,通过函数传来的参数msg(发送过来的消息字符串)和len(字符串长度),从信息中截取有用信息,并做下一步动作。
发送来的最重要的信息有如下两种:
(sense_body 0 (view_mode high normal) (stamina 8000 1) (speed 0) (kick 0) (dash 0) (turn 0) (say 0))
该信息由服务端发送而来,大约每个时间周期发送一次(一个时间周期约几十毫秒),用于球员认知自身状态,该信息以sense_body开头,其后紧随的数字为比赛进行时间(为0表示还未开始比赛),其他信息意义如下:
- stamina 球员体力值,8000为最大,执行动作后体力值会降低,降低到一定程度后球员变红,运动能力大幅下降,但经过休息后会回复。
- speed 球员此刻的近似速度。
- kick 球员总共踢了多少次球
- dash 球员总共冲锋了多少次
- turn 球员总共转身了多少次
- say 球员总共说了多少次话
更多信息定义,请参照百度网盘文件夹中附带的ppt
(see 0 ((flag c) 3 0 0 0) ((flag r t) 65.4 -31) ((flag r b) 65.4 31) ((flag g r b) 55.7 7) ((goal r) 55.7 0) ((flag g r t) 55.7 -7) ((flag p r b) 43.8 27) ((flag p r c) 38.9 0) ((flag p r t) 43.8 -27) ((ball) 3 0 0 0) ((line r) 55.7 90))
该信息由服务端发送而来,大约每个时间周期发送一次(一个时间周期约几十毫秒),是球员的视野,该信息以see开始,其后紧随的数字仍然为为比赛进行时间,其后的信息是它所看到的球员,球(如果它能看见),和球场的标志,其后紧随的两个数字分别是极坐标下与自身的距离和角度(顺时针正方向),关于球场标志,可参照下图:
if (iPlayerId == 1) { // 守门员的代码
if (std::strncmp(msg, "(see ", 5)) {
return;
}
double ball_dist = 0; // 球的方向
double ball_dir = 0; // 球的角度
char command[20]; // 命令缓冲
char *pball;
pball = strstr(msg, "(ball)"); // ball是msg的一个子串,是则返回"ball"地址
if (pball == 0) {
sprintf(command, "(turn 60)"); // 转60度
if (!sendCmd(command)) return; // 发送数据
return;
}
if (std::sscanf(pball, "(ball) %lf %lf", &ball_dist, &ball_dir)!= 2) { // 没有辨别出球的方位和角度
printf("get ball error\n"); // 获取球失败
return;
}
if (ball_dist < 3) { // 如果与球的距离小于3
sprintf(command, "(catch %lf)", ball_dir); //发送catch命令以捕获球
if (!sendCmd(command)) return; // 永远的发送数据
return;
}
if (ball_dir > 10 || ball_dir < -10) { // 如果与球的角度相差太大
sprintf(command, "(turn %lf)", ball_dir); // 转向球的方向
if (!sendCmd(command)) return;
return;
}
}
这些是守门员的所有逻辑,守门员(默认ID为1的球员是守门员)的逻辑为:如果球离自己比较近,就前去扑球,并朝对面踢出,代码注释比较详细,就不详细解释了。
RoboCup智能机器人足球教程(一)
RoboCup智能机器人足球教程(三)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)