亀場でプログラミング:群れロボット
このページでは、亀ロボットを使って「群れ」を考察してみる。
このサイトのオーナーは、ここのところ、動物(特に鳥)の「群れ」の研究を行っている。 実際のところ、亀が群れを形成するものかどうかよく判らないけれども、プログラミングの 練習、という意味で取り組んでほしい。
1. 集団行動させる
亀場に居る自分以外の亀たちを「敵」とみるか、「仲間」と見做すかで、行動の様子は自ずと違ってくるはずだ。 多くの動物は「群れ」を形成することによって環境への適応性を高めているし、植物もコロニーなどを形成することで 集団として生きながらえているばかりか、それによって環境のほうも少なからず変化を遂げている。
そして、もちろん、我々人間とて一人では生きていけない。まさに no man is an island だ。
ここでは、掃除プログラムや対戦プログラムに手を加えて、亀に協調的な行動をさせててみよう。 協調的な行動をさせるためには、もちろん、相手の様子をある程度把握しておく必要がある。 そのために使えるセンサーを、まず整理しておこう:
名称 | 機能 | C関数 | コメント |
---|---|---|---|
FINDER | 前方正面の物体を識別する | int res; |
0:障害物なし、1:亀、2:ドーナッツ、 3:石、4:壁 |
RADAR | 一番近い物体までの方向角(°)を検出する | float angle; |
正面方向の角度0、左方向が角度正 |
SONAR | 斜め左、正面、斜め右の障害物の有無を検出 | int left,front,right;; |
1:障害物あり、0:無し |
近くの亀の方向に進路を向ける
これらのセンサーを使って行動をプログラムするためのヒントを、ひとつふたつ紹介しておく。 例えば、一番近い亀の方向に徐々に舵を切るには、RADARを使って
for (cnt=0 ; cnt<反復回数 ; cnt++ ) { Q_RADAR(&angle) ; LT(angle) ; } FD(2);
と書くやり方が考えられる。 RADARから得た相手との相対角度(反時計回りが正)に、自分の方向角を LT(angle) で変えてやるわけだ。 ところが、「対戦モード」中は、3°よりも大きい回転角を指定しても、1回あたり3°しか亀は回転しない。 そこで、反復回数によって「切れ角」を調整しようというのが、上のコードのアイデアである。
衝突を回避する
斜め前方に別の亀が居たときに、衝突を回避するため、ちょっとかわすには、SONARを使って
Q_SONAR(&left,&front,&right) ; if (left==1 && front==0 && right==0) { RT(3) ; } else if (left==0 && front==0 && right==1) { LT(3) ; }
といったコードも有効なはずだ。
練習:亀のダンス
周囲に亀がいたら、その亀に追従し、集団(あるいは「群れ」)を形成するよう、行動をプログラムしなさい。 また、実際に数匹の亀を放ち、その動作を確認しなさい。
ヒント
ついでながら、軍事パレードなどで見られるように互いの間隔や位置関係まできっちり揃えようとすると、 群れは「不安定」になりやすい。ちょっとした乱れ(誰かがつまづいたり)が増幅されながら周囲に伝搬し、 ドミノ倒しのような状況に陥る可能性があるためだ。
動いている物体(動物、ロボット、乗り物等)が群れを形成するためには、(i)互いの運動の方向を揃えようとする、と (ii)、互いの相対速度をできるだけ揃えようとする、という風に振る舞うように行動をプログラムする必要があるだろう。 例えば、上のSONARを使った衝突回避のプログラムで、相手をかわす角度を大きくしすぎると、相対的な入射角度 より反射角度のほうが大きくなってしまうので、(i)に反してしまう。 また、それぞれのロボットの運動速度が違ってしまっては(ii)を満足しない。
亀場の周囲には壁があるので、ロボットがうまく群れを形成できたとすると、その行動は、水族館の水槽を ぐるぐる廻る魚の群れのような風になるだろう。もっとも、亀の数は魚群よりもずっと少ないので、むしろ、 回転しながらダンスをしているように見えるかもしれない
それぞれの実行プログラム(a.out)が亀場と通信を開始すると、動作しているプログラムの数だけ、 亀場に亀が登場することになる。
コンパイルしたプログラムを複数同時に動かすには、コマンドライン上で
a.out & <ENTER>
すればよい。コマンドの最後に&をつけてから実行すると、そのコマンドは「背後(background)」で処理が継続される。亀場サーバーのメニュー(右クリック)で"Kill All Turtles"を選ぶと、 これらのプログラム(a.out)も強制終了する(させられる)ようになっている。 同時に亀場に接続できるプログラムは最大10まで。
2. 群れによるゴミ集め
「コイン」の使い方
亀は亀場の主からコイン(金貨)というアイテムを借りることができる。 そして、そのコインは(他のアイテムをゲットするためではなくて)亀場の上に印を残すために使う。 以下のプログラミングではこのコインの操作が必要となるので、少しスペースを割いて、コインの扱い方をまず まとめておく。
最初の状態で、亀はコインを1枚も持っていない。そこで、
BORROWCOIN()でコイン10枚を借りる
BORROWCOIN() ;
を呼び出すと、「亀場の主」からコインを10枚受け取れる(借りられる)ようになっていて、 それを亀場に落としたり、自分の近くに落とされたコインがあるかどうか調べたり、見つけたコインを拾ったりできる。 ただし、亀場の中のコインの総数は300枚までなので、あまり何回も借りることはできない。
現在位置にコインを一つ落とすには
DROPCOIN()でコイン1枚を現在位置に落とす
DROPCOIN() ;
関数を呼び出す。すると、亀場に金貨(のようなもの)が表示される。コインは(ドーナッツとは違って)亀の歩行には全く影響しない。 コインを1枚も持っていなければ、この関数を呼んでも、コインを落とすことはできない。
現在位置の周囲(亀の体長の2倍の半径の円の内)にコインが何枚あるかは
Q_COIN(&枚数)で現在位置の周りのコインを探す
Q_COIN(&nc) ;
で調べることができる(この例では、int型の変数ncに枚数がセットされる)。そこからコインを1枚拾うには
PICKCOIN()で現在位置のコインを1枚拾う
PICKCOIN() ;
関数を呼び出す。
ここで注意しなければならないのは
if (何々の条件) { DROPCOIN() ; } Q_COIN(&nc) ; if (nc>0) { PICKCOIN() ; }
のようなコードを書くと、一旦置いたコインが、直ちに拾われてしまうので、結局、亀場にコインは残らない(残せない)ということだ。 つまり、コインを亀場に置いたら、次に拾うのは亀がある程度移動してから、という風に、タイミングをずらさなければならない。
また、自分の持っているコインの枚数を確認するには
Q_MYCOIN(&枚数)で手持ちのコインの枚数を知る
Q_MYCOIN(&nmycoin) ;
を使えばよい。
最後に、亀場のランダムな位置に金貨を1枚落とすための関数(あるいは、まじない)
COIN()で亀場にコインを1枚ばらまく
COIN() ;
がある。この関数でコインを出現させて、それをPICKCOIN()で拾うことができれば、自分のコインが1枚増えることになる。
アリなどの社会性昆虫の行動にヒントを得て、その数理的なモデル化や、さらには、
群れで行動する自律型のロボットの開発が行われている。
たとえば、社会性昆虫の集団行動とその応用に関する研究で世界的に有名なDeneubourg博士らの研究が、論文のPDFを含め
こちらのサイト
から参照できる。
ロボットへの応用については、
こちらの論文などが参考になる。
(情報提供:東北学院大・菅原先生)
みんなでゴミ(コイン)集め
ここではコインをゴミに見立て、ロボットにゴミ集めをさせてみよう。まず、亀場のあちこちにゴミが散乱している状況を想定する。
町内のゴミ集積所のように決められた場所があるなら、「拾ったらその場所に運んで、そこに置く」を繰り返せば、 ゴミを一箇所に集めることができるだろう。けれども、亀場のように、そうした特別な目印の無い場合はどうしたらよいだろうか。
そんな場合の、一つの方策として、以下の行動アルゴリズムが考えられる:
1.現在地の周りのゴミを確認する 2.ゴミが落ちていたら、手持ちのゴミの数によって行動を選択する 2−1.もしゴミを持っていたら、そこに落として、その場から立ち去る 2−2.ゴミを持っていなければ、そのゴミを拾って、その場から立ち去る 3.1〜2を繰り返す |
随分と効率が悪そうだけれども、沢山が集まって共同作業すれば、話しが違ってくるかもしれない。
練習:集団でゴミ(コイン)集め
COIN()関数を使って、亀場にコインを30枚ばらまいた後、上に示した行動アルゴリズムを参考に、亀場のコインをできるだけ1箇所(数カ所でも構わない)に 集めるロボットをデザインしなさい。
さらに、作成したロボットを複数個同時に動かしたとき、ゴミ集めの効率が上がるように工夫しなさい。
ヒント
プログラムの骨格は、以下のようになるだろう:
#include "turtle.h" int main() { int cnt,nc,res ; CON("localhost") ; BMODE() ; 必ず対戦モードにする for (cnt=0; cnt<30; cnt++) COIN() ; コインを30枚亀場にばらまく while (1) { Q_COIN(&nc) ; 周囲のコインの枚数を調べて if (nc>0) { コインが見つかった場合の処理 } Q_FINDER(&res) ; 前方を確認して if (res==0) { 障害物が無い場合の処理 } else { 障害物を回避するための処理 } } } |
複数のカメを同時に動かす場合、後から参加するカメは、亀場にコインを散らす必要がないので、COIN()関数のところを削ったバージョンも用意しておくか、あるいは、
Q_NT(&n) ; /* 亀場の物体数を問い合わせる */ if (n==1) for (cnt=0; cnt<30; cnt++) COIN() ; /* 亀が自分だけなら、コインをばらまく */
のようにコードを変更しておくとよい。