1. Purpose

The manual instructs how to install and execute the RoboCup Rescue Simulation Agent Development Framework (ADF) Sample Agents, and how to implement a new team of agents using the ADF Sample Agents.

2. Installation

This manual assumes the agents will run in a Linux machine even though it is possible to run them in Microsoft Windows or Apple macOS. We recommend to use Linux because it is open-source and most of the distributions have a good support from the users' community. If you have never used Linux before and intend to, we recommend starting with a user-friendly distribution, such as Ubuntu or Fedora.

2.1. Software Requirements

  • Java OpenJDK 17

  • Git

  • Utilities like wget, bash, xterm, tar, gzip, etc.
    NOTE: If you are using Ubuntu, all of these utilities are present in the default software repositories.

2.2. Download

You can download the sample agents with ADF by cloning the https://github.com/roborescue/adf-sample-agent-java repository. Clone this repository using the command

git clone https://github.com/roborescue/adf-sample-agent-java.git

2.3. Directories

The adf-sample-agent-java contains multiple directories. The important directories are:

  • config/: ADF and Agent Modules' configuration files

  • src/: Sample agents' source codes

  • precomp_data: results of a precomputation for each type of agents

2.4. Compiling

Execute the steps below to compile the ADF Sample Agent.

$ cd adf-sample-agent-java
$ ./gradlew clean build

3. Running

There are two modes of execution of the simulation server and ADF Sample Agent: Precomputation and Normal.

3.1. Precomputation Mode

In the precomputation mode, the simulator connects one agent of each type and allows them to write the computation results persistently.

The sequence of commands to run the simulation server in precomputation mode are:

$ cd rcrs-server
$ cd scripts
$ ./start-precompute.sh -m ../maps/test/maps -c ../maps/test/config

See RoboCup Rescue Simulator Manual for further information on how to compile and run the RoboCup Rescue Simulator server.

After running the simulation server for the precomputation, move to the ADF Sample Agent directory on another terminal window and run the agents executing the commands:

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

[START] Connect to server (host:localhost, port:27931)
[INFO] Connected - adf.agent.platoon.PlatoonFire@756ec19c (PRECOMPUTATION)
[INFO] Connected - adf.agent.platoon.PlatoonPolice@366bbbe (PRECOMPUTATION)
[INFO] Connected - adf.agent.platoon.PlatoonAmbulance@2a453513 (PRECOMPUTATION)
********************
[FINISH] Connect PoliceForce (success:1)
[FINISH] Connect AmbulanceTeam (success:1)
[FINISH] Connect FireBrigade (success:1)
[FINISH] Done connecting to server (3 agents)

Once the precomputation is completed, press Control-C and type bash kill.sh to stop the simulation server of running.

Control-C
$ bash kill.sh

3.2. Normal Mode

In the normal mode, the simulator connects all agents defined in the scenario and allows them to use the precomputation output (see Section 3.1).

The sequence of commands to run the simulation server in normal mode are:

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

See RoboCup Rescue Simulator Manual for further information on how to compile and run the RoboCup Rescue Simulator server.

After running the simulation server, move to the ADF Sample Agent directory on another terminal window and run the agents using the commands:

$ bash launch.sh -all
[FINISH] Done connecting to server (3 agents)

4. Develop your own agents using ADF

This section explain how to implement your agents using the ADF Sample Agent as the starting point.

4.1. Workflow for coding your agents

The steps necessary to code your own agents are:

  • Implement the customized modules

  • Change the config/module.cfg to point to the customized modules

4.2. Customize modules

