智能机器人编程游戏robocode的运行代码简析

本文深入剖析了Robocode游戏的运行机制,包括其主线程Battle.run()的工作流程,以及子线程RobotPeer如何通过tick()与wakeup()机制与主线程同步。探讨了机器人初始化过程、每回合的动作执行细节,以及提出了一种简化实现方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


智能机器人编程游戏robocode的运行代码简析
金庆
2007.6.1

阅读robocode1.3的源代码,查看运行的原理。

(转载请注明来源于 金庆的专栏)

主线程Battle.run()
-------------------
主线程是Battle.run(), 循环进行多局的较量。

每一局初始化后,主要是调用runRound()进行战斗。

runRound()内部是一个循环,直到该局结束。
while ( ! battleOver) ... {
//...
}

循环内,处理子弹移动,死亡事件,雷达扫描,结束判断,重画战场及配音播放,最后wakeupRobots()进入各个机器人的运行片。每个循环称为一个帧。wakeupRobots()应该保证短时间内会执行完毕,即每个时间片的机器人运行是可控的。

wakeupRobots()内部对每个运行中的机器人r(RobotPeer)执行:

synchronized (r) ... {
//Thiscallblocksuntilthe
//robot'sthreadactuallywakesup.
r.wakeup(this);

if(!r.isSleeping())
r.wait(TURN_TIME);
}

...
setSkippedTurns...

r.wakeup()之前线程r应该已经处于睡眠状态,r.wakeup使之运行一个回合,之后机器人仍应回到睡眠状态,即运行到tick()函数中的wait()阻塞状态。如果没有,r.wait(TURN_TIME)就给机器人一点时间进入睡眠。之后还有skippedTurns设置,表示该机器人若不能在指定时间内进入睡眠,如某个事件处理时间太长,无法在一个回合的时间内完成运行,该回合将跳过,并以事件的方式通知机器人。对机器人来说,该执行机会称为一个回合(Turn)。每帧都是一回合。

RobotPeer.wakeup()实现如下:
public synchronized void wakeup(Battleb) ... {
if(isSleeping)...{
//Wakeupthethread
notify();
try...{
wait(
10000);
}
catch(InterruptedExceptione)...{}
}

}

此处notify() + wait() 唤醒RobotPeer线程并允许它运行,直到它主动调用tick()来再次进入睡眼(Sleeping),或者10s内还没有调用tick()则超时退出。从下面tick()代码可以看到超时退出时isSleeping == false, 该状态在r.wakeup()之后用来判断是否错过这一回合并设置skippedTurns。


机器人线程RobotPeer
----------------------
RobotPeer本身是Runnable线程。它的run()函数大约如下:

if (robot != null ) ... {
robot.run();
}

for (;;) ... {
tick();
}

它表示一直执行robot的run(),如果robot.run()退出,就无限次执行tick()。其中robot就是用户自己实现的机器人。循环通过异常来中断,如死亡,获胜,等等。

(Robot, AdvancedRobot是用户自定义机器人的基类,实现了Runnable接口。可是Robot.run()是由RobotPeer.run()来调用的,没有使用到Runnable接口。Robot实现Runnable接口是多余的。)

其中RobotPeer.tick()是一个关键性的函数,它表示机器人本回合的命令完毕,可以进入下一回合。robot.run()中一般是通过间接的tick()调用来进入下一回合,未能及时进入下一回合将错过一个回合。

例如:
public class MyFirstRobot extends Robot ... {

publicvoidrun()...{
while(true)...{
ahead(
100);//Moveahead100
turnGunRight(360);//Spingunaround
back(100);//Moveback100
turnGunRight(360);//Spingunaround
}

}

}



其中ahead()和back()将调用peer.move(),而RobotPeer.move()如下:

public final void move( double distance) ... {
setMove(distance);
do...{
tick();
//Alwaystickatleastonce
}
while(distanceRemaining!=0);
}


turnGunRight()类似,调用RobotPeer.turnGun(),同样进入tick()循环。

