掃除ロボット シミュレーション デモ

概要

このデモは、いわゆるロボットコンテスト的な簡単なシミュレーションです。部屋を想定した画面上を掃除ロボットが動き回り、部屋内のゴミ収集を行います。このデモは、ロボット制御ソフトウェア上でロボットの動作ロジックを組み立て、ロボットのハードウェアに関する単純な API を起動することで、ロボットが適切に部屋内を掃除できるようになることを目的とします。

掃除ロボットにはいくつかのセンサが搭載されています。制御ソフトウェアはセンサからのイベントをハンドリングすることによりロボットの動作を決定し、ロボットの駆動系に対し命令を発行します。

このデモでは、センサおよび制御ソフトウェアを MixJuice モジュールとして表現します。モジュールの組み合わせによる掃除ロボットの動作の違いを確認することができます。またユーザは、制御ソフトウェア部分のコードをモジュールとして記述し、実行時にリンクさせることでユーザオリジナルの動作ロジックでロボットを動作させることもできます。

仕様解説

掃除ロボット

本デモでは、掃除ロボットとして以下のような仮想的なハードウェアを想定しています。

制御ソフトウェア側から見た場合、掃除ロボット駆動系に対して以下の 5 つの命令を発行することができます。

これらの命令が発行されると、次の命令が発行されるまで車輪はそのまま回転し続けます。すなわち、「右回転」を行うと、「停止」ないしは他の命令の発行を行わない限り、掃除ロボットはひたすら右回転し続けることになります。

実装の簡易化のため、ロボットの移動単位は画面上の各区画毎とし、また回転単位は 90°とします(すなわち、斜めに移動したりはしません)。

ゴミ吸引口は掃除ロボットの底面にあり、ゴミ吸引モーターは常に動作しているものとします。したがって、ゴミの上を通過することでゴミを吸引することができます。

センサ

センサは、掃除ロボットに搭載される仮想的なハードウェアを想定しています。センサは掃除ロボット周辺の環境を検知し、制御ソフトウェアに対してイベントを発行します。センサの種類によって検知できる対象、および発行するイベントが異なります。

センサは各センサの機能に応じたイベントを発生させるのみなので、発生したイベントに応じて掃除ロボットをどのように動作させるか(例えば、前方に障害物を検知した場合、右方向に曲がるか、後退するか、あるいは何も行わないか、等)は制御ソフトウェアの実装に依存します。

本デモであらかじめ用意されているセンサは以下の 5 種類です。

方向センサ

掃除ロボットの向きを感知するセンサです。仕様上掃除ロボットは 4 方向にしか向かないので、このセンサが発行するイベントも 4 種類です。

モーターセンサ

掃除ロボットの動作状況を感知するセンサです。ここで言う動作状況とは、上記駆動系命令発行により変化する掃除ロボットの動作の状態です。したがって、駆動系命令と同様「前進中」「後退中」「右回転中」「左回転中」「停止中」の 5 つの状態が存在し、それぞれのイベントが発生します。

障害物センサ

障害物を検知するセンサです。掃除ロボットの前方、および左右のそれぞれ 1 区画に障害物があるかどうかを検知することができます。個々の区画について「障害物あり」「障害物なし」の状態毎にイベントが発生するので、計 6 種類のイベントが発生します。

ゴミセンサ

ゴミを検知するセンサです。掃除ロボットの前方、および左右のそれぞれ 1 区画にゴミがあるかどうかを検知することができます。個々の区画について「ゴミあり」「ゴミなし」の状態毎にイベントが発生するので、計 6 種類のイベントが発生します。

リモコンセンサ

掃除ロボットのマニュアル制御を可能とするため、掃除ロボットにはリモコンセンサを搭載することができます。ここで使用されるリモコンは、キーボードのキーにマッピングされています。ハンドリングできるキーイベントは 5 種類で、カーソルキーの上下左右、およびスペースキーを押したときのイベントです。

