← UIAPduino WebHID Lab

Maze Solver

HID ProMicro CH32V003  /  Tools → USB: Keyboard+Mouse+WebHID  /  Sketch: MazeSolver.ino

⚠️ このページは Chrome または Edge でのみ動作します。
WebHID API は Firefox・Safari では利用できません。

接続

未接続

※ Chrome / Edge のみ対応。接続後に「開始」ボタンが有効になります。

迷路

経過時間
0.0s
現在位置 (x, y)
移動回数
0
迷路 No.
1
状態
待機中

通信ログ

WebHID プロトコル

Feature Report 32バイト(Web → UIAPduino)/ Input Report 8バイト(UIAPduino → Web)

方向byte[0]byte[1]byte[2]説明
Web→Arduino0x01 CMD_STARTsx (x座標)sy (y座標)ゲーム開始・スタート座標通知
Web→Arduino0x02 CMD_RESULTresult_code移動/センス要求への応答
Web→Arduino0x03 CMD_MAZE_SIZEwidth=15height=15迷路サイズ通知
Web→Arduino0x04 CMD_GOALgx (x座標)gy (y座標)ゴール座標通知
Web→Arduino0x05 CMD_RESETリセット
Arduino→Web0x10 CMD_MOVEx (列)y (行)(x,y)へ移動要求・位置更新
Arduino→Web0x11 CMD_SENSEx (列)y (行)(x,y)を確認(移動しない)
Arduino→Web0x12 CMD_SOLVED解決完了通知
Arduino→Web0x14 CMD_READY起動完了・接続確認

result_code: 0x00 OPEN   0x01 WALL   0x02 GOAL   0x03 START   0x04 OUT_OF_BOUNDS

ヒント

ヒント 1 — 座標系と迷路の構造
迷路は 15×15 のグリッドです。
X = 列(右方向)、Y = 行(下方向)、原点 (0,0) は左上。
ゴールは X=15(右端)または Y=15(下端)の出口セルです。

Web ページから通知されたスタート・ゴール座標は、loop() で受け取って グローバル変数に保存されます。solveMaze() を呼ぶ時点では 以下の変数が利用可能です:

startX, startY — スタート座標
goalX, goalY — ゴール座標
mazeW, mazeH — 迷路サイズ(どちらも 15)
ヒント 2 — maze.sense() と maze.moveTo() の使い方
maze.sense(x, y) は移動せずにそのセルの状態を確認します。
maze.moveTo(x, y) はそのセルへの移動を試みます。
どちらも 結果コード を返します:

MazeHID::RESULT_OPEN (0) — 通路(移動可)
MazeHID::RESULT_WALL (1) — 壁(移動不可)
MazeHID::RESULT_GOAL (2) — ゴール!
MazeHID::RESULT_START (3) — スタート地点(移動可)
MazeHID::RESULT_OUT (4) — 範囲外

隣のセルは (x±1, y) または (x, y±1) です。斜め移動はありません。

isPassable(r)RESULT_OPEN / RESULT_START / RESULT_GOAL のいずれかなら true を返す便利なヘルパーです。
isValidTarget(nx, ny) は座標が迷路内またはゴール出口なら true を返します。
ヒント 3 — 右手法(Wall Follower)
壁に沿って右手を置き続けながら進む古典的なアルゴリズムです。
スタックやキューが不要で、単純なループで実装できます。

進行方向を dir(0=右, 1=下, 2=左, 3=上)として管理します:
  1. 右手方向 (dir+1)&3 を試す
  2. ダメなら正面 dir を試す
  3. ダメなら左 (dir+3)&3 を試す
  4. ダメなら後ろ (dir+2)&3 へ戻る
const int8_t dx[] = { 1,  0, -1,  0 };
const int8_t dy[] = { 0,  1,  0, -1 };
uint8_t x = startX, y = startY;
uint8_t dir = 0; // 最初は右向き

while (true) {
    const uint8_t order[] = {
        (dir+1)&3, dir, (dir+3)&3, (dir+2)&3
    };
    for (uint8_t i = 0; i < 4; i++) {
        uint8_t d  = order[i];
        int8_t  nx = (int8_t)x + dx[d];
        int8_t  ny = (int8_t)y + dy[d];
        if (!isValidTarget(nx, ny)) continue;
        uint8_t r = maze.moveTo((uint8_t)nx, (uint8_t)ny);
        if (r == MazeHID::RESULT_GOAL) return;
        if (r == MazeHID::RESULT_OPEN || r == MazeHID::RESULT_START) {
            x = (uint8_t)nx; y = (uint8_t)ny;
            dir = d;
            break;
        }
    }
}
ヒント 4 — DFS(深さ優先探索)
スタックを使って行き止まりを見つけたら戻る(バックトラック)手法です。
未訪問セルを優先して深く潜り、全セルを探索できます。

sense() で壁か確認してから moveTo() で進み、 行き止まりなら1つ前の座標へ moveTo() で戻ります:
bool    visited[16][16] = {};
uint8_t stackX[256], stackY[256];
uint8_t nextDir[256];
const int8_t dx[] = { 1, 0, -1,  0 };
const int8_t dy[] = { 0, 1,  0, -1 };

uint16_t sp = 0;
stackX[sp] = startX; stackY[sp] = startY;
nextDir[sp] = 0;
visited[startY][startX] = true;

while (true) {
    uint8_t x = stackX[sp], y = stackY[sp];
    bool advanced = false;
    while (nextDir[sp] < 4) {
        uint8_t d = nextDir[sp]++;
        int16_t nx = (int16_t)x + dx[d];
        int16_t ny = (int16_t)y + dy[d];
        if (!isValidTarget(nx, ny)) continue;
        if (visited[ny][nx]) continue;
        if (!isPassable(maze.sense((uint8_t)nx, (uint8_t)ny))) continue;
        uint8_t mr = maze.moveTo((uint8_t)nx, (uint8_t)ny);
        if (mr == MazeHID::RESULT_GOAL) return;
        if (mr == MazeHID::RESULT_OPEN || mr == MazeHID::RESULT_START) {
            sp++;
            stackX[sp] = (uint8_t)nx; stackY[sp] = (uint8_t)ny;
            nextDir[sp] = 0;
            visited[ny][nx] = true;
            advanced = true; break;
        }
    }
    if (advanced) continue;
    // 行き止まり → バックトラック
    if (sp == 0) return;
    sp--;
    maze.moveTo(stackX[sp], stackY[sp]);
}

スケッチソース — MazeSolver

GitHub ↗

ボード: HID ProMicro CH32V003  /  Tools → USB: Keyboard+Mouse+WebHID