1.目的

该教程说明如何安装和执行 RoboCup 救援模拟代理开发框架 (ADF) 示例代理,以及如何使用 ADF 示例代理实现新的代理团队


2.安装

本手册假定代理将在 Linux 机器中运行,即使它们可以在 Microsoft Windows 或 Apple macOS 中运行。 我们推荐使用 Linux,因为它是开源的,并且大多数发行版都得到了用户社区的良好支持。 如果您以前从未使用过 Linux 并打算使用,我们建议您从用户友好的发行版开始,例如 Ubuntu or Fedora

2.1 软件要求
2.2 下载

您可以通过克隆 https://github.com/roborescue/rcrs-adf-sample 存储库来下载带有 ADF 的示例代理。

使用此命令克隆存储库

git clone https://github.com/roborescue/rcrs-adf-sample.git
2.3. 目录

rcrs-adf-sample 包含多个目录。 重要的目录是:

  • config/: 配置文件
  • src/: 源代码
  • precomp_data: 每种类型代理的预计算结果
  • build/: 构建结果
  • library/: 使用的库
2.4 编译

执行以下步骤来编译 ADF 示例代理:

  1. 切换到目录 rcrs-adf-sample
  2. 使用命令编译项目
$ ./gradlew clean
$ ./gradlew build
2.5 示例

在 Ubuntu 中,安装根据以下命令进行:

$ git clone https://github.com/roborescue/rcrs-adf-sample.git
$ cd rcrs-adf-sample
$ ./gradlew clean
$ ./gradlew build

3.运行

模拟服务器和 ADF 示例代理有两种执行模式:预计算模式和正常模式。

3.1 预计算模式

在预计算模式下,模拟器连接每种类型的一个代理,并允许它们写入计算结果。

在预计算模式下运行模拟服务器的命令序列是:

$ cd rcrs-server
$ cd boot
$ bash start-precompute.sh

在为预计算运行模拟服务器后,移动到另一个终端窗口上的 ADF 目录并运行执行以下命令:

$ bash launch.sh -t 1,0,1,0,1,0 -h localhost -pre 1 & APID=$! ; sleep 120 ; kill $APID

预计算完成后,按下 Ctrl + C 并键入 sh kill.sh 以停止运行的模拟服务器。

$ bash kill.sh
3.2 正常模式

在正常模式下,模拟器连接场景中定义的所有代理,并允许它们使用预计算输出。

在正常模式下运行模拟服务器的命令序列是:

$ cd rcrs-server
$ cd boot
$ bash start-comprun.sh

运行模拟服务器后,移动到另一个终端窗口上的 ADF 目录并使用以下命令运行代理:

$ bash launch.sh -all

4.使用 ADF 开发您自己的代理

本节介绍如何使用 ADF 示例实现您的代理。

4.1 开发代理的文件

您仅可以使用以下目录中的文件开发自己的代理代码:

  • src/adf/sample/centralized:中央代理的源代码。 这是一种代理,其与世界的唯一交互是通过无线电通信。 中心代理分为三种类型:救护中心消防局警察局,它们在模拟服务器中表示为建筑物。
  • src/adf/sample/extraction:该目录中是描述组合动作的代码。
  • src/adf/sample/module:算法的具体代码,例如路径规划、聚类、目标检测等。该目录包含两个目录:
    • src/adf/sample/module/algorithm
    • src/adf/sample/module/complex

注意!!! 您不得对 src/adf/sample/tactics 中的文件进行任何更改。 这是我们目前的竞争规则的限制。

您应该从根本上复制示例代码,而不是编辑它们。 原因是如果 ADF 找不到您自己的代码,将使用示例代码。 您可以通过修改 src/adf/config/module.cfg 轻松更改对模块的引用。

4.2 为您的代理编码的工作流程

编写您自己的代理程序所需的步骤是:

  • 复制与您要创建的代理相关的示例代码
  • 编辑拷贝的文件
  • 根据编辑的文件编辑src/adf/config/module.cfg
  • 编译和运行