画面解説

以下にデモ実行画面を例示します。デモ画面をクリックすることで、デモが開始されます。

掃除ロボット

掃除ロボットです。赤くなっている部分が前方を表します。

障害物

障害物です。掃除ロボットは障害物の上を通ることができません。掃除ロボットの前方に障害物が存在しているのに前進する場合、または後方に障害物が存在しているのに後退する場合には、車輪は空回りし、掃除ロボットは移動できません。

床です。掃除済みないしは、もともとゴミが存在しなかった区画を表します。

ゴミ

ゴミです。掃除ロボットがゴミの上を通過するとゴミが掃除され、「床」の状態の区画に変化します。ゴミをすべて掃除するとシミュレーションが終了します。

経過時間

シミュレーションが開始されてからの時間を表します。単位時間あたりに掃除ロボットは1つの動作(「前進」「後退」「右回転」「左回転」「停止」のいずれか)を行えます。

ゴミ表示

ゴミの区画数を表します。(残りゴミ区画数) / (初期ゴミ区画数)と表示されます。

センサアイコン

掃除ロボットに搭載されているセンサを表します。これは、デモ起動時に指定する、センサに関するモジュールの組み合わせにより変化します。以下に、アイコンとセンサの対応を示します。

クイックスタート

センサや制御ソフトウェアといった各種モジュールの組み合わせによりデモの動作を変えることができます。ここでは、以下の 9 種類の組み合わせのアプレットを用意しています(リンクをクリックすると別ウィンドウが開きアプレットが動作します)。各モジュール、およびモジュールの組み合わせの詳細については後述します。

  1. フレームワークのみ
  2. フレームワーク、掃除ロボットのみ
  3. リモコンセンサ、マニュアル制御その1動作解説
  4. リモコンセンサ、方向センサ、マニュアル制御その2動作解説
  5. モーターセンサ、ランダム駆動を行う自動制御動作解説
  6. モーターセンサ、障害物センサ、障害物を回避する自動制御動作解説
  7. モーターセンサ、ゴミセンサ、ゴミを探索する自動制御動作解説
  8. 障害物回避、ゴミ探索を組み合わせた自動制御動作解説
  9. 障害物回避、ゴミ探索、ランダム駆動を組み合わせた自動制御動作解説

ソースコード

ダウンロード

ソースコードのみを閲覧する場合には、以下よりダウンロードしてください。

画像ファイルを含む、動作可能なソースコード群のアーカイブは以下よりダウンロードしてください。

コンパイル/実行環境

本デモをソースコードからコンパイル/実行するためには、以下の環境が必要です。

コンパイル/実行方法

以下に、コンパイル/実行方法を示します。ここではすでに Java および MixJuice の実行環境が整備されていることを前提とします。

  1. ダウンロードした AutoSweeperDemo.zip を適当なディレクトリに解凍します。
  2. 解凍したディレクトリの直下 AutoSweeperDemo ディレクトリに移動し、以下のように mjc コマンドを用いてコンパイルを行います。
    % mjc AutoSweeperDemo.java
    
  3. モジュールの組み合わせを指定して、mj コマンドを実行します。例えば「リモコンセンサ、マニュアル制御その1」という組み合わせで動作させる場合には、以下のように実行します。
    % mj -s sensor.key control.manual
    

動作解説

ここでは、詳細なプログラムの解説を行う前に、モジュールの組み合わせとしていくつか例を挙げながら本デモの動作の解説を行います。

センサのモジュールには sensor.* という名前が、制御ソフトウェアのモジュールには control.* という名前が命名されています。以下の実行例に記述されている mj コマンド実行時のモジュール指定から、センサ/制御ソフトウェアの組み合わせを推測してみてください。

マニュアル動作その1

まず、リモコンセンサと、簡単なマニュアル動作の制御ソフトウェアとの組み合わせにより掃除ロボットを操作してみることとします。

