実は筆者,Javaをずっと毛嫌いしていた。言語仕様が決めきめで「こんな風に書かなくてはいけない」というルールが多いからだ。また,言語仕様を覚えただけでは十分ではなく,フレームワークの勉強もしなくてはいけない。例えば,WebアプリではStrutsなどのフレームワークを勉強しなくてはいけない。大規模なアプリを作る場合,フレームワークを適用すると処理の仕方が統一され開発効率が上がるというメリットはたしかにそうだと思う。でも,理解しなくてはいけないことが多く,XMLの設定やエラーメッセージなどを記したApplicationResource.propertiesのKeyがプログラムの動作に関与したりするので,慣れないとどうも使いにくい。Javaの文法はコンパイラがチェックしてくれるが,複雑な設定ファイルの入力ミスが混乱の元になることがある。
7月22日 市内の小学校のコンピュータ室で児童を相手にロボットプログラミングの講習を行う。
ペットボトルを倒し,黒い線を感知したら一旦停止し,ピンポン玉をゴールに向かって押し出すというゲームだ。子供たちにロボット作成の注意点を説明した後,自分はEclipse+LejOSでJavaプログラミングを始めた。
ROBOLABでロボットのプログラムを作成する児童からは質問や「プログラムがIRタワーで送れなくなった」などという苦情があがる。サポートしながら,プログラムを作っているとなかなか考えがまとまらない。そして,あせるとクラスもインターフェイスも継承もぶっ飛んでしまうのだ。ひとつのクラスにズラズラとコードを書いてしまう。例えば,こんなコードだ。
aという名前で作成したら,
---------------------------------------------------------- Motor.A.forward(); Motor.C.forward(); try {Thread.sleep(2000);} catch (InterruptedException e) {} Motor.A.forward(); Motor.C.backward(); try {Thread.sleep(200);} catch (InterruptedException e) {} ----------------------------------------------------------まっすぐ,2000ミリ秒(2秒)進んで,次に右に曲がるというコードである。もちろん,Eclipseのリファクタリング機能を使ってメソッドを次のように抽出することは可能だ。
---------------------------------------------------------- private static void turnright() { Motor.A.forward(); Motor.C.backward(); try {Thread.sleep(200);} catch (InterruptedException e) {} } ----------------------------------------------------------しかし,これでは非オブジェクト指向言語であるC言語に似たNQCでプログラミングするのと同じである。複雑な処理を関数に分けてわかりやすく,かつ使いやすくするだけのことである。
競技会やコンテストでは,決められた時間内であせってプログラミングすることも多いだろう。そんなときにパワーコーディングにならない仕組みがLeJOSには用意されている。それが,行動制御APIだ。原題はThe Behavior API だから,行動APIと書くほうが正しいのかもしれない。
行動APIはBehaviorインターフェイスと,Arbitrator(仲裁するもの)クラスで構成される。Behaviorインターフェイスを実装するクラスを複数作成し,Arbitratorクラスのオブジェクトに仲裁を依頼するのである。Behaviorインターフェイスを実装するクラスが行動を示す。
たとえば,次のような行動を記述する。
---------------------------------------------------------- 1番目のクラスの行動は真っ直ぐ走る。 2番目のクラスの行動は障害物にあたったら,バックし進行方向を変える。 3番目のクラスの行動は黒い線を見つけたら停止し,ビープを2回鳴らす。 ----------------------------------------------------------MindStromsの基本部品であるモーターとタッチセンサー,光センサーを全部使っている。これらのクラスのインスタンスを作成し,配列としてArbitratorに渡すのだ。
Arbitratorの原則は「上位の行動は下位の行動を抑制(suppress)する」である。上位か下位かは配列のインデックスで決まる。
---------------------------------------------------------- package behavior; import josx.robotics.*; public class BumperCar { public static void main(String [] args) { Behavior b1 = new DriveForward(); Behavior b2 = new HitWall(); Behavior b3 = new DarkLine(); Behavior [] bArray = {b1, b2, b3}; Arbitrator arby = new Arbitrator(bArray); arby.start(); } } ----------------------------------------------------------この例の場合,真っ直ぐ走るDriveForwardが一番下位の行動になり,黒い線で停止するDarkLineが最上位の行動である。
まず,DriveForwardクラスからみていこう。
---------------------------------------------------------- package behavior; import josx.robotics.*; import josx.platform.rcx.*; public class DriveForward implements Behavior { public DriveForward(){ Motor.A.setPower(2); Motor.C.setPower(2); } public boolean takeControl() { return true; } public void suppress() { Motor.A.stop(); Motor.C.stop(); } public void action() { Motor.A.forward(); Motor.C.forward(); } } ----------------------------------------------------------takeControlがtrueを返すときは,actionメソッドを実行し真っ直ぐ走る。takeControlは常にtrueを返すだけなので,ロボットは真っ直ぐ走り続ける。takeControlがfalseを返すようにすると,ロボットはちんとしている(富山弁講座 ちんとする : 少しかしこまって,じっとしている様子。落ち着きのない子に「ちんとしとられ」などのように使う)。
HitWallクラスは壁に衝突したときに回避行動をとることで,DriveForward クラスの真っ直ぐ走るという行動を抑制(suppress)する。
---------------------------------------------------------- package behavior; import josx.robotics.*; import josx.platform.rcx.*; public class HitWall implements Behavior, SensorListener{ boolean hasCollided; public HitWall() { hasCollided = false; Sensor.S2.addSensorListener(this); } public void stateChanged(Sensor bumper,int oldValue, int newValue) { if (bumper.readBooleanValue()==true) { hasCollided = true; } } public boolean takeControl() { if (hasCollided) { hasCollided = false; return true; } else { return false; } } public void suppress() { Motor.A.stop(); Motor.C.stop(); } public void action() { // Back Motor.A.backward(); Motor.C.backward(); try{Thread.sleep(200);}catch(Exception e) {} // Rotate Motor.A.forward(); try{Thread.sleep(200);}catch(Exception e) {} Motor.C.stop(); } } ----------------------------------------------------------センサーリスナーを追加し,bumper.readBooleanValue()がtrueを返したときに,ブール型のインスタンス変数hasCollidedをtrueにする。takeControlではhasCollidedを参照しtrueだったら,trueを返すのでaction(回避行動)が開始される。
最上位のDarkLineクラスは,直進中でも回避行動中でも,黒い線を通過したらロボットを停止させ,ビープ音を2度鳴らす。
---------------------------------------------------------- package behavior; import josx.platform.rcx.LCD; import josx.platform.rcx.Motor; import josx.platform.rcx.Sensor; import josx.platform.rcx.Sound; import josx.platform.rcx.SensorListener; import josx.robotics.Behavior; public class DarkLine implements Behavior, SensorListener{ private boolean hasPassed; private final int sikii=40; public DarkLine() { hasPassed = false; Sensor.S1.activate(); Sensor.S1.addSensorListener(this); } public void stateChanged(Sensor light,int oldValue, int newValue) { if (newValue < sikii) { LCD.showNumber(newValue); hasPassed = true; } } public boolean takeControl() { if (hasPassed) { hasPassed = false; return true; } else { return false; } } public void suppress() { Motor.A.stop(); Motor.C.stop(); } public void action() { Motor.A.stop(); Motor.C.stop(); Sound.twoBeeps(); try {Thread.sleep(3000);} catch (InterruptedException e) {} Motor.A.forward(); Motor.C.forward(); try {Thread.sleep(100);} catch (InterruptedException e) {} } } ----------------------------------------------------------光センサーをセンサーリスナーに追加し,しきい値(sikii=40)より小さい値を返したら,hasPassedをtrueにしている。takeControl がtrueを返したときのactionは停止後,ビープ音を2回鳴らし,3秒待って再度,動くというものだ。
この仕組みはたしかに便利である。行動APIを利用すればロボットの複雑な動きを一つの目的を持ったクラスの積み重ねで表現できるからだ。