--- title : RRS Tutorial 2 robots : noindex, nofollow lang : ja-jp breaks : false --- # RoboCupRescue Simulation Tutorial 2 ## 関連リンク * [RRS Tutorial 1 - 導入編](https://hackmd.io/@f7c-HYEQTiygEJma8JWbjQ/SyU4ZRY68) * RRS Tutorial 2 - 環境構築 & タスク決定編 * [RRS Tutorial 3 - クラスタリング編](https://hackmd.io/@f7c-HYEQTiygEJma8JWbjQ/SyWm6r24H) * [RRS Tutorial 4 - サーチ対象決定編](https://hackmd.io/@f7c-HYEQTiygEJma8JWbjQ/Sy5ydo2Lr) * [RRSに関する情報置き場](https://hackmd.io/@f7c-HYEQTiygEJma8JWbjQ/SJDghjVbH) ## 本資料の目的 本資料ではRRSの環境構築とエージェントを操作する簡単なプログラムの作成をおこないます. これにより,RRSを理解する最初の段階として,エージェントの開発方法への理解とシミュレーションの実行がおこなえるようになることを目的としています. ## 環境構築 RRSの環境は大きく分けると,以下の要素で構成されています. * シミュレーションの進行・管理をおこなうサーバ * 災害救助エージェントを操作するプログラム これから環境構築をおこなうOSは Ubuntu 18.04 LTS を想定しており,次の環境が整備されている必要があります. * Open JDK 9+ * Git :::info **NOTE:** macOS Sierra でも正常に動作することを確認しています. ::: :::warning **NOTE:** 2020年07月10日時点では, エージェント操作プログラムをOpen JDK 14でビルドすることができません. Gradle関連ファイルの更新を待つ必要があります. ::: ### サーバのインストール サーバのインストールは,次のコマンド列をコンソールに入力することでおこないます. ここでは,インストールするディレクトリを `WORKING_DIR` で表しています. :::info **NOTE:** サーバとエージェント操作プログラムで別々のTerminalウィンドウが必要になります. ::: :::info **NOTE:** 特に指針がない場合は,`WORKING_DIR` を `~/Desktop` で置き換えてください. ::: ###### Terminal A ```shell # Ubuntu $ cd WORKING_DIR $ git clone https://github.com/roborescue/rcrs-server.git $ cd rcrs-server $ ./gradlew completeBuild $ sed -i '/wall-extent/s/:.*/:0/g' maps/gml/test/config/collapse.cfg ``` ```shell # macOS $ cd WORKING_DIR $ git clone https://github.com/roborescue/rcrs-server.git $ cd rcrs-server $ ./gradlew completeBuild $ sed -i -e '/wall-extent/s/:.*/:0/g' maps/gml/test/config/collapse.cfg ``` :::info **NOTE:** この資料では,行の省略を `...` ,実行の度に変わる文字列を `x` で表しています. ::: 次のような画面が,コンソールに表示されていれば成功です. ###### Terminal A ```shell ... > Task: completeBuild BUILD SUCCESSFUL in xxs 1 actionable task: 1 executed ``` ### エージェント操作プログラムのインストール エージェント操作プログラムのインストールは,次のコマンド列をコンソールに入力することでおこないます. ###### Terminal B ```shell $ cd WORKING_DIR $ git clone https://github.com/roborescue/rcrs-adf-sample.git $ cd rcrs-adf-sample $ mv config/module_sample.cfg config/module.cfg $ ./gradlew build ``` 次のような画面が,コンソールに表示されていれば成功です. ###### Terminal B ```shell Starting a Gradle Daemon (subsequent builds will be faster) BUILD SUCCESSFUL in xxs 2 actionable tasks: 2 executed ``` ### 動作確認(シミュレーションの実行) 初めに,次のコマンドによってサーバを起動します. ###### Terminal A ```shell $ cd WORKING_DIR/rcrs-server $ cd boot $ bash start-comprun.sh ``` 次に,次のコマンドによってエージェント操作プログラムを起動します. ###### Terminal B ```shell $ cd WORKING_DIR/rcrs-adf-sample $ sh launch.sh -all ``` 次のようなウィンドウが現れ,ウィンドウ中の図形群が動き始めたら環境構築は成功です. ![](https://i.imgur.com/EaIgqY3.png) :::warning **NOTE:** 今回,画像中の瓦礫は無効化しているため表示されません. ::: サーバとエージェント操作プログラムは,シミュレーションが最後まで進むと自動的に終了します. サーバを途中で終了したい場合は,サーバを起動したコンソール上で以下のキーとコマンドを入力してください. ###### Terminal A ```shell Ctrl-C $ bash kill.sh ``` エージェント操作プログラムを途中で終了したい場合は,エージェントを起動したコンソール上で次のキーを入力してください. ###### Terminal B ```shell Ctrl-C ``` ## エージェントを動かす RRSの災害救助エージェントは3種類あり,種類毎にそれぞれ異なるプログラムを書く必要があります. しかし,初めから全てのプログラムを書くことは困難です. ここではまず初めに,消防エージェントを操作するプログラムの一部を書いてみましょう. :::info **NOTE:** エージェントを操作するプログラムは,エージェントの種類毎に同一です. プログラムは各エージェントに配られ,そのエージェントのみの操作を担います. 消防隊エージェントを操作するプログラムを書けば,それがすべての消防隊エージェント上でそれぞれ動作します. ::: ![](https://i.imgur.com/74ywybF.jpg) ### 書くプログラムとその役割 ![](https://i.imgur.com/rHqzxAr.png) 消防隊の思考ルーチンは上の図の通りにおおよそおこなわれます. それぞれの機能がモジュールと呼ばれるプログラムとして分割して表現されています. 今回はこの中で,消火対象を決定する `Building Detector` モジュールを開発します. ### プログラムを書くファイル 今回プログラムを書くために,次のコマンドで `AITBuildingDetector.java` を作成して下さい. ```shell $ cd WORKING_DIR/rcrs-adf-sample $ mkdir -p src/tutorial/module/complex $ touch src/tutorial/module/complex/AITBuildingDetector.java ``` このファイル全体が,`Building Detector` モジュール1個を表します. このファイルを,IDE 又はエディタで開いてください. :::info **NOTE:** 著者(宮本)は,IntelliJ IDEA の使用をお薦めしています. ただし,著者(宮本)は,Vimユーザなので助言が一切できません. ::: 初めに,次の内容を `AITBuildingDetector.java` に写してください. これが今回書くモジュールの雛形になります. ```java package tutorial.module.complex; import adf.agent.info.*; import adf.component.module.complex.BuildingDetector; import adf.agent.module.ModuleManager; import adf.agent.develop.DevelopData; import rescuecore2.standard.entities.*; import rescuecore2.worldmodel.EntityID; import java.util.*; public class AITBuildingDetector extends BuildingDetector { private EntityID result; public AITBuildingDetector( AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager mm, DevelopData dd) { super(ai, wi, si, mm, dd); } // Tactics内の2に相当 @Override public EntityID getTarget() { return this.result; } // Tactics内の1に相当 @Override public BuildingDetector calc() { return this; } } ``` ### モジュールの登録 エージェント操作プログラムが `AITBuildingDetector.java` を使用するように設定の変更をおこないます. `WORKING_DIR/rcrs-adf-sample/config/module.cfg` の13行目を,次のように変更してください. このファイルに記述されたモジュールが自動的にエージェント操作プログラムの一部として使用されます. ```yaml TacticsFireBrigade.BuildingDetector : tutorial.module.complex.AITBuildingDetector ``` 写し終わった後に再度 `./gradlew build` とサーバ・エージェントの実行をしてみて下さい. 消防隊が消火対象を選択しないため,消火活動ができずにほぼ全ての建物が燃え尽き黒色で塗りつぶされてしまいました. ### モジュールの実装 消火対象選択プログラムの中身を書き,消防隊エージェントが消火活動をおこなえるように修正します. 消防エージェントの消火対象を最も簡単に選択する方法は,燃えている建物の中で最もエージェントに近い建物を選択する方法です. 今回は,この方法を採用した消防エージェントの行動対象決定モジュールを書いてみましょう. 燃えている建物の中で最もエージェントに近い建物を消火対象として選択する方法は,フローチャートで表すと下図のようになります. このフローチャート中の各処理を,次小節で紹介する各クラス・メソッド等で置き換えたものを,`AITBuildingDetector.java` の `calc` メソッドの中に書くことでプログラムが完成します. :::info **NOTE:** フローチャート中に現れるエンティティとIDについては次小節で説明します. ::: ![](https://i.imgur.com/B0uxmIZ.png) ### 使用するクラス・メソッド #### WorldInfo エージェントが把握している情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,他のエージェントやオブジェクトの状態を確認します. `AITBuildingDetector` クラスのインスタンス中では,このクラスのインスタンスを `this.worldInfo` で参照することができます. #### AgentInfo エージェント自身の情報とそれに関する操作をおこなうメソッドをもったクラスです. エージェントはこのクラスのインスタンスを通して,エージェント自身の状態を取得します. `AITBuildingDetector` クラスのインスタンス中では,このクラスのインスタンスを `this.agentInfo` で参照することができます. #### EntityID 全てのエージェント/オブジェクトを一意に識別するためのID(識別子)を表すクラスです. RRSではエージェントとオブジェクトをまとめて,エンティティと呼んでいます. * エージェント自身の `EntityID` を `id` として取得する ```java EntityID id = this.agentInfo.getID(); ``` * エージェント `id` の周辺に位置するエンティティの `EntityID` を全て `ids` として取得する ```java int range = this.scenarioInfo.getPerceptionLosMaxDistance(); Collection<EntityID> ids = this.worldInfo.getObjectIDsInRange(id, range); ``` * `EntityID id1` と `EntityID id2` からそのエンティティ間の距離を `distance` として取得する ```java double distance = this.worldInfo.getDistance(id1, id2); ``` #### StandardEntity エンティティを表すクラスです. 全てのエンティティはこのクラスを基礎として表現されています. エージェントは `WorldInfo` と `EntityID` から `StandardEntity` を取得することができます. * `EntityID id` から `StandardEntity` を `entity` として取得する ```java StandardEntity entity = this.worldInfo.getEntity(id); ``` * `StandardEntity entity` から `EntityID` を `id` として取得する ```java EntityID id = entity.getID(); ``` #### Building 建物オブジェクトを表すクラスです.建物を表すことが分かっている `StandardEntity` から, ダウンキャストすることで得られます. このクラスからは,建物の位置や火災の進行状況を取得することができます. * `StandardEntity entity` が建物オブジェクトか確認( `isBuilding` )する ```java boolean isBuilding = entity instanceof Building; ``` * `StandardEntity entity` を建物オブジェクトに `building` として変換する ```java Building building = (Building)entity; ``` * `Building building` が燃えているか確認( `isBurning` )する ```java boolean isBurning = building.isOnFire(); ``` :::info **NOTE:** RRS上のエンティティは下図のように `StandardEntity` を継承したクラスで表現されています. 赤枠で囲まれたクラスは,クラスの意味がそのままRRSの直接的な構成要素を表しています. 例: `Road` クラスのインスタンスの中には, `Hydrant` クラスを継承してない通常の道路を表すものも存在しています. ::: ![](https://i.imgur.com/6ZhUZGf.png) :::info **NOTE:** 今回は必要ありませんが,各クラスから他にどのような情報が取得できるかは, 関連リンクを辿った先にある Javadoc を参照してください. 情報の中にはサーバのために用意されたもので, エージェント操作プログラムからは取得できないものもあります. ::: :::info **NOTE:** 情報はそのエンティティがエージェントの知覚範囲に入るまで,最新の状態に更新されません. 通信によって,知覚したエンティティの情報を共有することも可能です. ::: ### プログラム例 ```java package tutorial.module.complex; import adf.agent.info.*; import adf.component.module.complex.BuildingDetector; import adf.agent.module.ModuleManager; import adf.agent.develop.DevelopData; import rescuecore2.standard.entities.*; import rescuecore2.worldmodel.EntityID; import java.util.*; public class AITBuildingDetector extends BuildingDetector { private EntityID result; public AITBuildingDetector( AgentInfo ai, WorldInfo wi, ScenarioInfo si, ModuleManager mm, DevelopData dd) { super(ai, wi, si, mm, dd); } @Override public EntityID getTarget() { return this.result; } @Override public BuildingDetector calc() { EntityID me = this.agentInfo.getID(); int range = this.scenarioInfo.getPerceptionLosMaxDistance(); Collection<EntityID> ids = this.worldInfo.getObjectIDsInRange(me, range); EntityID nearestID = null; double nearestDistance = Double.POSITIVE_INFINITY; for (EntityID id : ids) { StandardEntity entity = this.worldInfo.getEntity(id); boolean isBuilding = entity instanceof Building; if (isBuilding) { Building building = (Building)entity; boolean isBurning = building.isOnFire(); if (isBurning) { double distance = this.worldInfo.getDistance(me, id); if (distance < nearestDistance) { nearestID = id; nearestDistance = distance; } } } } this.result = nearestID; return this; } } ``` :::info **NOTE:** 火災が発生した建物を消防隊が消火できていれば,動作成功だと思います. :::