ここで用意する制御ソフトウェアは、リモコンセンサのイベントを受け取ると直接掃除ロボットの駆動系命令を発行します。すなわち、以下のようにキーボードを操作して掃除ロボットを制御します。

リモコンセンサが発行するイベント 発行する駆動系命令
カーソルキー↑押下 前進
カーソルキー↓押下 後退
カーソルキー→押下 右回転
カーソルキー←押下 左回転
スペースキー押下 停止

この例を動作させるには、以下のように実行します。

% mj -s sensor.key control.manual

マニュアル動作その2

マニュアル動作その1 を動作させてみると、操作に慣れまで多少苦労するかと思います。これは、制御ソフトウェア control.manual は、リモコンセンサからのイベント(キーボードの入力)を掃除ロボットの駆動系命令発行に単純にマッピングしているだけなので、キーボードの上下左右という操作と、掃除ロボットの画面上での上下左右移動が直感的にマッピングされていないためです。そこで次に、キーボードからの上下左右の入力により、掃除ロボットを画面上で上下左右に動かすような制御ソフトウェアを考えます。

このような動作を、掃除ロボットが持つ駆動系命令で実現するためには、掃除ロボットの向きに応じて動作を変える必要があるのが分かります。例えば、カーソルキー↑を押したときに掃除ロボットを上方向へ移動させるためには、掃除ロボットの向きに応じて次のような動作を組み合わせる必要があります。

掃除ロボットの向き 上方向へ移動させるのに必要な動作
前進
左回転 + 前進
右回転 + 前進
後退

ここで紹介する制御ソフトウェア control.manual2 は、リモコンセンサ、方向センサのイベントから、掃除ロボットを上記のように制御するよう実装されています。この例を動作させるには、以下のようなモジュールの組み合わせで実行します。

% mj -s sensor.key -s sensor.direction control.manual2

ランダム駆動を行う自動制御

リモコンセンサを用いたマニュアル制御に関しては以上とし、次に掃除ロボットを自動駆動させる制御ソフトウェアを考えます。

以下ではいくつかの制御ソフトウェアを実装しますが、その前に実装を容易にするための制御ソフトウェアをあらかじめ用意します。掃除ロボットの駆動系命令は「右回転」「左回転」というものであり、「右方向へ移動」「左方向へ移動」という動作を行うには、必ず「前進」と組み合わせる必要があります。そこで、モーターセンサからの「右回転中」「左回転中」のイベントを受け取ると「前進」を行うような制御ソフトウェアを用意します。これをモジュール control.turnAndForward に実装し、以下の制御ソフトウェアのモジュールは control.turnAndForward を extends するようにします。これにより、「右方向へ移動」「左方向へ移動」を行いたい場合には、単に「右回転」「左回転」の命令を発行すればよいことになり、以下の実装が容易になります。

さて、まずはある確率でランダムに「右回転」「左回転」を行う制御ソフトウェアのモジュール control.random を実装します。このモジュールは上述 control.turnAndForward を extends しているため、正常に動作するためには、モーターセンサ (sensor.motor) と組み合わせて実行する必要があります。

実行して分かる通り、これでは非常に動作の効率が悪く、とても部屋全体を掃除できるものではありません。

% mj -s sensor.motor control.random

障害物を回避する自動制御

次に、障害物を回避する制御ソフトウェアを考えます。障害物センサは、掃除ロボットの前方、および左右の 3 区画の障害物を検知することができます。ここで実装する制御ソフトウェア control.avoidWall モジュールでは、以下のように単純な障害物検知による動作を行うこととします。この実装では、デフォルト状態で掃除ロボットは前進していることを前提としています。

障害物センサによる検知(○:検知、×:未検知、−:無関係) 発行する駆動系命令
前方 右方向 左方向
× (動作変化なし)
× 右回転
左回転

この制御ソフトウェアを使用したデモを動作させるには、次のように実行します。実行して分かる通り、これだけの実装では、掃除ロボットはすぐに袋小路にはまってしまいます。

