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

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

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

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

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

runRound()内部是一个循环,直到该局结束。

     while  ( ! battleOver)  ... {
        // ...
    }

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

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

     synchronized  (r)  ... {
        // This call blocks until the
        // robot's thread actually wakes up.
        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(Battle b)  ... {
        if (isSleeping) ...{
            // Wake up the thread
            notify();
            try ...{
                wait(10000);
            } catch (InterruptedException e) ...{}
        }
    }

此处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  ... {

    public void run() ...{
        while (true) ...{
            ahead(100); // Move ahead 100
            turnGunRight(360); // Spin gun around   
            back(100); // Move back 100
            turnGunRight(360); // Spin gun around
        }
    }
}

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

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

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

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

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

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

RobotPeer.tick()
---------------------
RobotPeer.tick()中最重要的程序块是:

     synchronized  ( this )  ... {
        // Notify the battle that we are now asleep.
        // This ends any pending wait() call in battle.runRound().
        // Should not actually take place until we release the lock in wait(), below.
        notify();
        isSleeping = true;
        // Notifying battle that we're asleep
        // Sleeping and waiting for battle to wake us up.
        try ...{
            wait();
        } catch (InterruptedException e) ...{
            log("Wait interrupted");
        }
        isSleeping = false;
        // Notify battle thread, which is waiting in
        // our wakeup() call, to return.
        // It's quite possible, by the way, that we'll be back in sleep (above)
        // before the battle thread actually wakes up
        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();
            // Wait for the robot to go to sleep (take action)
            r.wait(waitTime);

        } catch (InterruptedException e) ...{
            log("Wait for " + r + " interrupted.");
        }
    }
     if  ( ! r.isSleeping())  ... {
        log(" " + r.getName() + " still has not started after " + waitTime + " ms... giving up.");
    }

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,代码

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

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

  1. YOLOv5Face YOLO5Face人脸检测论文及代码简析

    YOLO5face人脸检测模型论文和代码简析 YOLO5Face模型分析 论文及源码下载 论文创新点 实验结果 下载代码跑起来 调整数据集 训练完成之后检验结果 一点点代码简析 文件结构 data m ...

  2. [2021-CVPR] Jigsaw Clustering for Unsupervised Visual Representation Learning 论文简析及关键代码简析

    [2021-CVPR] Jigsaw Clustering for Unsupervised Visual Representation Learning 论文简析及关键代码简析 论文:https:/ ...

  3. 关于php车服务论文,「PHP」行车服务app后端代码简析

    之前发布了一篇关于我的 行车服务 app iOS 端代码简析的文章:文章地址. 此篇是对这个项目后端 iOS端代码地址: iOS代码,PHP代码.如果你觉得有帮助,希望能够点个 Star ,感谢~ 笔 ...

  4. Learning with Noisy Correspondence for Cross-modal Matching 文献翻译 代码简析

    Learning with Noisy Correspondence for Cross-modal Matching 基于噪声对应的跨模态匹配学习 Learning with Noisy Corre ...

  5. ruoyi框架默认的导出Excel功能代码简析

    ruoyi框架默认导出Excel功能 项目使用的是RuoYi Bootstrap多模块版本4.7.2,启动项目后会有默认的导出功能.包括使用ruoyi自带代码生成器,都会有导出功能的附带.接下就讲解一 ...

  6. WinForm 自动完成控件实例代码简析

    在Web的应用方面有js的插件实现自动完成(或叫智能提示)功能,但在WinForm窗体应用方面就没那么好了. TextBox控件本身是提供了一个自动提示功能,只要用上这三个属性: AutoComple ...

  7. RAP开发入门-运行过程简析(三)

    今天通过标准的RAP程序来简单分析下RAP的启动过程 1.新建一个标准的rap plugin-in 项目: 得到的项目结构大概如下: run confi..->..add bundle(配置好b ...

  8. django的manage.py代码简析

    django中创建一个project之后,就会创建一个以项目名称命名的文件中,文件夹中包含了一个同名文件夹和一个manage.py文件.比如: 之后如果要启动服务.同步数据库等操作,都是用python ...

  9. DirectShow程序运行过程简析

    这段时间一直在学习陆其明老师的<DirectShow开发指南>一书,书中对DirectShow的很多细节讲解清晰,但是却容易让人缺少对全局的把握.在学习过程中,整理了关于DirectSho ...

最新文章

  1. GPU—加速数据科学工作流程
  2. 经典排序算法之直接选择排序
  3. matlab 高维数组赋值,MATLAB之三(高)维数组的创建
  4. 怎么使用python中的字典_Python中字典的使用
  5. 我的MVC之旅(3)--------MVC Music Store 第三篇 Views and ViewModels [翻译]
  6. 让Microsoft Web Application Stress Tool 支持非80端口的站点测试
  7. C#LeetCode刷题之#434-字符串中的单词数​​​​​​​(Number of Segments in a String)
  8. nsinteger转float_int、NSInteger、NSNumber和NSString以及相互转换
  9. centos u盘安装_利用Win32 Disk Imager 实现U盘刻录ISO
  10. mysql 主从复制延迟_什么情况会导致MySQL主从复制延迟?
  11. 恒强制版系统980_恒功率、大功率及无线充电,充电设备面临哪些趋势和挑战?...
  12. oracle财务数据权限思考
  13. 修改 Apple ID 的国籍
  14. w10怎么改mysql用户名_win10用户名彻底改为英文的详细操作步骤
  15. ffmpeg 转换flv压缩大小_ffmpeg 视频压缩 转换
  16. SpringBoot版本升级
  17. Solver Prototxt - 参数说明
  18. tensorflow+python flask进行手写识别_python+flask搭建CNN在线识别手写中文网站!简直太屌了!...
  19. 【YOLOv3原文+翻译】YOLOv3:An Incremental Improvement
  20. .Net Core 分布式微服务框架介绍 - Jimu

热门文章

  1. 苹果宣布推出全新辅助功能 Apple Watch支持辅助触控
  2. ORA-00206 ORA-00202 ORA-27061
  3. 如何解决Kerberos问题: Server has invalid Kerberos principal: hdfs/host2@****.COM
  4. 服务器出错的原因有哪些 原
  5. Office办公软件各种神操作,看到第1个就傻眼了,建议收藏
  6. 地图数据的存储探索(GIS)
  7. 洛谷 10月 csp-s 模拟赛 T1,T2解析及代码
  8. 揭秘篇:架构腐化之谜
  9. Mysql compact行格式
  10. javascript之传智播客