ADF is a modular framework whose modules were define in the adf-core-java (https://github.com/roborescue/adf-core-java) repository together with a set of default implementations. To implement your own team of agents, you have to implement the modules' Java interfaces correspondent to the behavior you want to customize.

The default implementations of the modules' Java interfaces is available under the package impl in the adf-core-java repository. There you find default implementations for:

  • adf.impl.centralized: source code of the central agents. This is the type of agents whose only interaction with the world is through radio communication. There are three types of central agents: Ambulance Centers, Fire Stations and Police Office, and they are represented as buildings in the simulation server.

  • adf.impl.extraction: source code of the possible actions available to agents.

  • adf.impl.module: source code of the algorithms, e.g., path planning, clustering, target detection, etc. representing the agents' behavior. The modules are split into

    • adf.impl.module.algorithm

    • adf.impl.module.comm

    • adf.impl.module.complex

To customize any of these modules, you can copy modules' file you want to customize to you team agents' repository and make changes to the implementation. Then you need to change the references to your modules by modifying config/module.cfg file (see below).

4.3. Modules' configuration file

The modules configuration file config/module.cfg indicates which class will be used as agents' module. Listing 1 shows part of the modules configuration file. The left-hand side of the colon indicates the module name, the right-hand side is the class name. In most cases, modules of which targets' problems are the same should refer to an identical class for all agent types. The example in Listing 1 is in DefaultTacticsAmbulanceTeam.Search and DefaultTacticsFireBrigade.Search indicates that both modules refer to sample_team.module.complex.SampleSearch. An usage example is shown in Section 4.4.3.

Listing 1. Part of a module configuration file
## DefaultTacticsAmbulanceTeam
DefaultTacticsAmbulanceTeam.HumanDetector : sample_team.module.complex.SampleHumanDetector
DefaultTacticsAmbulanceTeam.Search : sample_team.module.complex.SampleSearch
DefaultTacticsAmbulanceTeam.ExtActionTransport : adf.impl.extaction.DefaultExtActionTransport
DefaultTacticsAmbulanceTeam.ExtActionMove : adf.impl.extaction.DefaultExtActionMove
DefaultTacticsAmbulanceTeam.CommandExecutorAmbulance : adf.impl.centralized.DefaultCommandExecutorAmbulance
DefaultTacticsAmbulanceTeam.CommandExecutorScout : adf.impl.centralized.DefaultCommandExecutorScout

## DefaultTacticsFireBrigade
DefaultTacticsFireBrigade.HumanDetector : sample_team.module.complex.SampleHumanDetector
DefaultTacticsFireBrigade.Search : sample_team.module.complex.SampleSearch
DefaultTacticsFireBrigade.ExtActionFireRescue : adf.impl.extaction.DefaultExtActionFireRescue
DefaultTacticsFireBrigade.ExtActionMove : adf.impl.extaction.DefaultExtActionMove
DefaultTacticsFireBrigade.CommandExecutorFire : adf.impl.centralized.DefaultCommandExecutorFire
DefaultTacticsFireBrigade.CommandExecutorScout : adf.impl.centralized.DefaultCommandExecutorScout

4.4. Example of implementing A* algorithm for Path Planning algorithm

In this example, you will learn how to implement the A* Path Planning algorithm in a module and how to setup the ADF Sample Agent to use it instead of the Dijkstra Path Planning. Here we assume that you will apply the changes to the adf-sample-agent-java repository.

4.4.1. Copy the Dijkstra Path Planning file

First, you should copy the Dijkstra path planning (src/main/java/adf/impl/module/algorithm/DijkstraPathPlanning.java) from the adf-core-java repository to the adf-sample-agent-java repository (src/main/java/sample_team/module/algorithm).

$ cd adf-sample-agent-java
$ mkdir -p src/main/java/sample_team/module/algorithm
$ cp ../adf-core-java/src/main/java/adf/impl/module/algorithm/DijkstraPathPlanning.java src/main/java/sample_team/module/algorithm/AStarPathPlanning.java

4.4.2. Edit the Dijkstra code

Listing 2 is the code of DijkstraPathPlanning.java, which implements the Dijkstra’s algorithm. You should edit line 1 and 23th as well as replace the code in the method calc() starting on line 96. Remove the method isGoal() that is only used by the Dijkstra calc(). Listing 3 shows the results of editing these lines.

You must implement the method calc() to get its calculation result by the method getResult(). The type of getResult() returning is List<EntityID>.

Listing 4 indicates the contents of the method calc(). In addition, you should write the new private class Node which is used by the method calc(). The code is shown in Listing 5.

Listing 2. DijkstraPathPlanning.java file
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package adf.impl.module.algorithm; // Edit this line

import adf.core.agent.communication.MessageManager;
import adf.core.agent.develop.DevelopData;
import adf.core.agent.info.AgentInfo;
import adf.core.agent.info.ScenarioInfo;
import adf.core.agent.info.WorldInfo;
import adf.core.agent.module.ModuleManager;
import adf.core.agent.precompute.PrecomputeData;
import adf.core.component.module.algorithm.PathPlanning;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import rescuecore2.misc.collections.LazyMap;
import rescuecore2.standard.entities.Area;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;

public class DijkstraPathPlanning extends PathPlanning { // Edit this line

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

  private EntityID from;
  private Collection<EntityID> targets;
  private List<EntityID> result;

  public DijkstraPathPlanning(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() {   // Replace the code in this method by the A* Path Planning algorithm
    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;
  }

  private boolean isGoal(EntityID e, Collection<EntityID> test) {
    return test.contains(e);
  }
}
Listing 3. AStartPlanning.java file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package sample_team.module.algorithm; // Position of the file

import adf.core.agent.develop.DevelopData;
import adf.core.agent.info.AgentInfo;
import adf.core.agent.info.ScenarioInfo;
import adf.core.agent.info.WorldInfo;
import adf.core.agent.module.ModuleManager;
import adf.core.agent.precompute.PrecomputeData;
import adf.core.component.module.algorithm.PathPlanning;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import rescuecore2.misc.collections.LazyMap;
import rescuecore2.standard.entities.Area;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;

public class AStarPathPlanning extends PathPlanning {

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

  private EntityID from;
  private Collection<EntityID> targets;
  private List<EntityID> result;

  public AStarPathPlanning(AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager moduleManager, DevelopData developData) {
    super(ai, wi, si, moduleManager, developData);
    this.init();
  }

  ...
Listing 4. calc() method
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  @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);
        }
      }
    }
  }