% mj -s sensor.wall -s sensor.motor control.avoidWall

ゴミを探索する自動制御

次に、ゴミセンサを用いた、ゴミを探索する制御ソフトウェアを考えます。ゴミセンサについても障害物センサ同様、掃除ロボットの前方、および左右の 3 区画のゴミを検知できます。ここで実装する制御ソフトウェア control.seekTrash モジュールでは、以下のような単純なゴミ検知による動作を行うこととします。

ゴミセンサによる検知(○:検知、×:未検知、−:無関係) 発行する駆動系命令
前方 右方向 左方向
前進
× 右回転
× × 左回転
× × × (動作変化なし)

この制御ソフトウェアを使用したデモを動作させるには、次のように実行します。これも実行して分かる通り、掃除機はすぐに障害物にぶつかって動けなくなってしまいます。

% mj -s sensor.trash -s sensor.motor control.seekTrash

障害物回避、ゴミ探索を組み合わせた自動制御

これまでの自動制御では、いずれもすぐに掃除ロボットは適切な動作をできなくなってしまいました。そこで、次はためしに障害物回避、ゴミ探索の 2 つの制御ソフトウェアモジュールを組み合わせて動作させてみます。ここで注目すべきところは、2 つの制御ソフトウェアを組み合わせるにあたって、さらなる実装は必要がない、ということです。以下のように、両者のデモ実行時に使用したモジュールを、実行時に組み合わせるだけです。

mj -s sensor.wall -s sensor.trash -s sensor.motor \
   -s control.avoidWall control.seekTrash

今度は、なかなかゴミ掃除が進むようになりました。しかし、これでも完全に部屋内を掃除することはできません。これは、部屋の中の障害物とゴミの配置にも依存しますが、ゴミが少なくなると 障害物を回避する自動制御 と同様、袋小路にはまる可能性が高くなるためです。

障害物回避、ゴミ探索、ランダム駆動を組み合わせた自動制御

そこで、さらにランダム駆動を行う制御ソフトウェアモジュールを組み合わせて動作させてみます。

mj -s sensor.wall -s sensor.trash -s sensor.motor \
   -s control.avoidWall -s control.seekTrash control.random

これで時間はかなりかかりますが、部屋全体を掃除することができるようになりました。

センサと制御ソフトウェアに不整合がある組み合わせ

さて、ここまで見て分かる通り、制御ソフトウェアを適切に動作させるためには、制御ソフトウェアが使用する(制御ソフトウェアが動作の前提としているイベントを発行する)センサのモジュールと組み合わせて動作させることが必要となります。

しかし、例えば以下のように、センサと制御ソフトウェアに不整合がある組み合わせでデモを実行すること自体は可能です。

  1. 制御ソフトウェアが必要とするセンサが搭載されていない
  2. 制御ソフトウェアが必要としない余分なセンサが搭載されている

1. の例として、マニュアル動作その2 で方向センサを取り外して実行させてみます。

% mj -s sensor.key control.manual2

実行してみると、カーソルキーの入力に対して非常に不可解な動作をするのが分かります。実際には、方向センサが搭載されていないため常に一方向(右方向)を向いていると制御ソフトウェアが判断し、リモコンセンサからのイベントに対してその状態に応じた動作をしています。

また、2. の例として、同様に マニュアル動作その2 で不必要な障害物センサを搭載して実行してみます。

% mj -s sensor.key -s sensor.direction -s sensor.wall control.manual2

センサアイコンには、障害物センサが搭載されている表示がありますが、制御ソフトウェア側には障害物センサからのイベントをハンドリングする実装は何もないため、この場合掃除ロボットの動作には全く影響がありません。

なお、制御ソフトウェアが必要とするセンサのモジュールを自動的にリンクし、実行時にわざわざセンサのモジュールを指定しないで済むようにすることもできますが、本デモでは、上記のような不整合のある組み合わせでも実行できるよう、あえてそのようには行っていません。

