制御プログラムの構造を理解しよう


三つの領域

以下はインストールファイルに付属している制御プログラムです。

コード 5.1
/***********************************************
*        このファイルを編集してください        *
***********************************************/


// 以下に示したヘッダファイル以外をインクルードする場合は事前に相談のこと
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>


// これがこの DLL の名前です。日本語で 40 文字程度で指定してください
const char DLLNAME[NAMESIZE] = "サンプルロボット 1 号";

// グローバル変数や自作関数はこの辺りに書くといいでしょう。


// ロボットはリセット時にこの関数が呼ばれます。必要に応じてグローバル変数を初期化してください

void Init(int n){
  // コンソール画面を表示するにチェックを入れると printf() 等が使えます
  printf("作戦 %d でリセットされました\n", n);
}

// ロボットはループ毎に呼ばれます。周囲の状況を認識して次の行動を決めてください
void Loop(void){
  if (gSurTiles[0] & OBSTACLE_TILE)  // 前方が障害物なら
    NaviCmd(0,1);   // 右旋回
  else if (gSurTiles[0] & WAKEBLOCK)  // 前方が相手が通った直後でも
    NaviCmd(0,1); // 右旋回
  else if ((gSurTiles[1] & NEXTFLAG_TILE) || (gSurTiles[8] & NEXTFLAG_TILE))  // 目標が右斜め前なら
    NaviCmd(0,1);  // 右旋回
  else if ((gSurTiles[5] & NEXTFLAG_TILE) || (gSurTiles[16] & NEXTFLAG_TILE))  // 目標が左斜め前なら
    NaviCmd(0,2);  // 左旋回
  else if (isTgtAhead()){  // 目標が前方なら
    NaviCmd(1,0);  // 前進
  } else {  // 後方なら
    NaviCmd(0,1);  // 右旋回
  }
}

C 言語では /*〜*/ で括られた間、及び // から行末まではコメントです。 コメントとは人間がコードを読むときに参考になればと書かれたもので プログラムとしては関係ありません。 上記コードでは淡い緑で表示しました。

汚い赤で表示した部分はインクルード文です。 コメントに書いているようにここは弄るなと書いてあるので無視しましょう。 するとプログラムは次のようになります。

コード 5.2
const char DLLNAME[NAMESIZE] = "サンプルロボット 1 号";

void Init(int n){
  printf("作戦 %d でリセットされました\n", n);
}

void Loop(void){
  if (gSurTiles[0] & OBSTACLE_TILE)
    NaviCmd(0,1);
  else if (gSurTiles[0] & WAKEBLOCK)
    NaviCmd(0,1);
  else if ((gSurTiles[1] & NEXTFLAG_TILE) || (gSurTiles[8] & NEXTFLAG_TILE))
    NaviCmd(0,1);
  else if ((gSurTiles[5] & NEXTFLAG_TILE) || (gSurTiles[16] & NEXTFLAG_TILE))
    NaviCmd(0,2);
  else if (isTgtAhead()){
    NaviCmd(1,0);
  } else {
    NaviCmd(0,1);
  }
}

内容はまだ理解しなくていいです。 大事なのは構造です。 コード 5.3 のような三つの領域で構成されると理解してください

コード 5.3
const char DLLNAME[NAMESIZE] = "サンプルロボット 1 号";


void Init(int n){
  printf("作戦 %d でリセットされました\n", n);
}

void Loop(void){
  if (gSurTiles[0] & OBSTACLE_TILE)
    NaviCmd(0,1);
  else if (gSurTiles[0] & WAKEBLOCK)
    NaviCmd(0,1);
  else if ((gSurTiles[1] & NEXTFLAG_TILE) || (gSurTiles[8] & NEXTFLAG_TILE))
    NaviCmd(0,1);
  else if ((gSurTiles[5] & NEXTFLAG_TILE) || (gSurTiles[16] & NEXTFLAG_TILE))
    NaviCmd(0,2);
  else if (isTgtAhead()){
    NaviCmd(1,0);
  } else {
    NaviCmd(0,1);
  }
}

このうち領域と 領域は波括弧で囲まれています ({〜})。 波括弧の前に書いてある Init とか Loop とかいう文字を使って前者を Init() の中身、 後者を Loop() の中身と呼ぶことにします。 単語の後に () が付いているのはそういう風習だと思っててください。 波括弧に挟まれて無い赤い領域はトップレベルと 呼ぶことにします。 制御プログラムはトップレベルと Init() の中身、そして Loop() の中身の三つの領域で構成されます。

制御プログラムの動き

