← UIAPduino WebHID Labゲームを開く →
SnakeSolver.ino / VISUAL GUIDE

スネークの「記憶」を
盤面で見てみよう

UIAPduinoの中に実際の盤面はありません。2つの配列に書かれた truefalse が、岩と伸び続けるスネークの世界を表しています。

RESETROCK × nSTARTDIRTICK

同じ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()受信コマンドに応じて記憶を更新し、次の判断へ進む。