モジュール概説

ここからは本デモプログラムの実装についての解説を行います。

まずは、本デモプログラムの MixJuice モジュール構成の全体像を以下に示します。なお、以下の図では、便宜的にモジュールを UML パッケージとして表現しています。また、モジュール間の extends ないしは uses の関係を UML の依存関係として表現し、二者の区別をステレオタイプ表記にて行っています(簡略化のため、一部ステレオタイプ表記を省略している部分もあります)。

本デモプログラムにおけるモジュールは、大きく以下の 3 種類に分類されます。

  1. シミュレーションルール、(仮想的な)ハードウェアを表現するモジュール(薄い黄色のモジュール)
  2. ハードウェアと制御ソフトウェアのインターフェースとなるモジュール(濃い黄色のモジュール)
  3. 制御ソフトウェアを表現するモジュール(オレンジ色のモジュール)

本デモプログラムでは、制御ソフトウェアはユーザが実装することを想定しています。この場合、制御ソフトウェアが直接シミュレーションのルールやハードウェア部分には触れられない必要があります。すなわち、3. のモジュール群およびユーザが実装する制御ソフトウェアは 1. のモジュール内のクラスに差分を追加したり、参照したりすることは許されません。制御ソフトウェアは、必ず 2. のモジュール群のみの差分追加/参照を行うよう実装します。

なお、モジュール間の関係として extends ではなく、uses を使用している部分があるのが分かります。本来 uses は、モジュール間に循環関係がある場合でもそのモジュールを利用できるよう、module の linearize 時の上下関係に制約を設けないために利用されますが、実際本プログラム上のモジュールに循環関係はありません。ここでは「相手モジュールに含まれるクラスの差分は記述しておらず、モジュールに含まれるクラスを利用しているだけ」ということをソースコード上で明記するために使用しています。

モジュール詳細

ここでは、各モジュールの実装に関する詳細を記述します。

base モジュール

base モジュールは、本デモプログラムの基盤を成すモジュールです。以下に base モジュールのクラス構成を示します。クラス図において、薄い黄色で表記されているクラスは、このモジュールの中で定義 (define) されているクラスを表しています。

BaseApplet

java.awt.applet.Applet を継承したアプレットクラスです。

DrawCanvas

シミュレーションの描画領域を管理するクラスです。java.awt.Canvas を継承し、update() および paint() を実装することで、シミュレーション画面を描画します。また、描画領域はさまざまなクラスから参照できる方が便利であるため、(擬似的な)Singleton クラスとなっています。

Round

シミュレーションのシーケンスを制御するクラスです。java.lang.Runnable を実装し、スレッドとして動作します。シミュレーションの開始、終了およびシミュレーションの時間を管理します。シミュレーション内単位時間毎に doAction() というメソッドを呼び出しますが、このモジュール内での実装は、経過時間、ゴミ表示を描画するのみです。

Room

シミュレーションにおける "部屋" を表現するクラスです。障害物やゴミといった部屋のレイアウトを管理します。なおこの実装では、部屋のレイアウトはこの Room クラスにハードコードされています。部屋の描画(draw() メソッド)時に Floor(床)、Trash(ゴミ)、Wall(障害物)のインスタンス化を行います。

なお、SS クラスの main() メソッドはこの base モジュールに実装されています。したがって、base モジュール単独でも動作させることはできます。base モジュール内にまだ掃除ロボットは定義されていないので、実行時には掃除ロボットは登場せず、ただ刻々と時間が経過していくというシミュレーションの "場" が表示されるのみです。

% mj base

control モジュール

control モジュールは、ハードウェアと制御ソフトウェアとのインターフェースとなるクラス群が定義されているモジュールです。

Sweeper