4.3 模块的配置文件

模块配置文件 src/adf/config/module.cfg 指示哪些代码将用作代理模块。

以下显示模块配置文件的一部分。 冒号左边是模块名,右边是类名。 在大多数情况下,目标问题相同的模块应该针对所有代理类型引用相同的类

TacticsAmbulanceTeam.HumanDetector : adf.sample.module.complex.SampleHumanDetector
TacticsAmbulanceTeam.Search : adf.sample.module.complex.SampleSearch

TacticsAmbulanceTeam.ActionTransport : adf.sample.extaction.ActionTransport
TacticsAmbulanceTeam.ActionExtMove : adf.sample.extaction.ActionExtMove

TacticsAmbulanceTeam.CommandExecutorAmbulance : adf.sample.centralized.CommandExecutorAmbulance
TacticsAmbulanceTeam.CommandExecutorScout : adf.sample.centralized.CommandExecutorScout

TacticsFireBrigade.BuildingDetector : adf.sample.module.complex.SampleBuildingDetector
TacticsFireBrigade.Search : adf.sample.module.complex.SampleSearch

TacticsFireBrigade.ActionFireFighting : adf.sample.extaction.ActionFireFighting
TacticsFireBrigade.ActionExtMove : adf.sample.extaction.ActionExtMove

在该例子中, TacticsAmbulanceTeam.Search TacticsFireBrigade.Search 两个模块都引用 adf.sample.module.complex.SampleSearch

4.4 路径规划算法 A* 算法的示例
4.4.1 复制 ADF 示例代码

首先,您应该复制路径规划的示例代码,即 SamplePathPlanning.java,示例如下所述。

$ mkdir -p src/myteam/module/algorithm
$ cp src/adf/sample/module/algorithm/SamplePathPlanning.java src/myteam/module/algorithm/AStarPathPlanning.java
4.4.2 编辑 ADF 示例代码

以下是具有 Dijkstra 算法的 SamplePathPlanning.java 的代码。 您应该编辑第 1 行、第 18 行和第 27 行。 您将在方法 calc() 中实现自己的代码,并删除仅由 calc() 使用的方法 isGoal()

package adf.sample.module.algorithm; // Edit this line

import adf.agent.communication.MessageManager;
import adf.agent.develop.DevelopData;
import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import adf.agent.module.ModuleManager;
import adf.agent.precompute.PrecomputeData;
import adf.component.module.algorithm.PathPlanning;
import rescuecore2.misc.collections.LazyMap;
import rescuecore2.standard.entities.Area;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;

import java.util.*;

public class SamplePathPlanning extends PathPlanning { // Edit this line

  private Map<EntityID, Set<EntityID>> graph;