とりあえず今はトップレベル () は置いておきましょう。 Init() の中身 () と Loop() の中身 () を考えましょう。 Init() の中身はシミュレータの RESET ボタンが押されると 一回だけ実行されます。 一方 Loop() の中身は STEP ボタンが押されると一回だけ実行されます。 START ボタンが押されるとここが何度も呼び出されるのです。

この制御プログラムはシミュレータ上の仮想ロボット用のプログラムです。 その仮想ロボットが実際にあるとしましょう。 そのロボットは電源を入れると内部のコンピュータが起動して制御プログラムが 走り出します。 それは最初に初期設定を行い、後は周囲の状況から次にするべき動きを決定し 行動に移すということを繰り返す動きとなるでしょう。 Init() と Loop() の中身はそれぞれそれに対応しているのです。

さてもしかして学校で C 言語を習った人にとって、 このしくみは逆にピンと来ないかもしれません。 学校では C 言語は main() 関数から始まると習ったのに このサンプルには main() 関数が無いからです。 ここからしばらくそういった疑問を持った方向けの説明を行いますが、 不必要ならこの節は以降読み飛ばしてもかまいません。

仮想ロボットのプログラムは次のようなソースも存在し、 今回編集する RobotControl.cpp と共にコンパイル・リンクされているんだと 考えてみるのはどうでしょうか。

コード 5.4
int main (void) {
  Init(0);

  for (;;){ // 無限ループ
    ScanAround()>; // 状況把握
    Loop();
    ForwardAndTurn()>; // 指令されたように動く
    Sleep(100); // 一定時間待機
  }
  return 0;
}

プログラムは起動直後一回だけ Init() 関数を呼び出してます (ここでは引数を 0 に固定している)。 そしてその後、無限ループに入ってます。 無限ループは while(-1) 等と習うのかもしれませんが、こういう書き方もできます。 センシングしたり動くためのモータ制御、タイミング調整など あることはあるでしょうが、毎回 Loop() 関数を呼び出しているしくみになります。

プログラムの動作確認

今しがた制御プログラムは RESET ボタンを押すと Init() の中身が動くと話しました。 コード 5.2 から Init() 部分を取り出すと

コード 5.5
void Init(int n){
  printf("作戦 %d でリセットされました\n", n);
}

となってます。 C 言語を習ってなければ具体的な動きは分からないかもしれませんが、 大抵のプログラミング言語では print という単語は 指定した文字や数値を出力する命令に使うということくらいは知っていると 思いますので、 どこかに「作戦」とか「リセットされました」とかいう文字が出てくるはずです。 それはどこでしょう?

実は本シミュレータでは見栄えのため普段は隠しています。

画面右下にある「コンソール画面を表示する」ボタンを押すと、 黒いコンソール画面が現れます。

これを表した状態で RESET ボタンを押すと「作戦 0 でリセットされました」 とかいった表示が出るはずです。

少し話が飛びますが、この作戦 0 の 0 は各ロボットの作戦番号に対応しています。

番号を変えて RESET を押すと表示が変わります。

話を戻しましょう。 Loop() の中は STEP や START ボタンが押された時に処理されるものです。 具体的な方法はさておき、周囲の状況を認識し次の行動を決定している内容のはずです。 とりあえずここでは、本当に Loop() の中は STEP や START が押された時に実行されるものかどうか確認しましょう。 Init() の中身から推測されるように printf() という関数はコンソール画面に文字を出力するものです。 次のように Loop() の中を printf() だけにしてみましょう (念のため、書き換える前に今の RobotControl.cpp はどこかにコピーして取っておくほうがいいでしょう)。

コード 5.6
void Loop(void){
  printf("1 ターン進みました\n");
}

STEP ボタンを押すたびにコンソール画面に「1 ターン進みました」という表示が出るようになります。

コード 5.6 は画面に「1 ターン進みました」と表示するだけで、 ロボットを動かす命令は入っていません。そのため STEP ボタンを押してもロボットが動かないことも確認しておいてください。

最後に先ほど保留したトップレベル (コード 5.3 の部分) の話をします。 ここは処理内容自体は書きません。 Init() や Loop() の中身といった処理内容を書く上での共通認識事項を書きます。 コード 5.2 では「サンプルロボット 1 号」とロボットの名前が指定されています。 これもシステムにとって共通認識事項の一つです。 ためしにこれを 1 号から 2 号にしてみましょう。 シミュレータを起動すると、それが反映され「サンプルロボット 2 号」という 名前になっていることが確認できるはずです。

このようにロボットの名前は自由に変えることができますが、 あまり長い名前は避け、日本語で 28 文字以内にしてください。


[戻る]