Sweeper は、制御ソフトウェア側から見た掃除ロボットハードウェアのインターフェースです。制御ソフトウェアは、Sweeper の各メソッドを呼び出すことにより、掃除ロボットハードウェアに対して動作の命令を行います。

Controller

一方、Controller はシミュレーション/ハードウェア側から見た制御ソフトウェアのインターフェースです。init() はシミュレーション開始時に、control() はシミュレーション内単位時間毎にシミュレーション側から呼び出されます。両メソッドは、control モジュールでの定義時には実装は空となっています。制御ソフトウェア実装者は、この Controller クラスを実装し、適切に Sweeper の各メソッドを呼び出すことで、掃除ロボットを制御します。

なお、Controller クラス内には、Sweeper の参照を取得するための getSweeper() というメソッドが用意されており、制御ソフトウェア実装者が利用することができます。また、Controller クラスのインスタンス化はシミュレーションの内部で行われるので、制御ソフトウェア実装者がコンストラクタを呼び出す必要はありません。

hardware モジュール

hardware モジュールは、掃除ロボットのハードウェアを実装するモジュールです。

SweeperImpl

掃除ロボットの実装クラスです。control モジュールの Sweeper インターフェースを実装します。Sweeper インターフェースの各メソッドの実装は、後述の ActionCommand の各具象クラスをインスタンス化し保持するのみです。

Direction

掃除ロボットの向きを表します。このクラス自体は 360°の向きを 1°毎に表現できますが、本デモでは、90°毎のインスタンスしか使用しません。

ActionCommand

掃除ロボットの動作を表すインターフェースです。シミュレーション単位時間毎に掃除ロボットが動作を行う場合には、SweeperImpl がその時点で保持する ActionCommand の exec() メソッドを呼び出します。このインターフェースを実装したクラスとして、ForwardCommand, BackCommand, RightCommand, LeftCommand, StopCommand という具象クラスが存在します。これらはそれぞれ「前進」「後退」「右回転」「左回転」「停止」という掃除ロボットの動作を表し、exec() メソッドを実装しています。

次に、base モジュールとの関係をレイヤードクラス図にて表記します。

Round

シミュレーション内単位時間毎に呼び出される doAction() メソッドに、掃除ロボットを動作させるメソッド呼び出しを追加しています。

DrawCanvas

このモジュールで追加された SweeperImpl のインスタンスを新たに管理します。また、画面描画を行う draw() メソッドに、掃除ロボットの描画処理を追加しています。

sensor モジュール

sensor モジュールは、各センサ実装モジュールのための super-module です。

Sensor

各センサのスーパークラスです。各センサは、抽象メソッド sense() にセンサの機能を実装します。センサがその機能を満たすには Room, SweeperImpl, Controller の参照を得る必要があるため、それぞれ getRoom(), getSweeper(), getController() というメソッドが用意されており、センサ実装時に利用できます。

SensorSet

センサのコンテナクラスです。sense() を呼び出すことで、保持するすべてのセンサに対して sense() を呼び出すことができます。

このクラスには、includeSensors() という Template Method が用意されています。これは、SensorSet のコンストラクタの中で呼び出されるので、各センサを記述するモジュール内でこのメソッドの差分としてセンサの登録処理を記述することができます。これにより、センサに依存する登録処理もセンサ毎のモジュール内で閉じて記述でき、コードの局所化をはかることができます。センサの登録には add() メソッドを用います。

次に、base モジュールとの関係をレイヤードクラス図にて表記します。

Round

シミュレーション内単位時間毎に呼び出される doAction() メソッドに、センサ検知のためのメソッド呼び出しを追加しています。

DrawCanvas

このモジュールで追加された SensorSet のインスタンスを新たに管理します。また、draw() メソッドに、センサアイコン群の描画処理を追加しています。

sensor.* モジュール および event.* モジュール

sensor.* というモジュール群は、各センサの実装モジュールです。ここには、sensor モジュールの Sensor クラスを継承した各センサクラスが定義/実装されています。