  private EntityID from;
  private Collection<EntityID> targets;
  private List<EntityID> result;
  // Edit the following line
  public SamplePathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
    super(ai, wi, si, moduleManager, developData);
    this.init();
  }

  private void init() {
    Map<EntityID, Set<EntityID>> neighbours = new LazyMap<EntityID, Set<EntityID>>() {
      @Override
      public Set<EntityID> createValue() {
          return new HashSet<>();
      }
    };
    for (Entity next : this.worldInfo) {
      if (next instanceof Area) {
        Collection<EntityID> areaNeighbours = ((Area) next).getNeighbours();
        neighbours.get(next.getID()).addAll(areaNeighbours);
      }
    }
    this.graph = neighbours;
  }

  @Override
  public List<EntityID> getResult() {
    return this.result;
  }

  @Override
  public PathPlanning setFrom(EntityID id) {
    this.from = id;
    return this;
  }

  @Override
  public PathPlanning setDestination(Collection<EntityID> targets) {
    this.targets = targets;
    return this;
  }

  @Override
  public PathPlanning updateInfo(MessageManager messageManager) {
    super.updateInfo(messageManager);
    return this;
  }

  @Override
  public PathPlanning precompute(PrecomputeData precomputeData) {
    super.precompute(precomputeData);
    return this;
  }

  @Override
  public PathPlanning resume(PrecomputeData precomputeData) {
    super.resume(precomputeData);
    return this;
  }

  @Override
  public PathPlanning preparate() {
    super.preparate();
    return this;
  }

  @Override
  public PathPlanning calc() { // Renew this method (implement your algorithm here)
    List<EntityID> open = new LinkedList<>();
    Map<EntityID, EntityID> ancestors = new HashMap<>();
    open.add(this.from);
    EntityID next;
    boolean found = false;
    ancestors.put(this.from, this.from);
    do {
      next = open.remove(0);
      if (isGoal(next, targets)) {
        found = true;
        break;
      }
      Collection<EntityID> neighbours = graph.get(next);
      if (neighbours.isEmpty()) {
        continue;
      }
      for (EntityID neighbour : neighbours) {
        if (isGoal(neighbour, targets)) {
          ancestors.put(neighbour, next);
          next = neighbour;
          found = true;
          break;
        }
        else {
          if (!ancestors.containsKey(neighbour)) {
            open.add(neighbour);
            ancestors.put(neighbour, next);
          }
        }
      }
    } while (!found && !open.isEmpty());
    if (!found) {
      // No path
      this.result = null;
    }
    // Walk back from goal to this.from
    EntityID current = next;
    LinkedList<EntityID> path = new LinkedList<>();
    do {
      path.add(0, current);
      current = ancestors.get(current);
      if (current == null) {
        throw new RuntimeException("Found a node with no ancestor! Something is broken.");
      }
    } while (current != this.from);
    this.result = path;
    return this;
  }
  // Remove the method (it is only used by calc()).
  private boolean isGoal(EntityID e, Collection<EntityID> test) {
    return test.contains(e);
  }
}

以下代码显示了编辑这些行的结果。

您必须实现方法 calc() 才能通过方法 getResult() 获取其计算结果。 getResult() 返回的类型是 List<EntityID>

package myteam.module.algorithm; // Position of the file

import adf.agent.communication.MessageManager;
import adf.agent.develop.DevelopData;
import adf.agent.info.AgentInfo;
import adf.agent.info.ScenarioInfo;
import adf.agent.info.WorldInfo;
import adf.agent.module.ModuleManager;
import adf.agent.precompute.PrecomputeData;
import adf.component.module.algorithm.PathPlanning;
import rescuecore2.misc.collections.LazyMap;
import rescuecore2.standard.entities.Area;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;

import java.util.*;

public class AStarPathPlanning extends PathPlanning { // Same as the file name

  private Map<EntityID, Set<EntityID>> graph;

  private EntityID from;
  private Collection<EntityID> targets;
  private List<EntityID> result;
  // Same as the file name
  public AStarPathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
    super(ai, wi, si, moduleManager, developData);
    this.init();
  }

以下代码指示方法 calc() 的内容。

  @Override
  public PathPlanning calc() {
    List<EntityID> open = new LinkedList<>();
    List<EntityID> close = new LinkedList<>();
    Map<EntityID, Node> nodeMap = new HashMap<>();
    open.add(this.from);
    nodeMap.put(this.from, new Node(null, this.from));
    close.clear();

    while (true) {
      if (open.size() < 0) {
        this.result = null;
        return this;
      }
      Node n = null;
      for (EntityID id : open) {
        Node node = nodeMap.get(id);
        if (n == null) {
          n = node;
        } else if (node.estimate() < n.estimate()) {
          n = node;
        }
      }
      if (targets.contains(n.getID())) {
        List<EntityID> path = new LinkedList<>();
        while (n != null) {
          path.add(0, n.getID());
          n = nodeMap.get(n.getParent());
        }
        this.result = path;
        return this;
      }
      open.remove(n.getID());
      close.add(n.getID());

      Collection<EntityID> neighbours = this.graph.get(n.getID());
      for (EntityID neighbour : neighbours) {
        Node m = new Node(n, neighbour);
        if (!open.contains(neighbour) && !close.contains(neighbour)) {
          open.add(m.getID());
          nodeMap.put(neighbour, m);
        }
        else if (open.contains(neighbour) && m.estimate() < nodeMap.get(neighbour).estimate()) {
          nodeMap.put(neighbour, m);
        }
        else if (!close.contains(neighbour) && m.estimate() < nodeMap.get(neighbour).estimate()) {
          nodeMap.put(neighbour, m);
        }
      }
    }
  }

