⚠️ このページは Chrome または Edge でのみ動作します(WebHID API)。
← UIAPduino WebHID Lab

🪨 Rock Dodge

UIAPduino が落ちてくる岩をよけながら得点アイテムを拾い、最高スコアを競う

▦ RockDodge.ino の動きを図で理解する →

デバイス接続

未接続 PLAYER——

Tools → USB → WebHID Only で書き込んでください。 プレイヤー名はスケッチの #define PLAYER_NAME を書き換えて設定します(半角英数 12 文字まで)。

ゲーム

LEVEL
1
Remain
Score
0
Items
Time
00:00
待機中

水色のセーフティラインに乗っている限り、岩には絶対に当たりません。 しかし乗っているだけではアイテムのノルマに届かず、120 ステップ後にゲームオーバー。 高得点は壁の中やラインの外に湧きます。取りに行くかどうかはアルゴリズム次第 — 死んだらそれは欲張ったせいです。

🏆 ランキング(この PC のブラウザに保存)

ログ

レベル構成(全レベル 120 ステップ固定)

Level速度 (ms/tick)岩の湧き間隔岩パターンアイテムノルマ
11504 tickランダム 1〜2 個6 個
21503 tickランダム 2〜3 個7 個
31403 tickランダム+壁(隙間 4)8 個
41303 tick壁(隙間 4〜3)9 個
51202 tick壁+流れる壁(隙間 3)10 個
6+120 → 80 まで短縮2 tick全パターン混合11 → 12 個

「壁」= 隙間以外の全列に岩が並ぶ行。「流れる壁」= 隙間の位置が 1 枚ごとに左右へずれていく連続壁。 壁の岩は、一部が 2〜4 列つながってアイテムに置き換わることがあります — 隙間(=ラインの通り道)とは別の、岩に囲まれた「もうひとつの開口部」です。 奥へ入るほど高得点になりますが、それだけラインから遠ざかります。 奥のお宝には、数ティック後の壁に★(脱出路)が用意されることがあります。 120 ステップ生存かつノルマ達成でクリア。ノルマ未達はゲームオーバー。

得点ルール

岩をよけ続けるだけでは得点は増えません。 得点はアイテム(落下中に数字が見える)と、面クリアのサービス点 +10 点(固定)だけです。
アイテムの得点は、それを取るためにセーフティラインを何ティック外れる必要があるか(リスク d)に連動します。 帯は互いに重なっているので、「浅いのに高いアタリ」「深いのに安いハズレ」があります。数字をよく見て判断しましょう。
アイテムは1 行に何個でも湧きます。とくに壁の行では、岩の一部が 2〜4 列つながってアイテムに変わります。 その区間は岩に囲まれた通路なので、入り口の 1 個だけ拾って引き返すか、奥の高得点まで踏み込むかの判断になります。

リスク d(離脱ティック数)01234567+
得点範囲1–44–109–1816–3026–4642–6660–8878–99

★(面クリアアイテム): 得点 0。取ると即レベルクリア+ノルマ免除(クリアの +10 点は入る)。 1 レベルに最大 3 個、20 ステップ目以降に間隔をあけて湧きます。 置かれるのはラインから深く外れた位置(d 4〜7)なので、狙って行かないと取れません。
ノルマが危ないときの脱出口ですが、取ると残りの稼ぎを全部捨てることになります。 順調なときは踏まないように避けるのが正解 — うっかり踏んで面を早抜けすると大損です (実測: ★を避ける実装は避けない実装より約 20% 高得点)。
ノルマは安全運転では届きません。 ラインに乗ったまま取れる低リスクなアイテムは 1 レベルに 4 個しか保証されません(=詰み防止の最低保証だけ)。 実測で、安全運転で取れるのは 1 面 7〜9 個。ノルマは Lv1 で 6 個、Lv5 で 10 個まで増えます。 ラインを外れて稼ぐしかない — それがこのゲームの本体です。
到達できない位置に高得点が湧くこともあります(とくに壁の中)。 数字の大きさに釣られず、自分で到達可能性を確かめましょう。

⚡ 脱出路の仕掛け
壁の奥深くにお宝があるとき、数ティック後の壁に、そのお宝の位置から届く★が埋め込まれることがあります。 奥まで踏み込めば戻れず詰み確定 — と思いきや、★を拾って即クリアで脱出できる、というルートです。
ただしいつも用意されているわけではありません(深いお宝の約 4 割)。 ★が来るかどうかを確かめずに突っ込めば、そのまま狩られて終わりです。 「奥のお宝を取ったあと、次の壁の★まで到達できるか」を先読みで検証できるかが腕の分かれ目になります。

WebHID プロトコル

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