また、event.* というモジュール群は、センサのイベントハンドラメソッドを定義するモジュールです。cotrol モジュールを extends し、各センサが発行するイベントに対応するイベントハンドラメソッドを、Controller クラスに追加しています。なお、event.* モジュールにおけるこれらのメソッド実装はすべて空です。

モジュール概説 を見ても分かる通り、sensor.* モジュールと event.* モジュールは対になっています。すなわち、sensor.* モジュールの各センサクラスの実装では、対になる event.* モジュールで定義された Controller クラスのメソッドを呼び出すことで、制御ソフトウェア側にイベントの伝達を行います。

control.* モジュール

control.* というモジュール群は、本デモにおける制御ソフトウェアの参照実装群です。これらのモジュールは、制御ソフトウェアでハンドリングしたいイベントの event.* モジュールを extends しています。制御ソフトウェアのクラスは Controller であり、それぞれイベントハンドラメソッドおよび init(), control() メソッドを実装することで、制御ソフトウェアのロジックを記述しています。

各モジュールの実装については、動作解説 の説明を参照してください。

モジュール機構利用による特長

本デモを実装するにあたり、MixJuice のモジュール機構がどのように利用され、実装の簡易化・コードの集約化などにどのように貢献しているかについては、前節まででいくつか紹介されていますが、ここではそれらをまとめて記述します。

センサ、制御ソフトウェアの組み合わせによる動作

冒頭から説明してきた通り、本デモは MixJuice のモジュール機構を用いることで、以下のような組み合わせを実行時に選択することができます。

なお、ここでひとつ考慮しておく必要があるのは、「複数の制御ソフトウェアモジュールの組み合わせ」についてです。障害物回避、ゴミ探索を組み合わせた自動制御 の例では、「障害物を回避する制御ソフトウェア」 control.avoidWall モジュールと「ゴミを探索する制御ソフトウェア」 control.seekTrash モジュールを組み合わせて動作させました。実際に動作させてみると、これらはうまく協調して望み通りに掃除ロボットを制御できているように見えます。しかし、厳密な意味では、いくつかのケースでは掃除ロボット制御に関する競合が発生しています。以下に例を示します。

掃除ロボットが上図のように前進してきた場合、control.avoidWall は障害物センサからの、control.seekTrash はゴミセンサからのイベントを元に、それぞれ矢印の異なる方向へ移動する命令を発行します。これらの命令は各モジュールの Controller#control() にて起動するよう実装されています。

module control.avoidWall
extends event.wall, control.turnAndForward
{
    class Controller {
        void control() {
            original();
            ...
                getSweeper().turnRight(); // これが実行
            ...
        }
    }
}

module control.seekTrash
extends event.trash, control.turnAndForward
{
    class Controller {
        void control() {
            original();
            ...
                getSweeper().turnLeft(); // これが実行
            ...
        }
    }
}

では、実際には、掃除ロボットはどちらの方向に移動するでしょうか?実際には、上方向、すなわち control.seekTrash による命令が有効となります。この理由は以下の通りです。

  1. control() 内で複数回 Sweeper に対するメソッド呼び出しが行われた場合、最後のメソッド呼び出しが有効となるよう、ハードウェア側は実装されている。
  2. MixJuice のモジュール linearize 機構により、control.seekTrash は linearize list 上 sub-module となる。

1. については本サンプルの仕様と呼べるものなので特に問題はありません。しかし 2. については、MixJuice ではこれを期待したコード記述を行うことが禁止されているので、この二つのモジュールを組み合わせて実行するユーザが、「このようなケースでは control.seekTrash を優先させたい」と考えるのであれば、問題があることとなります。そもそも、制御ソフトウェアが Sweeper に対して異なる複数の命令を同時に発行する事自体が矛盾する動作ではあります。このようなケースでも厳密に一つの命令を発行させるためには、障害物センサとゴミセンサのイベントを同時にハンドリングできる制御ソフトウェアを一から記述するべきであり、個々のイベントをハンドリングする複数の制御ソフトウェアを組み合わせるべきではありません。

