話をコード 9.3 の「障害物回避モード時の動作 」をコード 9.6 にした時のLoop() の中身だけ再掲してみます。
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 で動かしてみた時の図です。
これに対して左右の動きをまったく逆にすることもできます。 コードは次のようになるでしょう。
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 近傍しか知ることができないのでどっちが有利か分かりません。 ヤマカンに頼るのです。
決定はされていませんが、当プログラミングコンテストは
という運営でやっていきたいと考えています。 何勝したら勝にするかとかリーグにするかトーナメントにするか等は未定です。 とにかく地図を知るのはプログラムを作った後になります。
参加者 (競技者) は地図を見て、この地形ならどの戦略 (上記の例なら右回りか左回りか) を 取るのが有利か考え指定することができます。 それを指定するのがシミュレータの作戦と書かれた場所です。
コード 5.4の話を思い出してください。 RESET ボタンを押された時に呼び出される Init() には
void Init(int n){ printf("作戦 %d でリセットされました\n", n); } |
と書いておけば、選んだ作戦番号がコンソール画面に表示されていました。
変数 n に作戦番号が入っていたのです。 この変数 n は Init() の中しか通じない変数です。 そのままだと Loop() には伝わりません。 6 章で、独自に何回 Loop() を呼び出されたか数えるためには トップレベルで変数宣言しないといけないと話しましたが、それと同じで、 作戦番号の入った n の値をトップレベルで宣言した別の変数に入れるようにします。 後はその値に応じて動きを変えるだけです。
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() 部分はダラダラとした記述になってしまい、 なんだか無駄と感じるかもしれません。 プログラマとして非常に正しい認識です。 以下にスリム化したコードの一例を示します。
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 番にすると今どのような判断をしたか表示するようになっています。 コンテストの意義を考えるとサンプルロボットのソースコード自体は公開しませんが、 各ロボットがどういう判断でその一手を選んだかという根拠が分かりますので、 自身のロボットを作る際の参考にできるのではと考えます。