Roborescue仿真救援项目入门总结

作为一个比赛,我们的比赛任务是在模拟的地震火灾现场火势蔓延和建筑倒塌的恶劣环境下,

  • 利用有限的救援人员在给定的时间内尽可能地救出多的市民(存活的市民数量越多、总生命值越大,得分越高)
  • 利用有限多的消防员来进行灭火操作,尽量减少烧毁建筑的面积(未被完全烧毁的建筑越多,得分越高,如果仿真周期结束时某个建筑在燃烧但是没有烧毁,则不减分)。
1 人类:

救援人员包括警察(蓝色),救护员(白色),消防员(红色)
普通市民的颜色为绿色
各自的任务:

在这里插入图片描述

对人类而言,有以下几个常用的重要属性,罗列如下:

属性名含义
buriedness掩埋深度,埋得越深,需要的救援时间越久
damage每个周期损失了多少生命值
HP生命值,初始为10000,受伤的话则会降低,到达避难所则会保持不变,不会上升
position记录当前实体所在地的EntityID,可能是在建筑内、道路上、避难所内、救护员身上
X、Y当前实体所在位置的笛卡尔坐标
2.区域(Area)

区域包括两种类型:建筑和道路
一座建筑有一个EntityID值,而道路则不同,一段道路可能是有多个道路段拼接而成,道路段形状不一定一样,形状不规则

一些属性:

属性名含义
blockades该区域的障碍的list,因为路障形状不规则,可能一个area内有多个路障
edges该区域边界线的列表,一个区域是一个多边形,有边界的存在,edges存储的便是这些边(项目里有一个Edge类)的List
neighbours存储的是与当前区域相邻的Area们,可供之后操作
X、Y区域中心所在位置的笛卡尔坐标
2.1路障

路障是对比赛成绩影响极大地一个方面,我们需要尽快安排警察将主干道路清理通畅,这样市民、消防、救护员的动作才能正常执行。
清理路障主要有两种方式:区域清理和矩形框清理方式

区域清理: 警察对目标路障执行清理动作,路障开始以一定速度逐渐向中心收缩变小,最终消失(耗时很久)
矩形框清理 对当前目标路障,警察行进前方生成一个矩形框,在矩形框范围内的障碍会消失,没有被矩形框覆盖的障碍则不会变化(速度相对很快)

代码策略:
仿真初期: 由于此时的主要目标是尽快将各大主干道疏通,让消防能尽快前往着火建筑灭火、救火员尽快前去救人,所以我们采用矩形框清障方式,疏通主干道路
仿真中后期:由于此时大部分的道路已经可以通行了,但是由于矩形框清障时可能会在路上留下毛刺型障碍,可能还是会卡住其余人类,此时我们将清障方式改为区域清障,保证每条道路障碍被完全清理干净

关键问题在于:如何判断什么时候开始转换为区域清障方式,这也是亟待我们解决的问题

2.2建筑
  • 一般未着火建筑为灰色,如果着火的话会颜色会由黄色逐渐向红色加深,最终烧毁的话则变成纯黑色
  • 如果某一建筑被消防员浇水的话则为蓝色。
    在这里插入图片描述
    建筑的属性值如下:
属性名含义
temperature掩埋深度,埋得越深,需要的救援时间越久
damage每个周期损失了多少生命值
HP生命值,初始为10000,受伤的话则会降低,到达避难所则会保持不变,不会上升
position记录当前实体所在地的EntityID,可能是在建筑内、道路上、避难所内、救护员身上
X、Y当前实体所在位置的笛卡尔坐标

几种特殊的不会着火的建筑:

名称作用
避难所人类实体在里面生命值不会降低,消防员们可以在里面补水,救护员会将受伤市民移动到此处
救护中心实现救护员的总体统筹管理
消防中心实现消防员的总体统筹管理
警察局实现警察的总体统筹管理
消防栓水量无限,数量较多,随机分布在地图内,同一时间仅可以供一个消防员补水
汽油站重点关照目标,如果火势蔓延到汽油站的话会导致汽油站爆炸,瞬间汽油站周围的建筑都会剧烈着火**
3. 比赛得分计算
3.1 Time - 周期

一般的,每场比赛的仿真周期为300,300周期结束之后仿真器停止工作

3.2 Score—比赛得分