ただし、「命令が競合する場合には、どちらか一方の命令が有効になればよい」という曖昧さをユーザが許容するのであれば、今回のように複数の制御ソフトウェアを組み合わせることも許容されます。このように処理系依存ながらも、新たなコードの記述もなく、ある程度メソッドの実装を組み合わせることができるということも MixJuice の特長と言えます。

Round および DrawCanvas の実装

hardware モジュール、および sensor モジュールでは、base モジュールで定義される Round および DrawCanvas の実装に差分追加を行っています。追加される差分実装は、追加が行われるモジュール内で新たに定義されたクラスに関するものであり、実装の局所化が実現されています。

センサの登録処理

センサに依存するセンサの登録処理を、センサを実装するモジュールに局所化できます。具体例として、リモコンセンサモジュール sensor.key を挙げます。

module sensor.key
extends sensor
uses event.key
{
    define class KeySensor extends Sensor implements KeyListener {
        define KeySensor() { ... }
        void sense() {}
        void keyPressed(KeyEvent e) { ... }
        void keyReleased(KeyEvent e) {}
        void keyTyped(KeyEvent e) {}
    }
    class SensorSet {
        void includeSensors() {
            original();
            KeySensor s = new KeySensor();
            add(s);
            DrawCanvas.instance.addKeyListener(s); // リモコンセンサにのみ必要
        }
    }
}

リモコンセンサクラス KeySensor はキーイベントを捕捉する必要があるため、java.awt.event.KeyListener を実装しています。これは、AWT コンポーネントへキーリスナとして登録する必要がありますが、この処理はリモコンセンサに特殊であり他のセンサには不要な処理です。モジュール機構を用いることで、このような特殊な処理を、特殊な処理が必要なモジュール自身に記述することができ、コードの局所化が図られています。

モーターセンサの実装

モジュール機構の利用により、モーターセンサの実装は非常に簡潔かつ局所化されたものとなっています。

hardware モジュールには、掃除ロボットの動作を表す ActionCommand インターフェースおよび各具象クラスが定義されています。この仕組みを利用し、sensor.motor モジュールにて、これらのクラスおよびインターフェースにイベント通知を行う notify() メソッドを追加します。これにより、MotorSensor の sense() メソッドは、追加した notity() メソッドを SweeperImpl 経由で呼び出すだけで実装が完了し、非常に簡潔なものとなります。また、モーターセンサに関わる実装がすべて sensor.motor モジュールに集約されているのが分かります。

さらなる拡張

ここでは、本サンプルをさらに拡張するための指針を提示します。実際に MixJuice のコードを書いてみる練習として、是非ともチャレンジしてみてください。

制御ソフトウェアの追加

本デモはユーザが制御ソフトウェアを追加で実装することを想定しています。より確実に部屋を掃除するためには、例えば以下のような制御ソフトウェアを実装することが考えられます。

塗りつぶしのアルゴリズムを利用した制御ソフトウェア

モーターセンサ、方向センサ、障害物センサを利用して実装できると考えられます。確実に部屋全体を掃除することができますが、スタックポイントまで毎回戻る動作が入り、多少無駄な動きが多いかもしれません。

部屋の地図を描きながら、走行していない区画を探索する制御ソフトウェア

より最適に部屋全体の掃除を行えるものと考えられます。ただし、走行していない区画への最適経路探索や、障害物で囲まれた(障害物の)区画を走行不可能な区画と判断するアルゴリズムなど、実装の難易度は若干高いかもしれません。

ハードウェアやシミュレーションルールの変更

今回の実装は、最低限の機能に限られているため、必ずしもゲーム性が高いとは言えません。例えば、以下のような変更を加えることで、ゲームとしてより幅を持たせることが可能になると考えられます。


mj-logo