同じ16×16の場所を、2枚の透明シートで管理する
図では1枚の盤面に重ねて描きますが、プログラムの中では用途の違う2つの配列です。添字はどちらも [Y][X] の順です。
bool board[16][16]スネークが通ったマス。頭も体も true。
bool rocks[16][16]Webから知らされた岩のマスだけ true。
大事:
board[x][y] ではなく board[y][x]。座標 (X=4, Y=7) は board[7][4] です。00
COORDINATES
まず、XとYの向きをつかむ
左上が (0, 0)。Xは右へ、Yは下へ0〜15まで増えます。
X → / Y ↓
1マス = 配列の1要素
何もないマスは、両方の配列で false です。暗いマスは「空き」です。
board[7][4] // Y=7, X=4 rocks[2][11] // Y=2, X=11
01
CMD_RESET / 0x05
最初に、すべてを false に戻す
Webがレベル情報を送り始める前に CMD_RESET を送信します。UIAPduinoは前のレベルの記憶を消します。
256マスすべて空
clearAll()
二重ループで256個ずつ、合計512個の値を初期化します。
case CMD_RESET: clearAll(); board[y][x] = false; rocks[y][x] = false;
前の盤面→全マス false
02
CMD_ROCK / 0x03
岩の座標が、1個ずつ届く
岩の位置はWeb側で決まり、Feature Reportで順番に届きます。受け取るたび、その1マスだけを true にします。
例: 4個のCMD_ROCKを受信した後
false / 空きrocks[y][x] = true
受信例
CMD_ROCK[0x03, 2, 3] → rocks[3][2]
CMD_ROCK[0x03, 11, 2] → rocks[2][11]
CMD_ROCK[0x03, 7, 11] → rocks[11][7]
CMD_ROCK[0x03, 13, 8] → rocks[8][13]
case CMD_ROCK: rocks[buf[2]][buf[1]] = true;
03
CMD_START / 0x01
頭のスタート位置を board に書く
例として (X=4, Y=7) が届いたとします。岩は残したまま、スネーク用の board を初期化し、頭のマスを true にします。
headX = 4, headY = 7
岩頭 / board[7][4] = true
initBoard(4, 7)
headX = sx; // 4 headY = sy; // 7 board[sy][sx] = true; solveTick(); // 最初の方向を考える
board[7][4]→true
04
computeNextDir()
4方向を調べ、一番広い場所へ進む
壁・岩・自分の体に当たる候補を除外し、残った各マスからBFSを始めます。到達できる空きマスが最も多い方向を採用します。
水色の枠 = 次の候補
board = truerocks = true
countReachable() の比較イメージ
→ 右171
↓ 下109
← 左NG
↑ 上211
この例なら上が最大なので dx=0, dy=-1。数値は仕組みを示すための例です。
if (!found || cnt > bestCount) {
bestCount = cnt;
outDx = DDX[d];
outDy = DDY[d];
}05
CMD_DIR / 0x10
UIAPduinoは「方向」だけをWebへ送る
選んだ上方向を snake.sendDir(0, -1) で通知します。この時点では、UIAPduino側の頭の座標も board もまだ変えません。
黄色の+は予定地。まだtrueではない
UIAPduino → Web
void solveTick() {
int8_t dx, dy;
computeNextDir(dx, dy);
snake.sendDir(dx, dy);
}
// Input Report
[0x10, 0, 255] // 255 = -1ここがポイント: UIAPduinoが次の頭の座標を直接書いて送るのではありません。Webが方向を受けて実際に1マス動かし、その結果を次の
CMD_TICK で返します。06
CMD_TICK / 0x02
移動後の座標が戻り、そこを true にする
Webが (4, 6) へ動かした後、[CMD_TICK, 4, 6] を送ります。UIAPduinoは初めて新しい頭位置を記録し、また次の方向を考えます。
board[6][4] が新しく true
前の頭は体として残る新しい頭
記録して、すぐ次を計算
case CMD_TICK: headX = buf[1]; // 4 headY = buf[2]; // 6 board[headY][headX] = true; solveTick();
board[6][4]→true
computeNextDir()次の方向を選ぶ
CMD_DIR方向をWebへ送る
Webが移動衝突も判定する
CMD_TICK新座標をtrueにする
SnakeSolver.ino 全体を一言ずつ
clearAll()スネークと岩の記憶を全部消す。initBoard()頭の開始座標を記録する。countReachable()候補から行ける空きマスをBFSで数える。computeNextDir()4方向から最も広い空間につながる方向を選ぶ。solveTick()方向を計算してWebへ返す。loop()受信コマンドに応じて記憶を更新し、次の判断へ進む。