これまでのサンプル・プログラムでは,task main(メインタスク)の中にすべてのコードを書いていました。実はこれはお奨めできる方法ではありません。プログラムが長くなると理解しにくいだけでなく,プログラミングそのものの効率が悪くなってしまいます。

 効率の良いプログラムが書けるように,NQCは複数タスクの定義,サブルーチン化,インライン関数,マクロ定義などの機能をサポートしています。ちょっと難しくなってきましたね。でも,この章を理解すれば,立派なNQCの使い手になれます。


7-1.タスク

 NQCでは10個までのタスクを使うことができます。必ず1つはmainタスクでなければいけません。プログラムの実行は,mainタスクから始まるからです。他のタスクはmainタスクから起動することができるだけなくmainタスク以外のタスクから他のタスクを起動することもできます。同時に複数のタスクを動かすことをマルチタスクと言います。最近のパソコンと同様にMindstormsの小さな頭脳RCXはマルチタスクを実行できるのです。

 タスクはtaskコマンドでtask タスク名()のように定義します。

 他のタスクを起動するには,start タスク名,停止するにはstop タスク名と記述します。もちろん,各タスクには重複しない名前をつけなくてはいけません。

 無限軌道車の先端にタッチセンサーを取り付けました。

 task.nqc

 task main()
  {
   SetSensor(SENSOR_1,SENSOR_TOUCH);
   start check_sensor;
   start move_robot;
  }
  task move_robot()
  {
   while (true)
   {
    SetPower(OUT_A+OUT_C,OUT_HALF);
    OnFwd(OUT_A+OUT_C);
   }
  }
  task check_sensor()
  {
   while (true)
   {
    if (SENSOR_1 == 1)
    {
     stop move_robot;
     OnRev(OUT_A+OUT_C);
     Wait(50);
     OnFwd(OUT_A);
     Wait(150);
     start move_robot;
    }
   }
  }  

 まず,mainタスクでは入力ポート1に接続したセンサーをタッチセンサーであると指定しています。

次にstart命令で check_sensorタスク,move_robotタスクを起動しています。move_robotタスクはロボットを真っ直ぐ走らせます。

  check_sensorタスクでは,タッチセンサーが何かにぶつかった時,move_robotタスクをstop命令で止め,OnRevでバックした後,モーターAの回転方向を変え曲がります。その後,move_robotタスクを再起動します。

 ロボットをRunさせ,タッチセンサーを指で押してみましょう。尻込みするようにバックして,方向を変えます。こういうロボットの動きは見ていて面白いものです。ペットロボットが流行するのもうなずけます。


7-2.サブルーチン

 プログラミングをしていると,同じコードを何度も書いていると気付くことがあります。そんな時にサブルーチンを使います。よく使う複数の命令をサブルーチンとして定義しておけば同じコードを何度も記述せずに,サブルーチンを呼び出して実行することができます。
  ちょっと極端な例かもしれませんが,下記のプログラムを見てください。「直進して曲がる」を4回繰り返して元の位置に戻ってくるプログラムです。

 bfsub.nqc

 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);

  OnFwd(OUT_A+OUT_C);
  Wait(100);
  OnRev(OUT_A);
  Wait(150);

  OnFwd(OUT_A+OUT_C);
  Wait(100);
  OnRev(OUT_A);
  Wait(150);

  OnFwd(OUT_A+OUT_C);
  Wait(100);
  OnRev(OUT_A);
  Wait(150);

  OnFwd(OUT_A+OUT_C);
  Wait(100);
  OnRev(OUT_A);
  Wait(150);

  Off(OUT_A+OUT_C);
 }

 OnFwdからWait(150)まで,同じことを4回も書いています。この部分をサブルーチン化します。

 afsub.nqc

 sub run_robot()
 {
  OnFwd(OUT_A+OUT_C);
  Wait(100);
  OnRev(OUT_A);
  Wait(150);
 }
 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);
  run_robot();
  run_robot();
  run_robot();
  run_robot();
  Off(OUT_A+OUT_C);
 }

  サブルーチン化するとプログラムのサイズがコンパクトになります。サブルーチンの呼び出しは他の命令の呼び出しに似ていますがWait(100)やOnRev(OUT_A)のように( )の中に,引数を指定することはできません。
  サブルーチンには他にもいくつかの制限事項があります。まず,サブルーチンから他のサブルーチンを呼び出すことはできません。自分自身を呼び出すこともできません。また,サブルーチンは1つのプログラムに最大8つまでしか定義することができません。

 もちろん,上記のプログラムは下記のようにrepeatを使ったほうがよりスマートです。

 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);
  repeat(4)
  {
   run_robot();
  }
  Off(OUT_A+OUT_C);
 }