这是非对抗性比赛,所以比赛结果按照谁跑的分高来评判
比赛的时候一支队伍需要跑很多张图,以总的积分来决定谁更优秀。

比赛得分的计算方法:

在这里插入图片描述

  • V 表示得分
  • P 表示存活市民的数量,
  • S 表示仿真结束时所有市民健康值之和
  • Sint 表示仿真开始阶段所有市民健康值之和
  • B 表示比赛结束时未烧毁建筑物总面积
  • Bint 表示仿真开始阶段所有建筑物总面积

由上面的表达式可知:

  • P所占的权值非常非常地大,活着的市民数量是决定得分的关键。所以应给尽量减少市民的死亡数量。
  • 救护员智能体对被掩埋受伤的市民和其它智能体的救治以及 消防员对着火建筑物的灭火会直接影响得
    分,而警察清除路障的效率通过影响救护员和 消防员的行动而间接影响得分。
4、项目模块简介与联系

在这里插入图片描述

4.1. Kernel (系统内核)

Kernel 是整个系统的核心模块。Kernel 负责调度各个模块之间的通信,转发他们之间发送的消息,管理协调各个模块。

仿真每个仿真周期分为两部分

前半周期 Sub-simulator 发来各种仿真数据,Kernel 对这些数据进行分析和计算并发送给 GIS 和 Viewer,Viewer 会进行更新;
后半周期用于与 Client 端的Agent 进行交互,等待 Agent 动作命令响应。Kernel 必须在后半周期内接收到 Client 端发来
的动作命令消息,否则该动作将不会被执行。

4.2 Sub-simulator 仿真器

Sub-simulator 模块模拟现实灾害情况,如房屋倒塌、建筑物起火等,同时响应救援队伍的救援行为。
Sub-simulator 主要由火灾仿真器、交通环境仿真器、道路阻塞仿真器、建筑倒塌仿真器和其他仿真器等构成。

  • 火灾仿真器模拟建筑物燃烧情况并考虑相邻建筑之间的火势蔓延,同时响应 Fire Brigade 智能体的灭火动作;
  • 交通环境仿真器模拟移动智能体在不同路况下的移动情况,如移动速度,是否被路障阻塞而无法移动、若干智能体在
    通过狭窄路段时发生拥堵等;
  • 道路阻塞仿真器模拟建筑物倒塌后在道路上形成的阻塞情况,
    同时响应 Police Force 智能体的清障动作;
  • 建筑倒塌仿真器模拟由地震引起的房屋、桥梁以及道路塌方等情况。

各个仿真器根据自身内部的状态信息以及从 Kernel 接收到的信息和命令来计算世界模型将要发生的事件,其中包括 Agent 的各种动作以及它们的动作带来的环境变化,然后把计算结果反馈回 Kernel。

4.3 Viewer 可视化界面,即我们跑地图时看到的2D场景

Viewer 动态地映射仿真系统内发生的所有仿真行为动作和事物变化,通过 2D 图形界面形式直观、清晰地表现出系统世界模型的运动发展。建筑物坍塌和着火燃烧、道路阻塞、市民被掩埋和受伤以及各种异构智能体的不同救援行为
Viewer 界面随着每个仿真周期而更新一次。

4.4 智能体感知与行为

智能体的感知器用于获取自身以及环境信息,是做决策的核心部分。Agent 通过感知器获取自己所处的位置信息、建筑物信息、道路信息等。
为提高模拟仿真的真实度,感知器从Kernel 获得的信息都是带有噪音的,并且所有感知能力都受到距离的限制:视觉范围一般为 10米,而听觉范围一般为 30 米。
下图是仿真系统中 Viewer 表现下的 Agent 视线情况。在仿真系统实际运行时,Agent 的视觉、听觉感知范围都会受到物体阻挡等因素影响而出现不同程度上的浮动。

智能体
不同智能体和指挥中心可执行的动作:
在这里插入图片描述

4.4 联系与运行实例