Listing 5. Node class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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. Edit the Modules' configuration file

After created the module code, you must edit the module configuration file config/module.cfg and replace the modules you would like to use your implementation. Listing 6 and Listing 7 show the part of the default module.cfg and the part of the edited config/module.cfg where the lines related to a path planning are changed. In this case, all adf.impl.module.algorithm.DijkstraPathPlanning are replaced with sample_team.module.algorithm.AStarPathPlanning.

Listing 6. Default module.cfg
## SampleSearch
SampleSearch.PathPlanning.Ambulance : adf.impl.module.algorithm.DijkstraPathPlanning
SampleSearch.Clustering.Ambulance : adf.impl.module.algorithm.KMeansClustering
SampleSearch.PathPlanning.Fire : adf.impl.module.algorithm.DijkstraPathPlanning
SampleSearch.Clustering.Fire : adf.impl.module.algorithm.KMeansClustering
SampleSearch.PathPlanning.Police : adf.impl.module.algorithm.DijkstraPathPlanning
SampleSearch.Clustering.Police : adf.impl.module.algorithm.KMeansClustering
Listing 7. Edited module.cfg
## SampleSearch
SampleSearch.PathPlanning.Ambulance : sample_team.module.algorithm.AStarPathPlanning
SampleSearch.Clustering.Ambulance : adf.impl.module.algorithm.KMeansClustering
SampleSearch.PathPlanning.Fire : adf.impl.module.algorithm.AStarPathPlanning
SampleSearch.Clustering.Fire : adf.impl.module.algorithm.KMeansClustering
SampleSearch.PathPlanning.Police : adf.impl.module.algorithm.AStarPathPlanning
SampleSearch.Clustering.Police : adf.impl.module.algorithm.KMeansClustering