7-3.インライン関数

 インライン関数はサブルーチンによく似ています。違いは,インライン関数は呼び出すために呼び出した箇所にそのコードが展開されるということです。ちょっと分かりにくいですね。NQCコンパイラになったつもりで考えてみましょう。NQCコンパイラはサブルーチンに出会うとをそれを一回だけ,RCXが理解できるコード(マシン語=RCXバイトコード)に変換(コンパイル)します。しかし,インライン関数の場合は,インライン関数を呼び出している場所に,インライン関数の中に記述したコードをコピーします。複数回呼び出されていれば,その回数分コピーした上で,RCXバイトコードに変換します。プログラマは一回だけインライン関数を定義すれば,必要な時にそれを呼び出すだけで済むのですが,NQCコンパイラにとっては同じコードが何度も書かれているのと同じなのです。サブルーチンを使うとRCXバイトコード自体のサイズが小さくなりますが,インライン関数ではそうなりません。
 でも,インライン関数には便利な点があります。まず,サブルーチンのように8つまでという制限がありません。それから,引数を受け取ることができます。

 inlinesa.nqc

 void run_robot(int time_st,int time_tn)
 {
  OnFwd(OUT_A+OUT_C);
  Wait(time_st);
  OnRev(OUT_A);
  Wait(time_tn);
 }
 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);
  run_robot(120,120);
  run_robot(100,150);
  run_robot(120,120);
  run_robot(100,150);
  Off(OUT_A+OUT_C);
 }

 インライン関数はキーワードvoidを使って宣言します。真っ直ぐに進む時間(time_st)と曲がる時間(time_tn)を引数としてインライン関数run_robotに渡しています。


7-4.マクロ定義

 マクロ定義もコードをコンパクトにする簡単で,便利な方法です。マクロは定数を定義するのと同様に,#define命令で複数の命令を1つの命令として扱えるように定義します。

 macro.nqc

 #define run_robot OnFwd(OUT_A+OUT_C);Wait(100);OnRev(OUT_A);Wait(150);

 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);
  repeat(4)
  {
   run_robot;
  }
  Off(OUT_A+OUT_C);
 }

 OnFwdからWaitまでの4つの命令をrun_robotという名前でマクロ定義しました。複雑な処理はサブルーチンやインライン関数を使って定義し,単純な処理にはマクロ定義を使うと良いでしょう。

 マクロはインライン関数と同様に引数を受け取ることができます。

 #define run_robot(st,tn) OnFwd(OUT_A+OUT_C);Wait(st);OnRev(OUT_A);Wait(tn);
 task main()
 {
  SetPower(OUT_A+OUT_C,OUT_HALF);
  run_robot(100,150);
  run_robot(120,100);
  Off(OUT_A+OUT_C);
 }

  引数を受け取るマクロを呼び出すとはマクロ名の後ろに( )をつけ,中に渡す値を記述し,引数を受け取らないマクロは( )を付けません。

 プログラミングでは,漢字の練習や英単語の勉強のようにたくさん書くことが誉められることはありません。特にRCXのメモリ容量はパソコンと比べると比較にならないほど小さいものです(32KBのRAMしかない) 。コンパクトなコードを書く必要があります。また冗長なコードは,他の人が読むとわかりにくいものです。サブルーチンやインライン関数,マクロ定義を使って簡潔にわかりやすいコードを書くように心がけましょう。


(KBはキロバイトの意味です)