以下列出救援仿真系统运行过程中各模块之间消息传递步骤,救援仿真系统的运行就是这些步骤的不断循环迭代。

  • 1.Kernel 根据智能体的位置将该坐标点周围一定范围内的感知信息发送给对应智能体。
    感知信息包括视觉信息听觉信息:视觉信息指智能体所处坐标 10 米范围内能够看见的空间视觉信息;听觉信息指智能体坐标 30 米范围内(一般来讲)能够听见的声音信息。通常 Kernel 发送给智能体的信息会加入一定程度的噪声,以最大限度
    的模拟真实灾难情况
  • 2.智能体根据 Kernel 发来的感知信息以及与其他智能体通信获得的信息进行行为决策。根据决策将对应原子命令(如移动、救护和灭火等动作命令)发送给 Kernel;
  • 3.Kernel 接收到来自智能体的命令后将其转发给相应仿真器;
  • 4.各个仿真器根据从 Kernel 发来的命令和当前自身所存储的环境空间状态进行综合计算和分析,对灾难空间的发展变化做出预测,然后发送给 Kernel。如果 Kernel 在给各仿真器发送命令之后的半个周期内没有接收到相应反馈信息,则系统本周期的仿真状态将不会发生任何变化;
  • 5.Kernel 接收到来自仿真器的灾难预测数据后,将集成这些仿真信息并分别发送给 GIS和 Viewer;
  • 6.Viewer 接到 Kernel 的仿真更新信息后,向 GIS 发送信息更新请求;
  • 7.GIS 得到 Viewer 的请求后,将更新的灾难空间信息发送给 Viewer。
5. 常用模块介绍
5.1 世界模型 WorldInfo
  • 仿真系统运行过程中通过自身的感知信息和来自其它智能体的通信信息,救援智能体能够获得灾难空间的各种信息及其变化,包括智能体的异构类型、所处位置、自身状态以及灾难空间中其它对象的相关状态信息,因此世界模型可以看成是描述状态信息的一个接口。
  • 世界模型把所有外部灾难空间和内部自身状态的状态信息整合,体现了救援智能体对当前环境的认知能力。其准确性和信息量的完整性直接决定了智能体的行为和协作能力并最终影响救援效果。
  • 每个智能体都有自己独立的世界模型,包含的信息都是智能体与 Server 端进行交互并通过建模分析而获得。
5.2 路径规划 PathPlanning
  • 可移动救援智能体在灾难空间中可以自由移动,在执行救援任务时,它们需要移动到特定目标地点。智能体在灾难空间中的移动需要某种路径规划方法来实现从当前所处位置到目的地的移动,同时满足路径最短即耗时最短的要求。

  • 我们项目代码中是用的是A*寻路算法,采用启发式搜索算法来寻找最优路径。

路径规划具体流程为:以智能体当前所处位置为起始点,以目的地为终点,每一条路径中所经过所有道路的长度之和为评价依据。 如果路径中存在有路障的道路,该路径的长度将变为无穷大,下一轮路径搜索时将不再考虑该路径。所有路径计算完成后按照评价值进行从小到大排序,智能体使用长度最短的路径作为从当前所处位置到目的地的路径。通过这种动态的启发式搜索方法,智能体能够及时找到一条到达目的地的最短通路。

5.3 地图聚类与分区
  • 当救援智能体在某个运行周期任务列表为空,智能体便开始探索灾难空间,即探索地图。探索地图的目的是在更大范围上发挥感知环境的作用,然后将感知信息共享给其它智能体。
  • 探索过程中,涉及到智能体的协同问题。具有一定位置分布的救援智能体需要在有限且较短时间内进行对地图所有区域的遍历,从而在救援团队层面获得世界模型的全局信息。我们采用将地图分区的方法,根据智能体的位置分布将它们各自分配到不同的分区,然后只在自身所属的分区内探索,最后通过通信的方式分享局部视图,从而获得全局视图。
  • 在智能体开始救援工作之前,我们通过 K-means 方法]对地图进行聚类分区
    对地图上的各个节点按照它们的距离关系进行聚类分析,聚类获得的数据结构将会作为救援智能体实施协作方案的决策依据。
