UIAPduino が落ちてくる岩をよけながら得点アイテムを拾い、最高スコアを競う
▦ RockDodge.ino の動きを図で理解する →
Tools → USB → WebHID Only で書き込んでください。
プレイヤー名はスケッチの #define PLAYER_NAME を書き換えて設定します(半角英数 12 文字まで)。
水色のセーフティラインに乗っている限り、岩には絶対に当たりません。 しかし乗っているだけではアイテムのノルマに届かず、120 ステップ後にゲームオーバー。 高得点は壁の中やラインの外に湧きます。取りに行くかどうかはアルゴリズム次第 — 死んだらそれは欲張ったせいです。
| Level | 速度 (ms/tick) | 岩の湧き間隔 | 岩パターン | アイテムノルマ |
|---|---|---|---|---|
| 1 | 150 | 4 tick | ランダム 1〜2 個 | 6 個 |
| 2 | 150 | 3 tick | ランダム 2〜3 個 | 7 個 |
| 3 | 140 | 3 tick | ランダム+壁(隙間 4) | 8 個 |
| 4 | 130 | 3 tick | 壁(隙間 4〜3) | 9 個 |
| 5 | 120 | 2 tick | 壁+流れる壁(隙間 3) | 10 個 |
| 6+ | 120 → 80 まで短縮 | 2 tick | 全パターン混合 | 11 → 12 個 |
「壁」= 隙間以外の全列に岩が並ぶ行。「流れる壁」= 隙間の位置が 1 枚ごとに左右へずれていく連続壁。 壁の岩は、一部が 2〜4 列つながってアイテムに置き換わることがあります — 隙間(=ラインの通り道)とは別の、岩に囲まれた「もうひとつの開口部」です。 奥へ入るほど高得点になりますが、それだけラインから遠ざかります。 奥のお宝には、数ティック後の壁に★(脱出路)が用意されることがあります。 120 ステップ生存かつノルマ達成でクリア。ノルマ未達はゲームオーバー。
岩をよけ続けるだけでは得点は増えません。
得点はアイテム(落下中に数字が見える)と、面クリアのサービス点 +10 点(固定)だけです。
アイテムの得点は、それを取るためにセーフティラインを何ティック外れる必要があるか(リスク d)に連動します。
帯は互いに重なっているので、「浅いのに高いアタリ」「深いのに安いハズレ」があります。数字をよく見て判断しましょう。
アイテムは1 行に何個でも湧きます。とくに壁の行では、岩の一部が 2〜4 列つながってアイテムに変わります。
その区間は岩に囲まれた通路なので、入り口の 1 個だけ拾って引き返すか、奥の高得点まで踏み込むかの判断になります。
| リスク d(離脱ティック数) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7+ |
|---|---|---|---|---|---|---|---|---|
| 得点範囲 | 1–4 | 4–10 | 9–18 | 16–30 | 26–46 | 42–66 | 60–88 | 78–99 |
★(面クリアアイテム): 得点 0。取ると即レベルクリア+ノルマ免除(クリアの +10 点は入る)。
1 レベルに最大 3 個、20 ステップ目以降に間隔をあけて湧きます。
置かれるのはラインから深く外れた位置(d 4〜7)なので、狙って行かないと取れません。
ノルマが危ないときの脱出口ですが、取ると残りの稼ぎを全部捨てることになります。
順調なときは踏まないように避けるのが正解 — うっかり踏んで面を早抜けすると大損です
(実測: ★を避ける実装は避けない実装より約 20% 高得点)。
ノルマは安全運転では届きません。
ラインに乗ったまま取れる低リスクなアイテムは 1 レベルに 4 個しか保証されません(=詰み防止の最低保証だけ)。
実測で、安全運転で取れるのは 1 面 7〜9 個。ノルマは Lv1 で 6 個、Lv5 で 10 個まで増えます。
ラインを外れて稼ぐしかない — それがこのゲームの本体です。
到達できない位置に高得点が湧くこともあります(とくに壁の中)。
数字の大きさに釣られず、自分で到達可能性を確かめましょう。
⚡ 脱出路の仕掛け
壁の奥深くにお宝があるとき、数ティック後の壁に、そのお宝の位置から届く★が埋め込まれることがあります。
奥まで踏み込めば戻れず詰み確定 — と思いきや、★を拾って即クリアで脱出できる、というルートです。
ただしいつも用意されているわけではありません(深いお宝の約 4 割)。
★が来るかどうかを確かめずに突っ込めば、そのまま狩られて終わりです。
「奥のお宝を取ったあと、次の壁の★まで到達できるか」を先読みで検証できるかが腕の分かれ目になります。
Feature Report 32バイト(Web → UIAPduino)/ Input Report 8バイト(UIAPduino → Web)
| 方向 | byte[0] | byte[1] | byte[2..] | 説明 |
|---|---|---|---|---|
| Web→Arduino | 0x01 CMD_START | startX | [2]=ノルマ [3]=ステップ数(120) | ゲーム開始・プレイヤー初期位置・レベル条件 |
| Web→Arduino | 0x02 CMD_TICK | playerX | [2]=maskL [3]=maskH [4]=狙い岩列(255=なし) [5..20]=列ごとのアイテム値 | 1ティック進行。岩マスク+狙い岩(12行目)+アイテム(1行に複数可・0=なし/1〜99=得点/255=★) |
| Web→Arduino | 0x05 CMD_RESET | — | — | 岩・状態をリセット |
| Web→Arduino | 0x40 CMD_GET_NAME | — | — | プレイヤー名を要求(接続直後に送信) |
| Arduino→Web | 0x10 CMD_DIR | dx (−1/0/+1) | — | 次の移動(dx=+1 右, −1 左, 0 停止) |
| Arduino→Web | 0x41 CMD_NAME | chunk (0/1) | [2..7]=名前6文字 | プレイヤー名(6文字×2分割) |
| Arduino→Web | 0x14 CMD_READY | — | — | 起動完了・接続確認 |
通信の順序: CMD_RESET → CMD_START → CMD_DIR受信 → (CMD_TICK → CMD_DIR) × n。 岩は「毎ティック 1 マス落下」の決定論ルールなので、湧きマスクだけ受け取ればデバイス側で全岩の位置を追跡できます。
CMD_TICK がデバイスに届く → CMD_DIR を返す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) — 次の移動を送信(必ず呼ぶ)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 列)に着地する一番近いアイテムの方向(タダ取りの範囲) int8_t toItem = nearItemDir(); // あれば [toItem, 0, -toItem]、なければ [0, 中央寄り, 端寄り] の優先順。 // どの候補も canSurvive(生存経路)があることが絶対条件。 // 同点なら items[15][nx] / items[14][nx](すぐ拾える)を優先。これで「ラインの近くに湧くタダのアイテム(d=0〜1、1〜10点)」は拾えます。 しかし取れるのは 1 面 7〜9 個で、増えていくノルマには追いつきません。得点も最低クラスです。 ノルマを満たすには、ラインを外れて壁の中のアイテムを取りに行く必要があります。
ボード: HID ProMicro CH32V003 / Tools → USB: WebHID Only