人の介在


右回りと左回り

話をコード 9.3 の「障害物回避モード時の動作 」をコード 9.6 にした時のLoop() の中身だけ再掲してみます。

コード 11.1
void Loop(void) {
  if (AvoidMode==1) {
    if (!(gSurTiles[5] & OBSTACLE_TILE))
      NaviCmd(0,2);
    else if (gSurTiles[0] & OBSTACLE_TILE)
      NaviCmd(0,1);
    else {
      NaviCmd(1,0);
      if (isTgtAhead())
        AvoidMode=0;
    }
  } else {
    if (gSurTiles[0] & OBSTACLE_TILE){
      NaviCmd(0,1);
      AvoidMode=1;
    } else if ((gSurTiles[1] & NEXTFLAG_TILE) || (gSurTiles[8] & NEXTFLAG_TILE))
      NaviCmd(0,1);
    else if (isTgtAhead())
      NaviCmd(1,0);
    else
      NaviCmd(0,1);
  }
}

このコードは障害物に当たったらとにかく右に旋回して左回りをします。 次の軌跡はマップ a25x70_level07.txt で動かしてみた時の図です。

これに対して左右の動きをまったく逆にすることもできます。 コードは次のようになるでしょう。

コード 11.2
void Loop(void) {
  if (AvoidMode==1) {
    if (!(gSurTiles[1] & OBSTACLE_TILE))
      NaviCmd(0,1);
    else if (gSurTiles[0] & OBSTACLE_TILE)
      NaviCmd(0,2);
    else {
      NaviCmd(1,0);
      if (isTgtAhead())
        AvoidMode=0;
    }
  } else {
    if (gSurTiles[0] & OBSTACLE_TILE){
      NaviCmd(0,2);
      AvoidMode=1;
    } else if ((gSurTiles[5] & NEXTFLAG_TILE) || (gSurTiles[16] & NEXTFLAG_TILE))
      NaviCmd(0,2);
    else if (isTgtAhead())
      NaviCmd(1,0);
    else
      NaviCmd(0,2);
  }
}

同じマップで動かして見ると障害物にあたったら左に旋回して右回りをします。

どちらが有利でしょう?


答えは地図依存です。 典型的な例がマップ a25x70_level10.txt です。

コード 11.1 だと大回りして 230 ターンかかります。

一方コード 11.2 だと大回りしないので 209 ターンでゴールします。

人間はマップを俯瞰できますから、地図を見るとこの地形は右回りが有利だなと分かりますが、 ロボットは周囲 18 近傍しか知ることができないのでどっちが有利か分かりません。 ヤマカンに頼るのです。

戦略の付与

決定はされていませんが、当プログラミングコンテストは

  1. 予めプログラムを提出して頂き
  2. 当日、競技マップを発表し
  3. コイントスその他で先行か後攻か (黄色ロボットか緑ロボットか) を決めてもらい
  4. 先にミッションを達成したことが勝者

という運営でやっていきたいと考えています。 何勝したら勝にするかとかリーグにするかトーナメントにするか等は未定です。 とにかく地図を知るのはプログラムを作った後になります。

参加者 (競技者) は地図を見て、この地形ならどの戦略 (上記の例なら右回りか左回りか) を 取るのが有利か考え指定することができます。 それを指定するのがシミュレータの作戦と書かれた場所です。

コード 5.4の話を思い出してください。 RESET ボタンを押された時に呼び出される Init() には

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

と書いておけば、選んだ作戦番号がコンソール画面に表示されていました。

変数 n に作戦番号が入っていたのです。 この変数 n は Init() の中しか通じない変数です。 そのままだと Loop() には伝わりません。 6 章で、独自に何回 Loop() を呼び出されたか数えるためには トップレベルで変数宣言しないといけないと話しましたが、それと同じで、 作戦番号の入った n の値をトップレベルで宣言した別の変数に入れるようにします。 後はその値に応じて動きを変えるだけです。

コード 11.3
const char DLLNAME[NAMESIZE] = "サンプルロボット 3 号";
int stratNum;

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

void Loop(void){
  if (stratNum==0) {
    〜〜〜 コード 11.1 の中身 〜〜〜
  } else {
    〜〜〜 コード 11.2 の中身 〜〜〜
  }
}

作戦番号は 0〜7 の 8 つ設けられます。 コード 11.3 では 2 通りしか戦略を用意していませんが、 後 6 通り戦略を考えて、この地図を後略するにはどの戦略がいいか考えてみてもいいでしょう。 8 通りですから 3 ビットと考えることもできます。 ビット毎に戦略を作るという作り方もあります。

コードのスリム化

コード 11.1 と 11.2 は一部の変数の値が違うだけで処理内容は同じです。 そのためコード 11.3 の Loop() 部分はダラダラとした記述になってしまい、 なんだか無駄と感じるかもしれません。 プログラマとして非常に正しい認識です。 以下にスリム化したコードの一例を示します。

コード 11.2
void Loop(void) {
  int turn, opposite;
  int side1, side2, side3;
  if (stratNum==0) {
    上手い具合に turn, opposite, side1, side2, side3 の値を入れる
  } else {
    上手い具合に turn, opposite, side1, side2. side3 の値を入れる
  }


  if (AvoidMode==1) {
    if (!(gSurTiles[side3] & OBSTACLE_TILE))
      NaviCmd(0,opposite);
    else if (gSurTiles[0] & OBSTACLE_TILE)
      NaviCmd(0,turn);
    else {
      NaviCmd(1,0);
      if (isTgtAhead())
        AvoidMode=0;
    }
  } else {
    if (gSurTiles[0] & OBSTACLE_TILE){
      NaviCmd(0,turn);
      AvoidMode=1;
    } else if ((gSurTiles[side1] & NEXTFLAG_TILE) || (gSurTiles[side2] & NEXTFLAG_TILE))
      NaviCmd(0,turn);
    else if (isTgtAhead())
      NaviCmd(1,0);
    else
      NaviCmd(0,turn);
  }
}

動作状況の表示

プログラムを実行するにあたって、様々な内部状態を外部に表示すると、何が起きているかどうすればいいのか詳細に知ることができます。 当シミュレータの「コンソール画面」はそういう時のために用意しています。 変数の値やこの if 文の中に入ったかどうかなどを printf() 文で表示してみるといいでしょう。

その際、あまりにも沢山の情報を表示すると逆に表示がいっぱいありすぎて何が起きたか分かりづらくなることがあります。 そこで表示する内容を幾つかの重要度に区分し、今はこの重要度までの内容を表示して欲しい (それより低い重要度の情報は表示しなくていい) と依頼することがあります。 プログラムの開発では、これをデバッグレベルとかログレベルと呼んでいます。 作戦番号はそのようなデバッグレベルの指定にも使えます。

添付してあるサンプルロボットは、実は作戦番号 7 番にすると今どのような判断をしたか表示するようになっています。 コンテストの意義を考えるとサンプルロボットのソースコード自体は公開しませんが、 各ロボットがどういう判断でその一手を選んだかという根拠が分かりますので、 自身のロボットを作る際の参考にできるのではと考えます。


[戻る]