6. 常用代码分析
6.1 一些常用类型介绍
  • EntityID - 是仿真平台对于当前世界中所有东西进行的编号,比如当前智能体编号为001,前面那个建筑编号为8888,编号的目的是便于区分和使用

  • -AgentInfo 智能体自身世界观 主要封装了一些与智能体相关的函数,可以返回智能体的信息
    包括:
    1.设置以及返回自身记录的当前时间周期
    2.返回当前智能体的entityID即编号、在地图中的X、Y坐标、自身所在地区的编号
    3.听到的信息,看见的东西
    4.自身的一些属性,比如自己是否有灭火能力、当前水量、当前身上背的人的编号

  • WorldInfo 世界模型 主要封装了一些与地图属性相关的函数,可以返回地图的具体的信息

    1. public StandardEntity getEntity() 根据函数名来判断,此函数是传入一个编号,返回对应的实体

    2 public Collection getEntitiesOfType() 此函数是传入类别,返回对应的实体集合,包括
    ROAD  BLOCKADE BUILDING REFUGE HYDRANT GAS_STATION
    三种指挥中心 市民 三种智能体 等

    3. public Collection getObjectIDsInRange 返回所给区域内的实体ID
    4.public Collection getFireBuildings 返回着火建筑的集合
    5.public Collection getBuriedHumans 传入某建筑,返回建筑内被掩埋的人类集合
    6.public Collection getBlockades 返回某条路上的所有路障的集合
    7.public StandardEntity getPosition 返回某东西所在位置的实体

    剩下的函数可以自己去探索,列出的函数都是之后会经常用到的,调用时注意观察传入参数的返回类型以及函数作用,这就是我们编写代码的一般思路---对于想实现的某个功能,先搜索看看是否已经有相关的函数存在了,通过调用函数以及编写辅助代码,这样才能较快地写出稳健可运行的代码

  • ScenarioInfo 场景模型,主要返回的是场景相关的一些属性,用的较少

6.2 代码分析

目前我们可以对代码进行优化的部分对应的位置在src-adf.sample 包下面

  • centralized 通信相关代码
  • extaction 智能体动作相关代码(clear-清障 move-移动 FireFighting - 灭火 transport - 运输)
  • module 内含三个模块 algorithm 算法 、comm 没用过 、complex 复杂决策
    其中  algorithm 包括了火焰动态地图分区 初始建筑中心聚类 和 路径规划
    complex 下则是智能体们对于目标的搜索以及抉择
  • tactics 决策层面 不可改动

下面以extaction 下面的 ActionExtClear 作为例子,简单讲下一些基础的、通用的知识

一些变量的初始化


private PathPlanning pathPlanning;//路径规划
private int clearDistance;//清理过的路径长度
private int forcedMove;//强制移动,如果警察在某一区域停留的时间过长,则代码强制让其前往下一个目标
private int thresholdRest;//需要休息时的临界值
private int kernelTime;//内核时间 即1-300个仿真周期

private EntityID target;//本类所要得到的最终结果-清障目标
private Map<EntityID, Set<Point2D>> movePointCache;//移动时经过的位置编号、中心X、Y坐标所形成的map
private int oldClearX;//上一个周期的位置的X坐标
private int oldClearY;//上一个周期的位置的X坐标
private int count;//计数变量

初始化函数


public ActionExtClear(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData)

关于这几个Info 之前已经讲过了

  • ModuleManager 模块调用,主要是与config文件有关,负责各个模块具体调用的是哪个代码,不用细管
  • developData 新增数据 不用管
//预计算,预计算后代码跑的更好
public ExtAction precompute(PrecomputeData precomputeData) 
//直接开始 无预计算
public ExtAction resume(PrecomputeData precomputeData) 
//准备好了的
public ExtAction preparate() 
//每周期更新一下仿真平台的信息
public ExtAction updateInfo(MessageManager messageManager)

以上这四行代码基本在每一个类里面都有,有初始化功能之意,大家不用纠结,可以暂时不予修改

然后是


public ExtAction calc() 

calc是一个类中最为重要的函数,其计算结果直接或间接影响仿真结果的好坏,注意在分析代码的时候多多看看 if 结构的{ 和 }两个括号的匹配