此外,您应该编写新的私有类 Node,它被方法 calc() 使用。 代码如下所示。它必须放在文件 AStarPathPlanning.java 中。

private class Node {
  EntityID id;
  EntityID parent;

  double cost;
  double heuristic;

  public Node(Node from, EntityID id) {
    this.id = id;

    if (from == null) {
      this.cost = 0;
    } else {
      this.parent = from.getID();
      this.cost = from.getCost() + worldInfo.getDistance(from.getID(), id);
    }

    this.heuristic = worldInfo.getDistance(id, targets.toArray(new EntityID[targets.size()])[0]);
  }

  public EntityID getID() {
    return id;
  }

  public double getCost() {
    return cost;
  }

  public double estimate() {
    return cost + heuristic;
  }

  public EntityID getParent() {
    return this.parent;
  }
}
4.4.3 编辑模块的配置文件

您必须编辑与路径规划相关的模块配置文件 src/adf/config/module.cfg 才能使用您的代码。 下列显示了默认 module.cfg 的一部分和已编辑的 module.cfg 的一部分,其中与路径规划相关的行发生了更改。 在这种情况下,文件中的所有 adf.sample.module.algorithm.SamplePathPlanning 都将替换为 myteam.module.algorithm.AStarPathPlanning。 如果您想在某些模块中使用该代码,您可以指明只有模块引用它。

修改前:

SampleRoadDetector.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
SampleSearch.PathPlanning.Ambulance : adf.sample.module.algorithm.SamplePathPlanning
SampleSearch.PathPlanning.Fire : adf.sample.module.algorithm.SamplePathPlanning
SampleSearch.PathPlanning.Police : adf.sample.module.algorithm.SamplePathPlanning
ActionExtClear.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
ActionExtMove.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
ActionFireFighting.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
ActionTransport.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
CommandExecutorAmbulance.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
CommandExecutorFire.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
CommandExecutorPolice.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
CommandExecutorScout.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning
CommandExecutorScoutPolice.PathPlanning : adf.sample.module.algorithm.SamplePathPlanning

修改后:

SampleRoadDetector.PathPlanning : myteam.module.algorithm.AStarPathPlanning
SampleSearch.PathPlanning.Ambulance : myteam.module.algorithm.AStarPathPlanning
SampleSearch.PathPlanning.Fire : myteam.module.algorithm.AStarPathPlanning
SampleSearch.PathPlanning.Police : myteam.module.algorithm.AStarPathPlanning
ActionExtClear.PathPlanning : myteam.module.algorithm.AStarPathPlanning
ActionExtMove.PathPlanning : myteam.module.algorithm.AStarPathPlanning
ActionFireFighting.PathPlanning : myteam.module.algorithm.AStarPathPlanning
ActionTransport.PathPlanning : myteam.module.algorithm.AStarPathPlanning
CommandExecutorAmbulance.PathPlanning : myteam.module.algorithm.AStarPathPlanning
CommandExecutorFire.PathPlanning : myteam.module.algorithm.AStarPathPlanning
CommandExecutorPolice.PathPlanning : myteam.module.algorithm.AStarPathPlanning
CommandExecutorScout.PathPlanning : myteam.module.algorithm.AStarPathPlanning
CommandExecutorScoutPolice.PathPlanning : myteam.module.algorithm.AStarPathPlanning