方向byte[0]byte[1]byte[2..]説明
Web→Arduino0x01 CMD_STARTstartX[2]=ノルマ [3]=ステップ数(120)ゲーム開始・プレイヤー初期位置・レベル条件
Web→Arduino0x02 CMD_TICKplayerX[2]=maskL [3]=maskH [4]=狙い岩列(255=なし) [5..20]=列ごとのアイテム値1ティック進行。岩マスク+狙い岩(12行目)+アイテム(1行に複数可・0=なし/1〜99=得点/255=★)
Web→Arduino0x05 CMD_RESET岩・状態をリセット
Web→Arduino0x40 CMD_GET_NAMEプレイヤー名を要求(接続直後に送信)
Arduino→Web0x10 CMD_DIRdx (−1/0/+1)次の移動(dx=+1 右, −1 左, 0 停止)
Arduino→Web0x41 CMD_NAMEchunk (0/1)[2..7]=名前6文字プレイヤー名(6文字×2分割)
Arduino→Web0x14 CMD_READY起動完了・接続確認

通信の順序: CMD_RESET → CMD_START → CMD_DIR受信 → (CMD_TICK → CMD_DIR) × n。 岩は「毎ティック 1 マス落下」の決定論ルールなので、湧きマスクだけ受け取ればデバイス側で全岩の位置を追跡できます。

ヒント

ヒント 1 — ゲームの仕組みと RockDodgeHID クラス
Web ページがゲームを進行し、UIAPduino は 次の移動(左/停止/右) を返すだけです。

1ティックの順序(重要!):
① プレイヤーが移動(岩の衝突判定・アイテム取得判定)
② 全部の岩とアイテムが 1 マス落下(衝突判定・取得判定)
③ 最上段に新しい岩とアイテムが湧く
CMD_TICK がデバイスに届く → CMD_DIR を返す

岩とアイテムの状態はデバイス側で自分で管理します。落下は決定論(毎ティック必ず 1 マス)なので、 シフトするだけで Web 側と完全に同期できます。取得済みアイテムは自分の位置から消します:
bool    rocks[16][16]; // 岩があるセル → true
uint8_t items[16][16]; // 0=なし 1〜99=得点 255=★面クリアアイテム
uint8_t playerX;       // 自分の列(行は 15 固定)
uint8_t quota;         // このレベルの最低取得アイテム数

// CMD_TICK 受信時の更新(Web と同じ順序を再現する)
playerX = buf[1];
items[15][playerX] = 0;      // 判定①で拾われた分を消す
shiftRocks(mask);            // 岩を 1 段落とし row0=湧きマスク
shiftItems();                // アイテムも 1 段落とす
items[15][playerX] = 0;      // 判定②で拾われた分を消す
for (uint8_t x = 0; x < 16; x++)  // 新しいアイテムを登録(1行に複数可)
    items[0][x] = buf[5 + x];
if (buf[4] != 255)                // 狙い岩(12行目に出現)
    rocks[12][buf[4]] = true;
RockDodgeHID クラスのメソッド:
dodge.sendDir(dx) — 次の移動を送信(必ず呼ぶ)

盤面の外へ移動しようとした場合はその場に留まります(死にはしません)。
ヒント 2 — まず生き残る(時空間先読み)
落下は決定論なので、t ティック後の盤面は完全に予測できます。 いま rocks[15-t][x] にある岩が、t ティック後に最下段へ届きます。 そこで「位置 × 時間」の 2 次元(時空間)で探索します:
// 「t ティック後に列 x にいて安全か」
bool safeAt(int16_t x, uint8_t t) {
    if (x < 0 || x > 15) return false;
    if (t >= 17) return true; // 既知の岩は全部落ちきった
    if (rocks[16 - t][x]) return false;            // 移動直後の判定
    if (t <= 15 && rocks[15 - t][x]) return false; // 落下後の判定
    return true;
}

// (x, t) から生き残れる未来があるか(メモ化 DFS)
bool canSurvive(uint8_t x, uint8_t t) {
    if (t >= 16) return true;
    if (memo[t][x] >= 0) return memo[t][x];
    memo[t][x] = 0;
    const int8_t DD[] = { 0, -1, 1 };
    for (uint8_t i = 0; i < 3; i++) {
        int16_t nx = (int16_t)x + DD[i];
        if (nx < 0 || nx > 15) continue;
        if (!safeAt(nx, t + 1)) continue;
        if (canSurvive((uint8_t)nx, t + 1)) { memo[t][x] = 1; return true; }
    }
    return false;
}
状態数は 16 列 × 17 ティック = 272 個しかないので、CH32V003 でも一瞬で解けます。 サンプルスケッチはこれを実装済みです。