public ExtAction calc() {
        this.result = null;
        //policeForce 即为我自己
        PoliceForce policeForce = (PoliceForce)this.agentInfo.me();
		//注意if结构出现了 
		//如果我需要休息,我先把我的目标存储一下,然后调用calcRest函数进行动作的计算
		// private Action calcRest(Human human, PathPlanning pathPlanning, Collection<EntityID> targets) 
		//calcRest 这个函数字面意思是在我休息前进行一次计算,参数为 人类、路径规划方式类、警察的目标们   所以在调用时 人类就是我自己 路径规划方式类即本类的属性this.pathPlanning,目标为我之前存储的目标
        if(this.needRest(policeForce)) {
            List<EntityID> list = new ArrayList<>();
            if(this.target != null) {
                list.add(this.target);
            }
            this.result = this.calcRest(policeForce, this.pathPlanning, list);

			//如果我计算结果不为空,表明我有一个好的动作可以去做了,于是return, 跳出calc,终止计算(一次计算只返回一次结果)
            if(this.result != null) {
                return this;
            }
        }

		//如果我自身没有目标要去做,早点renurn 吧,计算也算不出什么结果的
        if(this.target == null) {
            return this;
        }

		//找到我自己所在位置的编号、目标的实体、我所在位置的实体
        EntityID agentPosition = policeForce.getPosition();
        StandardEntity targetEntity = this.worldInfo.getEntity(this.target);
        StandardEntity positionEntity = Objects.requireNonNull(this.worldInfo.getEntity(agentPosition));

		//目标位置实体为空或者不是区域 - 无法操作,早点返回,节省时间
        if(targetEntity == null || !(targetEntity instanceof Area)) {
            return this;
        }

        //如果我在路上,就返回清障目标,目标非空就去吧,return 不再计算
        if (positionEntity instanceof Road) {
            this.result = this.getRescueAction(policeForce, (Road) positionEntity);
            if (this.result != null) {
                return this;
            }
        }

        //如果我与目标在同一区域了,则清理区域内的障碍(getAreaClearAction)
        if(agentPosition.equals(this.target)) {
            this.result = this.getAreaClearAction(policeForce, targetEntity);
        }

        //如果目的地和我所在地有边相连,说明我们相邻了,调用有邻边相连区域的清障函数
        else if(((Area)targetEntity).getEdgeTo(agentPosition) != null) {
            this.result = this.getNeighbourPositionAction(policeForce, (Area)targetEntity);
        }

        //再不然说明我与目标很远啊,就规划路径
        else {
            // path 内存储的是从智能体所在地到目标的路径
            List<EntityID> path = this.pathPlanning.getResult(agentPosition, this.target);

            //路径规划成功
            if (path != null && path.size() > 0) {

                //index 存储的是警察所在地在path中的位置
                int index = path.indexOf(agentPosition);

                //如果警察所在地不在路径规划的路径之中
                if(index == -1) {

                    //area即为警察所在的地区
                    Area area = (Area)positionEntity;
                    //找到规划得到路径的某一段,我与它相邻,那我可以从这一段过去,我的位置也就相应的是这段路的位置,因为可能我在建筑里,而路径规划只规划道路,所以我要先到路上去
                    for(int i = 0; i < path.size(); i++) {
                        if(area.getEdgeTo(path.get(i)) != null) {
                            index = i;
                            break;
                        }
                    }

                }
                //如果警察所在地在路径规划的路径之中
                else if(index >= 0){
                    index++;
                }

                //警察所在地在规划的路径之中且不为终点
                if(index >= 0 && index < (path.size())) {

                    //entity为警察所在地
                    StandardEntity entity = this.worldInfo.getEntity(path.get(index));

                    //判断下我现在是否到达与目的地相邻的区域了,如果是,getNeighbourPositionAction返回的有值,如果不相邻,getNeighbourPositionAction返回为空
                    this.result = this.getNeighbourPositionAction(policeForce, (Area) entity);

                    
                    if (this.result != null && this.result.getClass() == ActionMove.class) {
                        if(!((ActionMove)this.result).getUsePosition()) {
                            this.result = null;
                        }
                    }
                }

                //结果设置为空,沿着path移动
                if(this.result == null) {
                    this.result = new ActionMove(path);
                }
            }
        }
        return this;
    }

在类里面一般会有一些工具函数的存在,比如

		private boolean intersect

这种函数,其代码量多,晦涩,而且细看无用,较难进行修改,因为了解到其用法和作用就要停下来,不要纠结于弄懂每个函数,可以直接通过其函数名、参数、返回值 大致了解其功能,如果不影响代码的阅读自然最好,实在是对代码理解需要的话再看,切记,不要太过深入,仿真项目一共寥寥数万行代码,深入看的话根本难以实现!!!

加油吧!!!!!!!!!!!!!!!!!!!!

  • 22
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值