实际上,用户的机器人的任何需要时间才能完成的动作,都是进入了一个循环调用tick(),直到动作完成。

而AdvancedRobot机器人使用一系列如setAhead()这样的非阻塞性调用,然后用execute()交出运行权,来达到更及时有效的控制,其实质就是execute()调用tick()。

tick与turn的概念是一致的,表示机器人的一步,robocod是和下棋一样回合制的。


RobotPeer.tick()
---------------------
RobotPeer.tick()中最重要的程序块是:
synchronized ( this ) ... {
//Notifythebattlethatwearenowasleep.
//Thisendsanypendingwait()callinbattle.runRound().
//Shouldnotactuallytakeplaceuntilwereleasethelockinwait(),below.
notify();
isSleeping
=true;
//Notifyingbattlethatwe'reasleep
//Sleepingandwaitingforbattletowakeusup.
try...{
wait();
}
catch(InterruptedExceptione)...{
log(
"Waitinterrupted");
}

isSleeping
=false;
//Notifybattlethread,whichiswaitingin
//ourwakeup()call,toreturn.
//It'squitepossible,bytheway,thatwe'llbebackinsleep(above)
//beforethebattlethreadactuallywakesup
notify();
}

大量的注释表明这段代码有点搞脑筋。

这段代码与RobotPeer.wakeup()是用synchronized标为互斥的。Battle.wakeupRobots()中r.wakeup()也是用r对象同步的。

Java概念:synchronized(r)表示以r对象为锁进行同步,使同一时刻只能有一个同步块运行。同步块中可以用wait(ms)暂时释放锁,允许其它同步块运行。wait(ms)可以超时退出来抢回同步锁,或等待其它同步块调用notify()并释放锁后中断。

RobotPeer线程一旦启动,正常情况下应该最终调用tick(),并停在其中的wait()语句上。tick()中的wait()是交出CPU并无限期的等待,直到r.wakeup()中的notify() + wait(10000)。r.wakeup()让RobotPeer从wait()中醒来,运行一圈后再次进入wait()。

tick()的结构是两个notify()中间夹一个wait()。机器人绝大部分时间是在tick()中的wait()处阻塞,等待Battle的唤醒,此时isSleeping == true。

第一个notify()中止Battle.wakeupRobots()中的wait(),第二个notify()中止RobotPeer.wakup()中的wait()。

机器人的wakeup()与tick()是互斥的,它们通过wait/notify来交替运行。


机器人的启动Battle.setupRound()
----------------------------------
robot的开始运行:Battle.setupRound()
对于每个RobotPeer r, 先同步r, 再开始运行r,

synchronized (r) ... {
try...{
log(
".",false);
r.getRobotThreadManager().start();
//Waitfortherobottogotosleep(takeaction)
r.wait(waitTime);

}
catch(InterruptedExceptione)...{
log(
"Waitfor"+r+"interrupted.");
}

}

if ( ! r.isSleeping()) ... {
log(
" "+r.getName()+"stillhasnotstartedafter"+waitTime+"ms...givingup.");
}

waitTime最大是10s,10s内robot必须调用tick()来将自己的状态设为isSleeping, 并交出执行权。否则认为该robot无法启动。


更简单的实现方式
------------------
synchronized/wait/notify,多么乱的一团线啊。从非Java程序员的眼光来看,这只是Battle和各Robot子线程之间的简单同步,没必要用这么难看的实现方式。

Battle只需要通知大家:现在第n回合开始!各个Robot线程只需执行一系列set后及时通知Battle: 本回合我的动作结束。Battle等待所有Robot的回合动作结束,或超时结束,执行实际的移动,碰撞等,再开始下一回合。主线程与子线程之间仅仅需要开始和结束两个事件来同步,如果子线程死循环不会影响到主线程继续下一回合。根本不需要wakeup机制。


参考
[1] Robocode的运行机制
[2] Robocode的线程与执行次序

Tag: runround,robocode,battle,robot,tick,java,运行,线程,robotpeer,代码

(转载请注明来源于 金庆的专栏)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值