ただし、これだけではノルマに届きません。 安全運転で拾えるのは 1 面 7〜9 個、ノルマは Lv1 で 6 個から Lv5 で 10 個まで増えるので、 サンプルはレベル 2〜3 でノルマ未達のゲームオーバーになります (岩には当たりません)。ここから先が本番です。
ヒント 3 — セーフティラインとタダ取り
Web はセーフティライン(画面の水色)という仮想プレイヤーを走らせていて、 岩の湧きは「ラインの上の仮想プレイヤーが生き残れること」だけを保証します。 つまりラインに乗っていれば絶対に安全です。

ラインの動き(決定論・再現可能): 生き残れる移動のうち、 プレイヤーが 2 列以内 にいれば「プレイヤーへ寄る → 停止 → 反対」、 同じ列か 3 列以上離れていれば「停止 → 中央寄り → 端寄り」の優先順で選ぶだけ。 つまりちょっとした寄り道なら迎えに来てくれますが、遠くへ稼ぎに行った人は追いかけません。 自力で戻る必要があります。同じ計算をすれば、あなたのスケッチでもラインの未来位置 L(t) を求められます。

離脱するとどうなるか: ラインから 2 列以上離れている間、 湧きが敵対的になります。壁の隙間はラインの位置に寄り、あなたの現在列の周辺には「狙い岩」が降ってきます (レベルが上がるほど高頻度に)。しかも狙い岩は盤面の途中(12 行目)から現れるので、 着地までの猶予はたった 3 ティック。深く外れるほど戻り道が塞がれ、詰まされる — それが「リスク」の正体です。

サンプルスケッチは「隣(±1 列)に着地するアイテムへ寄る(生存できる範囲で)」までを実装しています。 ラインはプレイヤーを 2 列以内なら迎えに来るので、1 列の寄り道は狩られにくいからです:
// 隣(±1 列)に着地する一番近いアイテムの方向(タダ取りの範囲)
int8_t toItem = nearItemDir();
// あれば [toItem, 0, -toItem]、なければ [0, 中央寄り, 端寄り] の優先順。
// どの候補も canSurvive(生存経路)があることが絶対条件。
// 同点なら items[15][nx] / items[14][nx](すぐ拾える)を優先。
これで「ラインの近くに湧くタダのアイテム(d=0〜1、1〜10点)」は拾えます。 しかし取れるのは 1 面 7〜9 個で、増えていくノルマには追いつきません。得点も最低クラスです。 ノルマを満たすには、ラインを外れて壁の中のアイテムを取りに行く必要があります。
ヒント 4 — 採算判断(ここが勝負どころ)
高得点アイテムはラインから離れた場所に落ちます。取りに行くには 「何ティック ラインを外れるか(離脱コスト d)」を自分で見積もる必要があります。

d の求め方: ヒント 2 の時空間探索に「ラインから外れているティック数」を コストとして載せた最短経路(DP)を解きます。状態 (x, t) ごとに 「岩を避けながらそこへ到達するのに必要な最小離脱ティック数」を計算し、 アイテムの着地点(列 c、t=15 または 16)での値がそのアイテムの d です。

採算の考え方:
・表示得点と d を比べる。得点の帯は重なっているので「浅いのに高いアタリ」が存在する
到達できない位置にも高得点は湧く。d が求まらない(届かない)アイテムは無視する
・壁の中のアイテム区間は、入り口の 1 個で満足するか奥まで踏み込むかで期待値が変わる
・深く外れるほど、湧きで詰まされる確率が上がる。死ねばこのランの得点は記録に残るが、その先の稼ぎを全部失う
復帰経路まで読む: 取った後にラインへ戻るまでが離脱時間。戻りが長い取り方は見た目の d より危険
・1 回の離脱で複数アイテムをまとめて取れる経路は効率が良い
・★(面クリアアイテム)は 1 面に最大 3 個湧く。ノルマが危ないときの脱出口だが、 順調なときは踏まないように避けること(早抜けは残りの稼ぎを全部失う)
2 手先の連鎖を読む: 壁の奥のお宝を取ると詰むが、 数ティック後の壁に★が埋まっていればそこへ逃げ込める。 「お宝 → ★」の 2 段階を 1 本の経路として時空間探索できれば、 他の実装が捨てる高得点を回収できる

得点は d に対して指数的に増えます(d=6 の 1 個 ≒ d=1 の 10 個分)。 事故率を下げられる実装ほど深く潜れて大差がつく — ここがこのゲームの競技部分です。 この部分の完成コードはあえて載せません。自分のアルゴリズムで勝負!

スケッチソース — RockDodge

GitHub ↗

ボード: HID ProMicro CH32V003  /  Tools → USB: WebHID Only