脳トレ系タッチゲームDIY ~自宅でワニワニパニックがしたいな~

つるさん(当サイトのもう一匹の管理者)と、自宅で「ワニワニパニック」がしたいと思い、脳トレ系の類似品をDIYしてみました♪

当記事の内容
  1. ゲームセンターの「ワニワニパニック」風のゲームをタッチセンサ活用で自作✋✋
  2. Arduino Uno初心者でもAIチャット活用により電子回路設計・プログラミングが可能⚡
  3. デジタル表示や4色のLEDライト採用。8種の脳トレ系ゲームの1~2人プレイに対応🎮🎮
  4. エネループ単3電池4本で駆動。「いつでも・どこでも・だれとでも」ハイスコアを競い合える🔥

 

背景・目的

幼少期のつるさんは、「ワニワニパニック」で遊んでいたようです!!

 

幼少期のかめは自宅でよくテレビゲームをしており、

「ワニを叩くのが楽しいなら」と考え、

モンスターハンターの協力プレイを誘ってみたのですが、

ケルビが倒せず断念…🦌🔨

 

「つるさんと一緒にゲームをする夢」

が諦めきれず、(資格の勉強中だったにもかかわらず、)

気が付いたら自作の勉強机の隣に自作のゲーム機が並んでいました(笑)

※自作の勉強机のDIY記事はこちら

ピアノが弾けてコーヒーも挽ける勉強机が欲しい

ゲーム企画

現役の「ワニワニパニックR」が、今でもゲームセンターで遊べるみたいですね!!

※バンダイナムコアミューズメントさんの紹介サイトはこちら:https://bandainamco-am.co.jp/am/vg/waniwanipanicr/

 

そして、「おうちでワイワイ ワニワニパニック」がバンダイさんから発売されています♪

 

検討の結果、

  • ゲームセンターに足を運んで毎回100円払うのは気が引ける
  • 三千円のクオリティでは恐らく満足できない
  • 飽きたら脳トレも遊びも続かない
☞「改造可能な開発型ゲームのDIYしかない★🔨」

という結論に至りました!!!

 

流行りのAIチャット(Gemini、Copilot、ChatGPT)への質問を繰り返して、以下の製作仕様に決定しました。

  • Arduino Unoを利用した電子回路⚡
  • ポリプロピレン製のBOXを連結。計16か所のタッチマスを空間的に配置📦
  • タッチセンサTTP223をBOX裏面に固定。激しく叩いても壊れにくい構造★🔨
  • タッチマスに対応する4色のLEDを計56点配置。ゲームモードに合わせて点灯💡☆🚨★
  • 数字やアルファベットが1文字表示可能なデジタル表示も計4点配置。ゲームモードの拡張に利用🎮🎮
  • LCDモニターによるゲームナビゲーションおよびハイスコア表示🗻
  • DFPlayer Miniを2台を搭載。お気に入りの効果音や楽曲mp3再生♪♫
  • エネループ単三×4本で駆動可能なDC5V回路⚡🔋

 

材料購入&組み立て
1.オーディオファンテック(楽天市場)

ゲームの頭脳となる「Arduino Uno R3」を購入。自分でゲームが自由に設計できるワクワク商品です🎉

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

ARDUINO UNO R3 ATmega328P ATMEGA16U2 開発ボード USBケーブル付き
価格:950円(税込、送料無料) (2026/7/18時点)

レイアウトひでぇな…(笑)

 

DFPlayer Mini+スピーカー×2個も2セット購入。microSDに保存したmp3音源を鳴らすことができます。

 

2.札幌貿易(楽天市場)

電子回路構築に必要なブレッドボード、ジャンパー線、スイッチ、電池ボックス、主電源用ヒューズを購入。

 

このブレッドボードがとても優秀で、次の電子工作に備えてたくさんストックしておきたくなりましたよ!🍞🍞🍞🍞🍞

  • とにかくコスパが良い
  • 中央にゼットソーで溝を掘ってLED配線すっきり【🚫改造利用のため要注意🚫】
  • LEDや抵抗、デジタル表示をそのまま挿すだけで使える

 

そして、液晶表示器LCD1602A(I2C変換基板付)を購入。2本の配線のみで、16文字×2行のモニター表示ができます。

 

3.うたかた雑貨、emulandなど(楽天市場)

ゲームの主要部品であるタッチセンサTTP223を20個1セット(4個は予備)で購入。超重要部品!🌟🔍

 

このタッチセンサをAIチャットでオススメされたことで、当DIY企画が実行段階に移りました🚀

 

想像以上にサイズが小さく3つのピンを自分ではんだ付けしなければならない点や、

BOX越しの貼り付けで正常にタッチが反応するか分からない点など、

AIチャットが物知りすぎて本当に助かりました💡

【上部添付写真のタッチマス裏面DIY材料構成】
  1. ダイソーのBOX(1mmの厚みはセンサーの動作に影響しない)
  2. ダイソーのOPPテープ(以下の透明ゲルテープの接着性UP用、センサ感度に影響なし)
  3. ダイソーの透明ゲルテープ(表面からタッチした時の衝撃吸収)
  4. タッチセンサTTP223(①GND、②I/Oタッチ信号、③VCC:DV5V電源の配線が必要)
  5. 秋月電子通商のピンヘッダ×3本(TTP223に対してオスピンのはんだ付けを3か所実施)
  6. 秋月電子通商のメスコネクタ付きケーブル×3色(平形で整線に便利)
  7. セリアの竹ひご×2本(ケーブルが動かないように挟む)
  8. ダイソーのOPPテープ(高湿度環境下で竹ひごがカビ出したので竹ひごの周囲をテーピング済み)
  9. ダイソーの園芸用結束バンド(締め付けてセンサ位置を固定)

 

4.秋月電子通商(専用通販サイトあり)

楽天市場の他のお店と比較して、楽天お買い物マラソンのポイントの値引きや追加で発生する送料550円を考慮しても、以下の17種類の部品は秋月電子通商でのまとめ買いが安価との判断。

  1. 3mm砲弾型黄緑色LED 570nm 単価10円
  2. 3mm砲弾型電球色LED 色温度3000K 10個セット価格140円
  3. 3mm砲弾型黄色LED 590nm 10個セット価格100円
  4. 3mm砲弾型青緑色LED 505nm 単価20円
  5. 赤色7セグメントLED表示器(カソードコモン) 単価40円
  6. コネクタ付ケーブル20cm(メス-メス) 40Pセット価格200円
  7. ピンヘッダ 40Pセット価格35円
  8. 8ビットシフトレジスタU74HC595AG-D16 単価40円
  9. 8ビットシフトレジスタTC74HC165AP 単価100円
  10. 電解コンデンサ47μF 16V 単価10円
  11. 電解コンデンサ100μF 16V 単価10円
  12. 電解コンデンサ220μF 16V 単価10円
  13. 電解コンデンサ470μF 16V 単価10円
  14. セラミックコンデンサ0.1μF 50V 10個セット価格50円
  15. カーボン抵抗1/4W 1kΩ 100本セット価格100円
  16. カーボン抵抗1/2W 330Ω 100本セット価格100円
  17. 押しボタン(橙色・モーメンタリ) 単価70円

カラフルなLED安価な出力用/入力用シフトレジスタ豊富な抵抗・コンデンサラインナップが最高です★

 

5.ダイソーやセリア店頭での購入
  • 【ダイソー】積み重ね収納ボックス(スクエア15.2-15.3-8.3cm)←タッチ箇所のキーアイテム🔑
  • 【ダイソー】園芸用結束バンド(インシュロック140本入り)
  • 【ダイソー】A4透明クリップボード(ポリスチレン製)
  • 【セリア】竹ひご(Φ4mm-長さ36cm 15本入り)
  • 【セリア】アンティークデザイン取っ手
  • 【セリア】木ねじ
  • 【セリア】キッチン壁用アルミシート

ブレッドボード上のLEDの保護のため、A4透明クリップボードを木ねじで固定。透明度良好。
※ただし、割れやすい&傷付きやすい材料のためタミヤの模型用ミニノコギリで切断を試みたところ、半日かかりました…(笑)

 

過去のDIYで余った木材や木ねじ、固定用金具、スタイロエース-Ⅱを活用して、取っ手付きの重厚なケースおよび裏蓋を構築🌳👜

 

裏蓋の赤松集成材にセリアのアルミシートを貼り付けることで、タッチセンサTTP223反応時に点灯するチップ上の赤色LEDの光を反射させ、BOXがより赤くなるように演出🚨

 

そして、過去に購入していたもので、今回一番活躍したのがチャイムコードΦ0.8mm-2芯🔌

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

エルパ TIVF-20H TIVF20H チャイムコード 20m グレー ELPA 【SB06309】
価格:690円(税込、送料別) (2026/7/29時点)

 

基板回路向けの規格品ではなく、導体サイズΦ0.8mmはピンヘッダサイズ□0.64mmよりやや大きく、Arduinoなどのメスコネクタに一度挿すと、ピンヘッダが二度とはまらなくなってしまう点は注意ですが、

  • 導体が単芯で太く、曲げた後の形状記憶力と挿し込んだ後の保持力が優秀
  • 自由な長さでカットした後の端末処理が不要で、ブレッドボードやメスコネクタにそのまま挿せる

のメリットが大きく、ブレッドボード上の82点のLED出力個別配線や、16か所に配置したタッチセンサTTP223の配線長さ調整に重宝しました🌟

 

総重量はかめの体重と合わせて体重計で78.7kg!! (えっ!?どっちが重いの?!?)

 

この重量感とゲームの完成度の高さから、

間違いなく【かめの甲羅】で決まりです!!!

 

脳トレ系タッチゲーム「」【かめの甲羅14号】🐢

※当サイトではかめの努力の積み重ねの成果を、[かめの甲羅]として登録しています(笑)

※前回の[かめの甲羅13号]は、「食品栄養成分計算表(自動計算エクセル)」です!!

【自動計算エクセル掲載】栄養成分データを活用して食生活を改善しよう!!

 

過去のDIYで余った材料の費用を除く、今回の追加投資額は11949円!!💰(120回遊べば元が取れる!?)

 

DIY完遂のポイント
1.AIチャットの進歩により、Arduinoの電子回路設計とプログラミングを全てお任せできた点
  1. AIチャット情報から「Arduino Uno」「タッチセンサTTP223」がコスト面で優位
  2. 電子回路設計やプログラミングが全く分からない初心者にも優しく、何をやっても褒めてくれるチャット返信
  3. 電子部品選定からおよそ2400行の難解なコード(当記事の最後に貼り付けあり)のエラー修正までネット調査不要でほとんどQ&Aで解決(AIチャットが関連のあるサイトの調査を代行)
これはまさに、AI時代の恩恵ですね!🧠

Brilliant!!!!!

 

一方で、AIチャット活用において以下の点に注意が必要と感じました。

  • こちらの状況や質問の意図を正しく汲み取れずに、不適切な提案を行うことがあり、プログラミングエラーに対する修正を繰り返しても解決しない沼にハマる。
  • 不適切な提案を自信満々にするが、誤りを指摘をするとすぐに謝って修正する。AIから提示される情報は鵜吞みにできない。
  • 【最も時間がかかった点】無料モードのAIチャットでは、およそ2400行のプログラミングコードの一括送信・受信が厳しく、分割が必要になるが、過去のチャット情報を次第に参照しなくなるため、コードが肥大化するに従って開発者自身の理解や手直しが必要となる。

 

2.AIチャットに質問するゲーム開発者(かめ)に予備知識があった点
  • 電子回路について:DC5Vの回路における直流電源、抵抗、コンデンサ、LED、シフトレジスタの挙動に関する物理の理解(AI提案内容の誤りに気付く上で必要)。
  • プログラミングについて:他のプログラミング言語(C言語、エクセルマクロ、サイト運営のためのCSS/HTMLなど)の仕組みの理解と編集経験(AI提案内容をコピぺするだけでは、ゴールにたどり着けないため)。

 

遊び方
1.電源スイッチを入れる🔘👆

 

2.赤色のON/OFFスイッチでプレイ人数「1人:SINGLE/2人:COOP」と、ゲームモード(全8種)を選ぶ🎮

 

【🎮8種のゲームモード🎮】
  • BASIC:黄緑色LEDが点灯するマスをタッチ。
  • 3COLORS:黄緑→黄→白色の順にLED点灯マスをタッチ。
  • DOUBLE:黄緑/黄/白色のうち、LEDが2灯点灯するマスをダブルタッチ。
  • MINIMUM:4つのデジタル表示の中で最も数字が小さいマスグループ内において、黄緑/黄/白色のLED点灯していないマスをタッチ。
  • ACE:4つのデジタル表示の中で最もアルファベット順がAに近いマスグループ内において、黄緑/黄/白色のLED点灯していないマスをタッチ。
  • UNLOCK:①デジタル表示のドットが点灯、②青緑色LEDが点灯、③黄緑/黄/白色LEDが全て消灯、の①②③を満たすマスをタッチ保持すると、ロックが解除され、タッチが有効なマスが出現。
  • PANIC:黄緑/黄/白色のうち、LEDが全点灯していないマスをタッチ。多数の点滅ダミーが出現。
  • RANDOM:上記の7種のゲームモードがタッチ成功の度に切り替わるシャッフルモード。

 

3.黄色ボタンでゲーム時間を調整⌛

 

4.隣の橙色ボタンでゲーム開始!!BGMランダム再生🎹
  • ベートーヴェン:ピアノ協奏曲第5番 Op.73-1「通称:皇帝」
  • ベートーヴェン:交響曲第6番 Op.68-1「田園」
  • ショパン:エチュード Op.25-1「通称:エオリアンハープ」
  • ショパン:ポロネーズ第3番 Op.40-1「通称:軍隊ポロネーズ」
  • ブラームス:ハンガリー舞曲 第5番
  • ドビュッシー:2つのアラベスク 第1番
  • チャイコフスキー:ピアノ協奏曲 Op23-3
  • ショスタコーヴィチ:ジャズ組曲 第1番 ワルツ
  • グリーグ:トロルドハウゲンの婚礼の日 Op.65-6
  • クライスラー/ラフマニノフ:愛の悲しみ
  • フォーレ:シシリエンヌ Op.78

※脳トレ系と言いつつモーツァルトはあえて選曲せず(笑)

 

5.ゲームモードごとに異なるLED点灯ルールに従って、正解のマスをタッチ(添付写真はゲームモードUNLOCK)💡✋

 

6.ゲーム時間内に何回有効なタッチできたか、ハイスコア(HS)を競い合う🔥🔥🔥

 

【ご参考】コード全文
  • AIチャットと共同で作り上げたプログラミングコード全文を、ご参考に貼り付けて思い出に残します♪
  • 改善の余地ありですが、Arduino Unoの有効残メモリが安全圏内に入ったため、AIチャットとの追い込みはほどほどに留めています。無料のAIチャットで長時間長文を送り続けていると、AIの処理速度に制限がかかるようになる上に、回答が次第に粗末になっていく印象があります(笑)
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>

const int PIN_PL = 8;
const int PIN_CLK_165 = 12;
const int PIN_QH = 7;

const int PIN_DS = 11;
const int PIN_STCP = 9;
const int PIN_CLK_595 = 13;
const int NUM_595 = 12;

const int dipPins[7] = { A0, A1, A2, A3, 2, 3, 4 };

LiquidCrystal_I2C lcd(0x27, 16, 2);

const byte SEG_0 = 0b11011110;
const byte SEG_1 = 0b00000110;
const byte SEG_2 = 0b11101100;
const byte SEG_3 = 0b01101110;
const byte SEG_4 = 0b00110110;
const byte SEG_5 = 0b01111010;
const byte SEG_6 = 0b11111010;
const byte SEG_7 = 0b00011110;
const byte SEG_8 = 0b11111110;
const byte SEG_9 = 0b01111110;

const byte SEG_A = 0b10111110;
const byte SEG_C = 0b11011000;
const byte SEG_E = 0b11111000;
const byte SEG_F = 0b10111000;
const byte SEG_H = 0b10110110;
const byte SEG_J = 0b11000110;
const byte SEG_L = 0b11010000;
const byte SEG_P = 0b10111100;
const byte SEG_U = 0b11010110;

const byte SEG_MINUS = 0b00100000;
const byte SEG_EQUAL = 0b01001000;
const byte SEG_IDENTICAL = 0b01101000;
const byte SEG_UNDERBAR = 0b01000000;
const byte SEG_OVERBAR = 0b00001000;

const byte SEG_BLANK = 0b00000000;

const byte testPatterns[] PROGMEM = {
SEG_0, SEG_1, SEG_2, SEG_3, SEG_4,
SEG_5, SEG_6, SEG_7, SEG_8, SEG_9,
SEG_A, SEG_C, SEG_E, SEG_F, SEG_H,
SEG_J, SEG_L, SEG_P, SEG_U,
SEG_UNDERBAR, SEG_MINUS, SEG_OVERBAR,
SEG_EQUAL, SEG_IDENTICAL, SEG_BLANK
};
const int testCount = sizeof(testPatterns) / sizeof(testPatterns[0]);

const int ledChip[4][4] = {
{ 5, 5, 6, 6 },
{ 4, 4, 7, 7 },
{ 3, 3, 8, 8 },
{ 2, 2, 9, 9 }
};

const int ledBitG[4][4] = {
{ 6, 2, 2, 6 },
{ 6, 2, 2, 6 },
{ 6, 2, 2, 6 },
{ 6, 2, 2, 6 }
};

const int ledBitY[4][4] = {
{ 7, 1, 1, 7 },
{ 7, 1, 1, 7 },
{ 7, 1, 1, 7 },
{ 7, 1, 1, 7 }
};

const int ledBitW[4][4] = {
{ 5, 3, 3, 5 },
{ 5, 3, 3, 5 },
{ 5, 3, 3, 5 },
{ 5, 3, 3, 5 }
};

const int unlockJ_chip[4][4] = {
{ 5, 5, 6, 6 },
{ 4, 4, 7, 7 },
{ 3, 3, 8, 8 },
{ 2, 2, 9, 9 }
};
const int unlockJ_bit[4][4] = {
{ 4, 4, 4, 4 },
{ 4, 4, 4, 4 },
{ 4, 4, 4, 4 },
{ 4, 4, 4, 4 }
};

const int unlockQ0_chip[4][4] = {
{ 0, 0, 10, 10 },
{ 0, 0, 10, 10 },
{ 1, 1, 11, 11 },
{ 1, 1, 11, 11 }
};
const int unlockQ0_bit[4][4] = {
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 }
};

const int segChips[4] = { 0, 1, 10, 11 };

const char COLOR3[3] = { 'G', 'Y', 'W' };

const int touchBit[4][4] = {
{ 0, 1, 8, 9 },
{ 2, 3, 10, 11 },
{ 4, 5, 12, 13 },
{ 6, 7, 14, 15 }
};

enum GameState {
WAIT,
COUNTDOWN,
PLAYING_SINGLE,
PLAYING_COOP,
RESULT
};

enum GameKind {
GAME_BASIC,
GAME_3COLORS,
GAME_DOUBLE,
GAME_RANDOM,
GAME_MINIMUM,
GAME_UNLOCK,
GAME_ACE,
GAME_PANIC
};

struct Target {
int row;
int col;
char color;
bool alive;
};

struct Player {
Target targets[4];
int stage;
int score;
};

struct Cell {
int row;
int col;
};

class DFPlayerManager {
public:
SoftwareSerial mp3_1;
SoftwareSerial mp3_2;
bool useMp3_1;

DFPlayerManager()
: mp3_1(255, 5), mp3_2(255, 6), useMp3_1(true) {}
void sendCommand(SoftwareSerial &mp3, byte cmd, byte p1, byte p2) {
uint16_t checksum = 0xFFFF - (0xFF + 0x06 + cmd + 0x00 + p1 + p2) + 1;
byte buf[10] = {
0x7E, 0xFF, 0x06, cmd, 0x00, p1, p2,
(byte)(checksum >> 8),
(byte)(checksum & 0xFF),
0xEF
};
mp3.write(buf, 10);
}

void playCoin() {
sendCommand(mp3_1, 0x03, 0x00, 0x01);
}

void playMusic() {
uint32_t now = millis();
byte track = (byte)(11 + (now % 12));
sendCommand(mp3_2, 0x12, 0x00, track);
}
};

class LedDriver {
public:
byte data[NUM_595];

LedDriver() {
for (int i = 0; i < NUM_595; i++) data[i] = 0x00;
}

void begin() {
pinMode(PIN_DS, OUTPUT);
pinMode(PIN_CLK_595, OUTPUT);
pinMode(PIN_STCP, OUTPUT);
digitalWrite(PIN_CLK_595, LOW);
}

void setAllOff() {
for (int i = 0; i < NUM_595; i++) data[i] = 0x00;
}

void setAllOn() {
for (int i = 0; i < NUM_595; i++) data[i] = 0xFF;
}

void lightColorLed(int row, int col, char color) {
int chip = ledChip[row][col];
int bit;
if (color == 'G') bit = ledBitG[row][col];
else if (color == 'Y') bit = ledBitY[row][col];
else if (color == 'W') bit = ledBitW[row][col];
else return;
data[chip] |= (1 << bit);
}

void update() {
digitalWrite(PIN_STCP, LOW);
for (int i = NUM_595 - 1; i >= 0; i--) {
shiftOut(PIN_DS, PIN_CLK_595, MSBFIRST, data[i]);
}
digitalWrite(PIN_STCP, HIGH);
}

void showLedTestPattern() {
setAllOff();
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
int chip = ledChip[r][c];
int bit = ledBitG[r][c];
data[chip] |= (1 << bit);
}
}
update();
}

void show7Seg(byte pattern) {
data[0] = pattern;
data[1] = pattern;
data[10] = pattern;
data[11] = pattern;
update();
}

void show7SegSections(byte p0, byte p1, byte p2, byte p3) {
data[0] = p0;
data[1] = p1;
data[10] = p2;
data[11] = p3;
update();
}

void clear7Seg() {
for (int i = 0; i < 4; i++) {
data[segChips[i]] = 0x00;
}
update();
}
};

byte digitToSeg(int d) {
switch (d) {
case 0: return SEG_0;
case 1: return SEG_1;
case 2: return SEG_2;
case 3: return SEG_3;
case 4: return SEG_4;
case 5: return SEG_5;
case 6: return SEG_6;
case 7: return SEG_7;
case 8: return SEG_8;
case 9: return SEG_9;
}
return SEG_BLANK;
}

byte aceToSeg(int idx) {
switch (idx) {
case 0: return SEG_A;
case 1: return SEG_C;
case 2: return SEG_E;
case 3: return SEG_F;
case 4: return SEG_H;
case 5: return SEG_J;
case 6: return SEG_L;
case 7: return SEG_P;
case 8: return SEG_U;
case 9: return SEG_MINUS;
case 10: return SEG_EQUAL;
case 11: return SEG_IDENTICAL;
}
return SEG_BLANK;
}

class Shift165 {
public:
void begin() {
pinMode(PIN_PL, OUTPUT);
pinMode(PIN_CLK_165, OUTPUT);
pinMode(PIN_QH, INPUT);
digitalWrite(PIN_PL, HIGH);
digitalWrite(PIN_CLK_165, LOW);
}

uint16_t read16() {
uint16_t value = 0;
digitalWrite(PIN_PL, LOW);
delayMicroseconds(5);
digitalWrite(PIN_PL, HIGH);
delayMicroseconds(5);

for (int i = 0; i < 16; i++) {
value <<= 1;
value |= digitalRead(PIN_QH);
digitalWrite(PIN_CLK_165, HIGH);
delayMicroseconds(3);
digitalWrite(PIN_CLK_165, LOW);
delayMicroseconds(3);
}
return value;
}
};

// GameManager
class GameManager {
public:
GameState state = WAIT;
GameKind gameKind = GAME_BASIC;
GameKind lastGameKind = GAME_BASIC;
bool lastCoopMode = false;

GameKind kindSingle = GAME_BASIC;
GameKind kindCrane = GAME_BASIC;
GameKind kindTurtle = GAME_BASIC;

unsigned long stateStart = 0;
int gameTime = 10;

bool prevD2 = false;
bool prevD3 = false;
bool prevStart = false;
bool prevD2_result = false;
bool prevD3_result = false;

uint16_t prevInput16 = 0;
uint16_t lastInput16_forRandom = 0;
uint16_t prevInput16_crane = 0;
uint16_t prevInput16_turtle = 0;

int highScore_basic_S = 0;
int highScore_basic_C = 0;
int highScore_3c_S = 0;
int highScore_3c_C = 0;
int highScore_double_S = 0;
int highScore_double_C = 0;
int highScore_random_S = 0;
int highScore_random_C = 0;
int highScore_minimum_S = 0;
int highScore_minimum_C = 0;
int highScore_unlock_S = 0;
int highScore_unlock_C = 0;
int highScore_ace_S = 0;
int highScore_ace_C = 0;
int highScore_panic_S = 0;
int highScore_panic_C = 0;

bool isRandomMode = false;

Player singleP;
Player craneP;
Player turtleP;

Cell sectionCells[4][16];
int sectionCellCount[4] = { 0, 0, 0, 0 };

int minimumDigits_S[4];
Cell minimumLit_S[4][3];

int aceDigits_S[4];
Cell aceLit_S[4][3];

int minimumDigits_Crane[2];
int minimumDigits_Turtle[2];
Cell minimumLit_Crane[2][3];
Cell minimumLit_Turtle[2][3];

int aceDigits_Crane[2];
Cell aceLit_Crane[2][3];

int aceDigits_Turtle[2];
Cell aceLit_Turtle[2][3];

Cell unlockCell;
Cell prevUnlockCell;
Cell gCell;
Cell prevGCell;
bool unlockActive;

Cell unlockCell_Crane;
Cell prevUnlockCell_Crane;
Cell gCell_Crane;
Cell prevGCell_Crane;
bool unlockActive_Crane;

Cell unlockCell_Turtle;
Cell prevUnlockCell_Turtle;
Cell gCell_Turtle;
Cell prevGCell_Turtle;
bool unlockActive_Turtle;

byte unlockDummyData[NUM_595];

byte unlockDummyData_Crane[NUM_595];
byte unlockDummyData_Turtle[NUM_595];

Cell panicCell;
char panicSkipColor;
Cell panicCell_Crane;
char panicSkipColor_Crane;
Cell panicCell_Turtle;
char panicSkipColor_Turtle;

unsigned long panicSegLastStep = 0;
unsigned long panicSegLastStep_Crane = 0;
unsigned long panicSegLastStep_Turtle = 0;

void forceKeepCellOneColorDark(const Cell &cell, char skipColor) {
int r = cell.row;
int c = cell.col;
int chip = ledChip[r][c];

if (skipColor == 'G') {
int bit = ledBitG[r][c];
led.data[chip] &= ~(1 << bit);
} else if (skipColor == 'Y') {
int bit = ledBitY[r][c];
led.data[chip] &= ~(1 << bit);
} else if (skipColor == 'W') {
int bit = ledBitW[r][c];
led.data[chip] &= ~(1 << bit);
}
}

LedDriver &led;
DFPlayerManager &dfp;
Shift165 &in165;

GameManager(LedDriver &l, DFPlayerManager &d, Shift165 &i)
: led(l), dfp(d), in165(i) {}

void drawTriple(const Cell triple[3]) {
for (int k = 0; k < 3; k++) {
led.lightColorLed(triple[k].row, triple[k].col, COLOR3[k]);
}
}

void setLeft7Seg(byte a, byte b) {
led.data[0] = a;
led.data[1] = b;
}

void setRight7Seg(byte a, byte b) {
led.data[10] = a;
led.data[11] = b;
}

void set4Seg(byte a, byte b, byte c, byte d) {
led.show7SegSections(a, b, c, d);
}

// ユーティリティ
Cell randomCell() {
Cell c;
c.row = random(0, 4);
c.col = random(0, 4);
return c;
}

// 初期化
void begin() {
for (int i = 0; i < 7; i++) {
pinMode(dipPins[i], INPUT_PULLUP);
}

dfp.mp3_1.begin(9600);
dfp.mp3_2.begin(9600);
delay(300);

dfp.sendCommand(dfp.mp3_1, 0x3F, 0, 0);
delay(30);
dfp.sendCommand(dfp.mp3_1, 0x06, 0, 18);

dfp.sendCommand(dfp.mp3_2, 0x3F, 0, 0);
delay(30);
dfp.sendCommand(dfp.mp3_2, 0x06, 0, 27);
delay(30);

dfp.sendCommand(dfp.mp3_2, 0x12, 0x00, 0x0A);

lcd.init();
lcd.backlight();
char line1[] = "TK-PANIC READY!";
char line2[] = " -SINCE 2026- ";

lcd.setCursor(0, 0);
for (int i = 0; i < strlen(line1); i++) {
lcd.print(line1[i]);
delay(100);
}
delay(300);

lcd.setCursor(0, 1);
for (int i = 0; i < strlen(line2); i++) {
lcd.print(line2[i]);
delay(100);
}

delay(1000);
lcd.clear();

led.setAllOff();
led.update();

highScore_basic_S = EEPROM.read(0);
highScore_basic_C = EEPROM.read(1);
highScore_3c_S = EEPROM.read(2);
highScore_3c_C = EEPROM.read(3);
gameTime = EEPROM.read(4);
highScore_double_S = EEPROM.read(5);
highScore_double_C = EEPROM.read(6);
highScore_random_S = EEPROM.read(7);
highScore_random_C = EEPROM.read(8);
highScore_minimum_S = EEPROM.read(9);
highScore_minimum_C = EEPROM.read(10);
highScore_unlock_S = EEPROM.read(11);
highScore_unlock_C = EEPROM.read(12);
highScore_ace_S = EEPROM.read(13);
highScore_ace_C = EEPROM.read(14);
highScore_panic_S = EEPROM.read(15);
highScore_panic_C = EEPROM.read(16);

if (gameTime < 1 || gameTime > 99) gameTime = 10;

randomSeed(analogRead(A6));
initSectionCells();

unlockActive = false;
unlockActive_Crane = false;
unlockActive_Turtle = false;
}

// メイン更新
void update(uint16_t input16) {
switch (state) {
case WAIT:
handleWait(input16);
break;
case COUNTDOWN:
handleCountdown();
break;
case PLAYING_SINGLE:
if (gameKind == GAME_MINIMUM) {
handleMinimumSingle(input16); // ← MINIMUMシングル用
} else if (gameKind == GAME_ACE) {
handleAceSingle(input16); // ← ACEシングル用
} else {
handleSingle(input16); // その他の通常シングル用(BASIC等)
}
break;
case PLAYING_COOP:
if (gameKind == GAME_MINIMUM) {
handleMinimumCoop(input16);
} else if (gameKind == GAME_ACE) {
handleAceCoop(input16);
} else if (gameKind == GAME_UNLOCK) {
handleCoopUnlock(input16);
} else {
handleCoop(input16);
}
break;
case RESULT:
handleResult();
break;
}
}

// BASIC / 3COLORS / DOUBLE ターゲット選択(安全配列版)
void chooseTargets(Player &p, GameKind kind, int colMin, int colMax) {
if (kind == GAME_BASIC) {
p.targets[0].row = random(0, 4);
p.targets[0].col = random(colMin, colMax + 1);
p.targets[0].color = 'G';
p.targets[0].alive = true;
p.stage = 0;
} else if (kind == GAME_3COLORS) {
for (int i = 0; i < 3; i++) {
bool ok;
do {
ok = true;
p.targets[i].row = random(0, 4);
p.targets[i].col = random(colMin, colMax + 1);
for (int j = 0; j < i; j++) {
if (p.targets[i].row == p.targets[j].row && p.targets[i].col == p.targets[j].col) {
ok = false;
}
}
} while (!ok);
p.targets[i].color = (i == 0 ? 'G' : (i == 1 ? 'Y' : 'W'));
p.targets[i].alive = true;
}
p.stage = 0;
} else if (kind == GAME_DOUBLE) {
static const char COLOR_PALETTE[3] = { 'G', 'Y', 'W' };
char mainColor = COLOR_PALETTE[random(0, 3)];
char sub1, sub2;
if (mainColor == 'G') {
sub1 = 'Y';
sub2 = 'W';
} else if (mainColor == 'Y') {
sub1 = 'G';
sub2 = 'W';
} else {
sub1 = 'G';
sub2 = 'Y';
}

for (int i = 0; i < 2; i++) {
bool ok;
do {
ok = true;
p.targets[i].row = random(0, 4);
p.targets[i].col = random(colMin, colMax + 1);
for (int j = 0; j < i; j++) {
if (p.targets[i].row == p.targets[j].row && p.targets[i].col == p.targets[j].col) {
ok = false;
}
}
} while (!ok);
p.targets[i].color = mainColor;
p.targets[i].alive = true;
}

for (int i = 2; i < 4; i++) {
bool ok;
do {
ok = true;
p.targets[i].row = random(0, 4);
p.targets[i].col = random(colMin, colMax + 1);
for (int j = 0; j < i; j++) {
if (p.targets[i].row == p.targets[j].row && p.targets[i].col == p.targets[j].col) {
ok = false;
}
}
} while (!ok);
p.targets[i].color = (i == 2 ? sub1 : sub2);
p.targets[i].alive = true;
}
p.stage = 0;
}
}

// UNLOCK ターゲット選択(SINGLE)
void chooseUnlockSingle() {
prevUnlockCell = unlockCell;
prevGCell = gCell;

do {
unlockCell = randomCell();
} while ((unlockCell.row == prevUnlockCell.row && unlockCell.col == prevUnlockCell.col) || (unlockCell.row == prevGCell.row && unlockCell.col == prevGCell.col));

do {
gCell = randomCell();
} while (gCell.row == unlockCell.row && gCell.col == unlockCell.col);

unlockActive = false;
generateUnlockDummySingle();
}

// UNLOCK ダミー生成(SINGLE)
void generateUnlockDummySingle() {
for (int i = 0; i < NUM_595; i++) unlockDummyData[i] = 0;

int ur = unlockCell.row;
int uc = unlockCell.col;

int realJ_chip = unlockJ_chip[ur][uc];
int realJ_bit = unlockJ_bit[ur][uc];
int realQ_chip = unlockQ0_chip[ur][uc];
int realQ_bit = unlockQ0_bit[ur][uc];

for (int chip = 0; chip < NUM_595; chip++) {
for (int b = 0; b < 8; b++) {
bool isRealJ = (chip == realJ_chip && b == realJ_bit);
bool isRealQ = (chip == realQ_chip && b == realQ_bit);

bool isRealGYW = false;
if (chip == ledChip[ur][uc]) {
if (b == ledBitG[ur][uc]) isRealGYW = true;
if (b == ledBitY[ur][uc]) isRealGYW = true;
if (b == ledBitW[ur][uc]) isRealGYW = true;
}

if (isRealJ || isRealQ || isRealGYW) continue;

if (random(0, 4) == 0) {
unlockDummyData[chip] |= (1 << b);
}
}
}

for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
if (r == ur && c == uc) continue;

int cj = unlockJ_chip[r][c];
int bj = unlockJ_bit[r][c];
int cq = unlockQ0_chip[r][c];
int bq = unlockQ0_bit[r][c];

bool jOnDummy = (unlockDummyData[cj] & (1 << bj));
bool jOnReal = (cj == realJ_chip);
bool jOn = jOnDummy || jOnReal;

bool qOnDummy = (unlockDummyData[cq] & (1 << bq));
bool qOnReal = (cq == realQ_chip);
bool qOn = qOnDummy || qOnReal;

int chip = ledChip[r][c];
bool gOn = (unlockDummyData[chip] & (1 << ledBitG[r][c]));
bool yOn = (unlockDummyData[chip] & (1 << ledBitY[r][c]));
bool wOn = (unlockDummyData[chip] & (1 << ledBitW[r][c]));

if (jOn && qOn && !gOn && !yOn && !wOn) {
int sel = random(0, 3);
if (sel == 0) unlockDummyData[chip] |= (1 << ledBitG[r][c]);
else if (sel == 1) unlockDummyData[chip] |= (1 << ledBitY[r][c]);
else unlockDummyData[chip] |= (1 << ledBitW[r][c]);
}
}
}
}

// UNLOCK ターゲット選択(COOP CRANE)
void chooseUnlockCrane() {
prevUnlockCell_Crane = unlockCell_Crane;
prevGCell_Crane = gCell_Crane;

do {
unlockCell_Crane = randomCell();
} while (
unlockCell_Crane.col > 1 || (unlockCell_Crane.row == prevUnlockCell_Crane.row && unlockCell_Crane.col == prevUnlockCell_Crane.col) || (unlockCell_Crane.row == prevGCell_Crane.row && unlockCell_Crane.col == prevGCell_Crane.col));

do {
gCell_Crane = randomCell();
} while (
gCell_Crane.col > 1 || (gCell_Crane.row == unlockCell_Crane.row && gCell_Crane.col == unlockCell_Crane.col));

unlockActive_Crane = false;
generateUnlockDummyCrane();
}

// UNLOCK ターゲット選択(COOP TURTLE)
void chooseUnlockTurtle() {
prevUnlockCell_Turtle = unlockCell_Turtle;
prevGCell_Turtle = gCell_Turtle;

do {
unlockCell_Turtle = randomCell();
} while (
unlockCell_Turtle.col < 2 || (unlockCell_Turtle.row == prevUnlockCell_Turtle.row && unlockCell_Turtle.col == prevUnlockCell_Turtle.col) || (unlockCell_Turtle.row == prevGCell_Turtle.row && unlockCell_Turtle.col == prevGCell_Turtle.col));

do {
gCell_Turtle = randomCell();
} while (
gCell_Turtle.col < 2 || (gCell_Turtle.row == unlockCell_Turtle.row && gCell_Turtle.col == unlockCell_Turtle.col));

unlockActive_Turtle = false;
generateUnlockDummyTurtle();
}

// RANDOM SINGLE
void randomSwitchSingle() {
static const GameKind pool[7] = {
GAME_BASIC, GAME_3COLORS, GAME_DOUBLE,
GAME_MINIMUM, GAME_UNLOCK, GAME_ACE, GAME_PANIC
};

GameKind next = pool[random(0, 7)];
kindSingle = next;

if (next == GAME_MINIMUM) {
initSectionCells();
setupMinimumLayoutSingle();
} else if (next == GAME_UNLOCK) {
chooseUnlockSingle();
} else if (next == GAME_ACE) {
initSectionCells();
setupAceLayoutSingle();
} else if (next == GAME_PANIC) {
setupPanicLayoutSingle();
} else {
chooseTargets(singleP, next, 0, 3);
}

prevInput16 = in165.read16();
lastInput16_forRandom = prevInput16;
}

// RANDOM COOP(全7モード対応 完全修正版)
void randomSwitchCoop(Player &p, GameKind &kind, bool isCrane) {
static const GameKind pool[7] = {
GAME_BASIC, GAME_3COLORS, GAME_DOUBLE, GAME_PANIC, GAME_MINIMUM, GAME_UNLOCK, GAME_ACE
};

GameKind next = pool[random(0, 7)];
kind = next;

p.stage = 0;
for (int i = 0; i < 4; i++) {
p.targets[i].alive = false;
p.targets[i].row = 0;
p.targets[i].col = 0;
}

if (next == GAME_PANIC) {
if (isCrane) setupPanicLayoutCrane();
else setupPanicLayoutTurtle();

} else if (next == GAME_MINIMUM) {
if (isCrane) setupMinimumLayoutCrane();
else setupMinimumLayoutTurtle();

} else if (next == GAME_UNLOCK) {
if (isCrane) chooseUnlockCrane();
else chooseUnlockTurtle();

} else if (next == GAME_ACE) {
if (isCrane) setupAceLayoutCrane();
else setupAceLayoutTurtle();

} else {
initSectionCells();
int colMin = isCrane ? 0 : 2;
int colMax = isCrane ? 1 : 3;
chooseTargets(p, next, colMin, colMax);
}

uint16_t now = in165.read16();
if (isCrane) {
prevInput16_crane = now & 0x00FF;
} else {
prevInput16_turtle = now & 0xFF00;
}
}

// 描画
void drawPlayer(const Player &p, GameKind kind) {
int n = 0;
if (kind == GAME_BASIC) n = 1;
else if (kind == GAME_3COLORS) n = 3;
else if (kind == GAME_DOUBLE) n = 4;

for (int i = 0; i < n; i++) {
if (p.targets[i].alive) {
led.lightColorLed(p.targets[i].row, p.targets[i].col, p.targets[i].color);
}
}
}

// ヒット判定(押した瞬間のみ検知&安全化)
bool checkHit(Player &p, GameKind kind, uint16_t changed, uint16_t input16) {
if (kind == GAME_BASIC) {
int bit = touchBit[p.targets[0].row][p.targets[0].col];
if ((changed & (1U << bit)) && (input16 & (1U << bit))) {
p.score += 1;
dfp.playCoin();
return true;
}
} else if (kind == GAME_DOUBLE) {
int bitA = touchBit[p.targets[0].row][p.targets[0].col];
int bitB = touchBit[p.targets[1].row][p.targets[1].col];
bool hitA = input16 & (1U << bitA);
bool hitB = input16 & (1U << bitB);
if (hitA && hitB) {
p.score += 2;
dfp.playCoin();
return true;
}
} else if (kind == GAME_3COLORS) {
if (p.stage < 3) {
int bit = touchBit[p.targets[p.stage].row][p.targets[p.stage].col];
if ((changed & (1U << bit)) && (input16 & (1U << bit))) {
p.targets[p.stage].alive = false;
p.score += 1;
dfp.playCoin();
p.stage++;
if (p.stage == 3) return true;
}
}
}
return false;
}

// MINIMUM & ACE セクションマッピング
void initSectionCells() {
for (int s = 0; s < 4; s++) sectionCellCount[s] = 0;

for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
int chip = ledChip[r][c];
int sec = -1;
if (chip == 4 || chip == 5) sec = 0;
else if (chip == 2 || chip == 3) sec = 1;
else if (chip == 6 || chip == 7) sec = 2;
else if (chip == 8 || chip == 9) sec = 3;

if (sec >= 0) {
int idx = sectionCellCount[sec]++;
sectionCells[sec][idx].row = r;
sectionCells[sec][idx].col = c;
}
}
}
}

// MINIMUM Crane用描画(左側)★修正
void drawMinimumCraneOnly() {
for (int s = 0; s < 2; s++) {
drawTriple(minimumLit_Crane[s]);
}
setLeft7Seg(
digitToSeg(minimumDigits_Crane[0]),
digitToSeg(minimumDigits_Crane[1]));
}

// MINIMUM Turtle用描画(右側)★修正
void drawMinimumTurtleOnly() {
for (int s = 0; s < 2; s++) {
drawTriple(minimumLit_Turtle[s]);
}
setRight7Seg(
digitToSeg(minimumDigits_Turtle[0]),
digitToSeg(minimumDigits_Turtle[1]));
}

// MINIMUM SINGLE レイアウト
void setupMinimumLayoutSingle() {
setupLayoutSingle_MA(10, minimumDigits_S, minimumLit_S);
}

// MINIMUM SINGLE 描写
void drawMinimumSingle() {
drawSingle_MA(minimumDigits_S, minimumLit_S, digitToSeg);
}

// MINIMUM SINGLE チェック
bool checkMinimumHitSingle(uint16_t changed) {
return checkHitSingle_MA(changed, minimumDigits_S, minimumLit_S);
}

// MINIMUM COOP レイアウト
void setupMinimumLayoutCrane() {
initSectionCells();
setupLayoutCoopSide_MA(10, 0, minimumDigits_Crane, minimumLit_Crane);
}

void setupMinimumLayoutTurtle() {
initSectionCells();
setupLayoutCoopSide_MA(10, 2, minimumDigits_Turtle, minimumLit_Turtle);
}

// MINIMUM COOP 描写
void drawMinimumCoop() {
drawCoop_MA(minimumDigits_Crane, minimumLit_Crane, digitToSeg,
minimumDigits_Turtle, minimumLit_Turtle, digitToSeg);
}

// MINIMUM COOP ヒット
bool checkMinimumHitCrane(uint16_t changed) {
return checkHitCoopSide_MA(changed, 0, minimumDigits_Crane, minimumLit_Crane);
}

bool checkMinimumHitTurtle(uint16_t changed) {
return checkHitCoopSide_MA(changed, 2, minimumDigits_Turtle, minimumLit_Turtle);
}

// UNLOCK 左右分離(判定・描画)
bool updateUnlockCrane(uint16_t leftBits) {
int ubitC = touchBit[unlockCell_Crane.row][unlockCell_Crane.col];
int gbitC = touchBit[gCell_Crane.row][gCell_Crane.col];

if (!unlockActive_Crane) {
if (leftBits & (1 << ubitC)) unlockActive_Crane = true;
} else {
if (leftBits & (1 << gbitC)) {
unlockActive_Crane = false;
return true;
}
if (!(leftBits & (1 << ubitC))) unlockActive_Crane = false;
}
return false;
}

bool updateUnlockTurtle(uint16_t rightBits) {
int ubitT = touchBit[unlockCell_Turtle.row][unlockCell_Turtle.col];
int gbitT = touchBit[gCell_Turtle.row][gCell_Turtle.col];

if (!unlockActive_Turtle) {
if (rightBits & (1 << ubitT)) unlockActive_Turtle = true;
} else {
if (rightBits & (1 << gbitT)) {
unlockActive_Turtle = false;
return true;
}
if (!(rightBits & (1 << ubitT))) unlockActive_Turtle = false;
}
return false;
}

void drawUnlockCraneOnly() {
if (!unlockActive_Crane) {
int r = unlockCell_Crane.row;
int c = unlockCell_Crane.col;
for (int i = 0; i < NUM_595; i++) led.data[i] |= unlockDummyData_Crane[i];
led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);
} else {
led.lightColorLed(gCell_Crane.row, gCell_Crane.col, 'G');
}
}

void drawUnlockTurtleOnly() {
if (!unlockActive_Turtle) {
int r = unlockCell_Turtle.row;
int c = unlockCell_Turtle.col;
for (int i = 0; i < NUM_595; i++) led.data[i] |= unlockDummyData_Turtle[i];
led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);
} else {
led.lightColorLed(gCell_Turtle.row, gCell_Turtle.col, 'G');
}
}

// UNLOCK 描画(SINGLE)
void drawUnlockSingle() {
for (int i = 0; i < NUM_595; i++) {
led.data[i] = unlockDummyData[i];
}

int r = unlockCell.row;
int c = unlockCell.col;

led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);

led.update();
}

// UNLOCK チェック(SINGLE)
bool checkUnlockSingle(uint16_t input16) {
int r = unlockCell.row;
int c = unlockCell.col;

bool jOn = led.data[unlockJ_chip[r][c]] & (1 << unlockJ_bit[r][c]);
bool qOn = led.data[unlockQ0_chip[r][c]] & (1 << unlockQ0_bit[r][c]);

int chip = ledChip[r][c];
bool gywOff = true;
if (led.data[chip] & (1 << ledBitG[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitY[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitW[r][c])) gywOff = false;

bool touchOn = input16 & (1 << touchBit[r][c]);

return (jOn && qOn && gywOff && touchOn);
}

// UNLOCK ハンドル(SINGLE)
void handleUnlockSingle(uint16_t input16) {
unsigned long t = millis() - stateStart;

int ubit = touchBit[unlockCell.row][unlockCell.col];
int gbit = touchBit[gCell.row][gCell.col];

if (!unlockActive) {
drawUnlockSingle();

if (input16 & (1 << ubit)) {
unlockActive = true;

led.setAllOff();
led.lightColorLed(gCell.row, gCell.col, 'G');
led.update();
}
} else {
led.setAllOff();
led.lightColorLed(gCell.row, gCell.col, 'G');
led.update();

if (input16 & (1 << gbit)) {
singleP.score += 2;
dfp.playCoin();

unlockActive = false;

if (isRandomMode) randomSwitchSingle();
else chooseUnlockSingle();
} else if (!(input16 & (1 << ubit))) {
unlockActive = false;
}
}

prevInput16 = input16;

if (t > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
}
}

// UNLOCK 描画(COOP CRANE)
void drawUnlockCrane() {
for (int i = 0; i < NUM_595; i++) {
led.data[i] = unlockDummyData_Crane[i];
}

int r = unlockCell_Crane.row;
int c = unlockCell_Crane.col;

led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);

led.update();
}

// UNLOCK ダミー生成(COOP CRANE)
void generateUnlockDummyCrane() {
for (int i = 0; i < NUM_595; i++) unlockDummyData_Crane[i] = 0;

int ur = unlockCell_Crane.row;
int uc = unlockCell_Crane.col;

int realJ_chip = unlockJ_chip[ur][uc];
int realJ_bit = unlockJ_bit[ur][uc];
int realQ_chip = unlockQ0_chip[ur][uc];
int realQ_bit = unlockQ0_bit[ur][uc];

for (int chip = 0; chip < NUM_595; chip++) {
if (!(chip == 0 || chip == 1 || chip == 2 || chip == 3 || chip == 4 || chip == 5)) continue;

for (int b = 0; b < 8; b++) {
bool isRealJ = (chip == realJ_chip && b == realJ_bit);
bool isRealQ = (chip == realQ_chip && b == realQ_bit);

bool isRealGYW = false;
if (chip == ledChip[ur][uc]) {
if (b == ledBitG[ur][uc]) isRealGYW = true;
if (b == ledBitY[ur][uc]) isRealGYW = true;
if (b == ledBitW[ur][uc]) isRealGYW = true;
}

if (isRealJ || isRealQ || isRealGYW) continue;

if (random(0, 4) == 0) {
unlockDummyData_Crane[chip] |= (1 << b);
}
}
}

for (int r = 0; r < 4; r++) {
for (int c = 0; c < 2; c++) {
if (r == ur && c == uc) continue;

int cj = unlockJ_chip[r][c];
int bj = unlockJ_bit[r][c];
int cq = unlockQ0_chip[r][c];
int bq = unlockQ0_bit[r][c];

bool jOnDummy = (unlockDummyData_Crane[cj] & (1 << bj));
bool jOnReal = (cj == realJ_chip);
bool jOn = jOnDummy || jOnReal;

bool qOnDummy = (unlockDummyData_Crane[cq] & (1 << bq));
bool qOnReal = (cq == realQ_chip);
bool qOn = qOnDummy || qOnReal;

int chip = ledChip[r][c];
bool gOn = (unlockDummyData_Crane[chip] & (1 << ledBitG[r][c]));
bool yOn = (unlockDummyData_Crane[chip] & (1 << ledBitY[r][c]));
bool wOn = (unlockDummyData_Crane[chip] & (1 << ledBitW[r][c]));

if (jOn && qOn && !gOn && !yOn && !wOn) {
int sel = random(0, 3);
if (sel == 0) unlockDummyData_Crane[chip] |= (1 << ledBitG[r][c]);
else if (sel == 1) unlockDummyData_Crane[chip] |= (1 << ledBitY[r][c]);
else unlockDummyData_Crane[chip] |= (1 << ledBitW[r][c]);
}
}
}
}

// UNLOCK チェック(COOP CRANE)
bool checkUnlockCrane(uint16_t leftBits) {
int r = unlockCell_Crane.row;
int c = unlockCell_Crane.col;

bool jOn = led.data[unlockJ_chip[r][c]] & (1 << unlockJ_bit[r][c]);
bool qOn = led.data[unlockQ0_chip[r][c]] & (1 << unlockQ0_bit[r][c]);

int chip = ledChip[r][c];
bool gywOff = true;
if (led.data[chip] & (1 << ledBitG[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitY[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitW[r][c])) gywOff = false;

bool touchOn = leftBits & (1 << touchBit[r][c]);

return (jOn && qOn && gywOff && touchOn);
}

// UNLOCK 描画(COOP TURTLE)
void drawUnlockTurtle() {
for (int i = 0; i < NUM_595; i++) {
led.data[i] = unlockDummyData_Turtle[i];
}

int r = unlockCell_Turtle.row;
int c = unlockCell_Turtle.col;

led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);

led.update();
}

// UNLOCK ダミー生成(COOP TURTLE)
void generateUnlockDummyTurtle() {
for (int i = 0; i < NUM_595; i++) unlockDummyData_Turtle[i] = 0;

int ur = unlockCell_Turtle.row;
int uc = unlockCell_Turtle.col;

int realJ_chip = unlockJ_chip[ur][uc];
int realJ_bit = unlockJ_bit[ur][uc];
int realQ_chip = unlockQ0_chip[ur][uc];
int realQ_bit = unlockQ0_bit[ur][uc];

for (int chip = 0; chip < NUM_595; chip++) {
if (!(chip == 6 || chip == 7 || chip == 8 || chip == 9 || chip == 10 || chip == 11)) continue;

for (int b = 0; b < 8; b++) {
bool isRealJ = (chip == realJ_chip && b == realJ_bit);
bool isRealQ = (chip == realQ_chip && b == realQ_bit);

bool isRealGYW = false;
if (chip == ledChip[ur][uc]) {
if (b == ledBitG[ur][uc]) isRealGYW = true;
if (b == ledBitY[ur][uc]) isRealGYW = true;
if (b == ledBitW[ur][uc]) isRealGYW = true;
}

if (isRealJ || isRealQ || isRealGYW) continue;

if (random(0, 4) == 0) {
unlockDummyData_Turtle[chip] |= (1 << b);
}
}
}

for (int r = 0; r < 4; r++) {
for (int c = 2; c < 4; c++) {
if (r == ur && c == uc) continue;

int cj = unlockJ_chip[r][c];
int bj = unlockJ_bit[r][c];
int cq = unlockQ0_chip[r][c];
int bq = unlockQ0_bit[r][c];

bool jOnDummy = (unlockDummyData_Turtle[cj] & (1 << bj));
bool jOnReal = (cj == realJ_chip);
bool jOn = jOnDummy || jOnReal;

bool qOnDummy = (unlockDummyData_Turtle[cq] & (1 << bq));
bool qOnReal = (cq == realQ_chip);
bool qOn = qOnDummy || qOnReal;

int chip = ledChip[r][c];
bool gOn = (unlockDummyData_Turtle[chip] & (1 << ledBitG[r][c]));
bool yOn = (unlockDummyData_Turtle[chip] & (1 << ledBitY[r][c]));
bool wOn = (unlockDummyData_Turtle[chip] & (1 << ledBitW[r][c]));

if (jOn && qOn && !gOn && !yOn && !wOn) {
int sel = random(0, 3);
if (sel == 0) unlockDummyData_Turtle[chip] |= (1 << ledBitG[r][c]);
else if (sel == 1) unlockDummyData_Turtle[chip] |= (1 << ledBitY[r][c]);
else unlockDummyData_Turtle[chip] |= (1 << ledBitW[r][c]);
}
}
}
}

// UNLOCK チェック(COOP TURTLE)
bool checkUnlockTurtle(uint16_t rightBits) {
int r = unlockCell_Turtle.row;
int c = unlockCell_Turtle.col;

bool jOn = led.data[unlockJ_chip[r][c]] & (1 << unlockJ_bit[r][c]);
bool qOn = led.data[unlockQ0_chip[r][c]] & (1 << unlockQ0_bit[r][c]);

int chip = ledChip[r][c];
bool gywOff = true;
if (led.data[chip] & (1 << ledBitG[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitY[r][c])) gywOff = false;
if (led.data[chip] & (1 << ledBitW[r][c])) gywOff = false;

bool touchOn = rightBits & (1 << touchBit[r][c]);

return (jOn && qOn && gywOff && touchOn);
}

// UNLOCK COOP ハンドラ
void handleCoopUnlock(uint16_t input16) {
unsigned long t = millis() - stateStart;

uint16_t leftBits = input16 & 0x00FF;
uint16_t rightBits = input16 & 0xFF00;

int ubitC = touchBit[unlockCell_Crane.row][unlockCell_Crane.col];
int gbitC = touchBit[gCell_Crane.row][gCell_Crane.col];

int ubitT = touchBit[unlockCell_Turtle.row][unlockCell_Turtle.col];
int gbitT = touchBit[gCell_Turtle.row][gCell_Turtle.col];

led.setAllOff();

if (!unlockActive_Crane) {
int r = unlockCell_Crane.row;
int c = unlockCell_Crane.col;

for (int i = 0; i < NUM_595; i++) {
led.data[i] |= unlockDummyData_Crane[i];
}

led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);
} else {
led.lightColorLed(gCell_Crane.row, gCell_Crane.col, 'G');
}

if (!unlockActive_Turtle) {
int r = unlockCell_Turtle.row;
int c = unlockCell_Turtle.col;

for (int i = 0; i < NUM_595; i++) {
led.data[i] |= unlockDummyData_Turtle[i];
}

led.data[unlockJ_chip[r][c]] |= (1 << unlockJ_bit[r][c]);
led.data[unlockQ0_chip[r][c]] |= (1 << unlockQ0_bit[r][c]);
} else {
led.lightColorLed(gCell_Turtle.row, gCell_Turtle.col, 'G');
}

led.update();

if (!unlockActive_Crane) {
if (leftBits & (1 << ubitC)) {
unlockActive_Crane = true;
}
} else {
if (leftBits & (1 << gbitC)) {
craneP.score += 2;
dfp.playCoin();
unlockActive_Crane = false;

if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else chooseUnlockCrane();
}
if (!(leftBits & (1 << ubitC))) {
unlockActive_Crane = false;
}
}

if (!unlockActive_Turtle) {
if (rightBits & (1 << ubitT)) {
unlockActive_Turtle = true;
}
} else {
if (rightBits & (1 << gbitT)) {
turtleP.score += 2;
dfp.playCoin();
unlockActive_Turtle = false;

if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else chooseUnlockTurtle();
}
if (!(rightBits & (1 << ubitT))) {
unlockActive_Turtle = false;
}
}

prevInput16_crane = leftBits;
prevInput16_turtle = rightBits;

if (t > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
}
}

// SINGLE ACE SETUP (M&A共通関数を利用)
void setupAceLayoutSingle() {
setupLayoutSingle_MA(12, aceDigits_S, aceLit_S);
}

// SINGLE ACE 描画 (M&A共通関数に aceToSeg を渡す)
void drawAceSingle() {
drawSingle_MA(aceDigits_S, aceLit_S, aceToSeg);
}

// SINGLE ACE ヒット判定 (M&A共通関数を利用)
bool checkAceHitSingle(uint16_t changed) {
return checkHitSingle_MA(changed, aceDigits_S, aceLit_S);
}

// COOP ACE CRANE SETUP (M&A共通関数を利用:プール12個、セクションオフセット0)
void setupAceLayoutCrane() {
initSectionCells();
setupLayoutCoopSide_MA(12, 0, aceDigits_Crane, aceLit_Crane);
}

// COOP ACE TURTLE SETUP (M&A共通関数を利用:プール12個、セクションオフセット2)
void setupAceLayoutTurtle() {
initSectionCells();
setupLayoutCoopSide_MA(12, 2, aceDigits_Turtle, aceLit_Turtle);
}

// COOP ACE 描画 (M&A共通関数に aceToSeg を渡す)
void drawAceCoop() {
drawCoop_MA(aceDigits_Crane, aceLit_Crane, aceToSeg,
aceDigits_Turtle, aceLit_Turtle, aceToSeg);
}

// COOP ACE CRANE ヒット判定 (M&A共通関数を利用)
bool checkAceHitCrane(uint16_t changed) {
return checkHitCoopSide_MA(changed, 0, aceDigits_Crane, aceLit_Crane);
}

// COOP ACE TURTLE ヒット判定 (M&A共通関数を利用)
bool checkAceHitTurtle(uint16_t changed) {
return checkHitCoopSide_MA(changed, 2, aceDigits_Turtle, aceLit_Turtle);
}

// ① レイアウト生成の共通化 (poolMax: MINIMUMは10, ACEは12)
void setupLayoutSingle_MA(int poolMax, int digits[], Cell lit[][3]) {
int pool[12];
for (int i = 0; i < poolMax; i++) pool[i] = i;
int poolSize = poolMax;

for (int s = 0; s < 4; s++) {
int k = random(0, poolSize);
digits[s] = pool[k];
pool[k] = pool[poolSize - 1];
poolSize--;
}

for (int s = 0; s < 4; s++) {
int n = sectionCellCount[s];
if (n < 3) continue;

int idx[16];
for (int i = 0; i < n; i++) idx[i] = i;
for (int i = 0; i < n - 1; i++) {
int j = random(i, n);
int tmp = idx[i];
idx[i] = idx[j];
idx[j] = tmp;
}

for (int k = 0; k < 3; k++) {
lit[s][k] = sectionCells[s][idx[k]];
}
}
}

// ② ヒット判定の共通化
bool checkHitSingle_MA(uint16_t changed, const int digits[], const Cell lit[][3]) {
int minSec = 0;
int minVal = digits[0];
for (int s = 1; s < 4; s++) {
if (digits[s] < minVal) {
minVal = digits[s];
minSec = s;
}
}

int n = sectionCellCount[minSec];
for (int i = 0; i < n; i++) {
int r = sectionCells[minSec][i].row;
int c = sectionCells[minSec][i].col;

bool isLit = false;
for (int k = 0; k < 3; k++) {
if (lit[minSec][k].row == r && lit[minSec][k].col == c) {
isLit = true;
break;
}
}
if (isLit) continue;

int bit = touchBit[r][c];
if (changed & (1 << bit)) return true;
}
return false;
}

// ③ 描画の共通化 (segFunc に digitToSeg または aceToSeg を渡す)
typedef byte (*SegConvFunc)(int);

void drawSingle_MA(int digits[], const Cell lit[][3], SegConvFunc segFunc) {
led.setAllOff();
for (int s = 0; s < 4; s++) {
drawTriple(lit[s]);
}
set4Seg(
segFunc(digits[0]),
segFunc(digits[1]),
segFunc(digits[2]),
segFunc(digits[3]));
led.update();
}

// ① COOP用レイアウト生成 (片側分:CraneまたはTurtle)
// sectionOffset: Craneなら 0, Turtleなら 2
void setupLayoutCoopSide_MA(int poolMax, int sectionOffset, int digits[], Cell lit[][3]) {
int pool[12];
for (int i = 0; i < poolMax; i++) pool[i] = i;
int poolSize = poolMax;

for (int s = 0; s < 2; s++) {
int k = random(0, poolSize);
digits[s] = pool[k];
pool[k] = pool[poolSize - 1];
poolSize--;
}

for (int s = 0; s < 2; s++) {
int globalSec = s + sectionOffset;
int n = sectionCellCount[globalSec];

int idx[16];
for (int i = 0; i < n; i++) idx[i] = i;
for (int i = 0; i < n - 1; i++) {
int j = random(i, n);
int tmp = idx[i];
idx[i] = idx[j];
idx[j] = tmp;
}

for (int k = 0; k < 3; k++) {
lit[s][k] = sectionCells[globalSec][idx[k]];
}
}
}

// ② COOP用ヒット判定 (片側分)
bool checkHitCoopSide_MA(uint16_t changed, int sectionOffset, const int digits[], const Cell lit[][3]) {
int minIdx = (digits[0] <= digits[1]) ? 0 : 1;
int globalSec = minIdx + sectionOffset;
int n = sectionCellCount[globalSec];

for (int i = 0; i < n; i++) {
int r = sectionCells[globalSec][i].row;
int c = sectionCells[globalSec][i].col;

bool isLit = false;
for (int k = 0; k < 3; k++) {
if (lit[minIdx][k].row == r && lit[minIdx][k].col == c) {
isLit = true;
break;
}
}
if (isLit) continue;

int bit = touchBit[r][c];
if (changed & (1 << bit)) return true;
}
return false;
}

// ③ COOP用全体描画 (左右両方をまとめて描画)
void drawCoop_MA(int craneDigits[], const Cell craneLit[][3], SegConvFunc craneSegFunc,
int turtleDigits[], const Cell turtleLit[][3], SegConvFunc turtleSegFunc) {
led.setAllOff();

for (int s = 0; s < 2; s++) {
drawTriple(craneLit[s]);
}
for (int s = 0; s < 2; s++) {
drawTriple(turtleLit[s]);
}

setLeft7Seg(
craneSegFunc(craneDigits[0]),
craneSegFunc(craneDigits[1]));
setRight7Seg(
turtleSegFunc(turtleDigits[0]),
turtleSegFunc(turtleDigits[1]));

led.update();
}

// ① SINGLE用 共通ハンドル
void handleSingle_MA(uint16_t input16, int poolMax, int digits[], Cell lit[][3], SegConvFunc segFunc) {
unsigned long t = millis() - stateStart;
uint16_t changed = (~prevInput16) & input16;

// 描画
drawSingle_MA(digits, lit, segFunc);

// ヒット判定
if (checkHitSingle_MA(changed, digits, lit)) {
singleP.score++;
dfp.playCoin();
if (isRandomMode) {
randomSwitchSingle();
} else {
setupLayoutSingle_MA(poolMax, digits, lit);
}
}

prevInput16 = input16;

// 時間切れ判定
if (t > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
}
}

// ② COOP用 共通ハンドル
void handleCoop_MA(uint16_t input16, int poolMax,
int craneDigits[], Cell craneLit[][3],
int turtleDigits[], Cell turtleLit[][3],
SegConvFunc segFunc, GameKind kindCraneVal, GameKind kindTurtleVal) { // ← ここを GameKind に修正
unsigned long t = millis() - stateStart;

uint16_t leftBits = input16 & 0x00FF;
uint16_t rightBits = input16 & 0xFF00;

uint16_t changedC = (~prevInput16_crane) & leftBits;
uint16_t changedT = (~prevInput16_turtle) & rightBits;

// 描画
drawCoop_MA(craneDigits, craneLit, segFunc, turtleDigits, turtleLit, segFunc);

// ヒット判定
bool hitC = checkHitCoopSide_MA(changedC, 0, craneDigits, craneLit);
bool hitT = checkHitCoopSide_MA(changedT, 2, turtleDigits, turtleLit);

if (hitC) {
craneP.score++;
dfp.playCoin();
if (isRandomMode) {
randomSwitchCoop(craneP, kindCraneVal, true);
} else {
initSectionCells();
setupLayoutCoopSide_MA(poolMax, 0, craneDigits, craneLit);
}
}

if (hitT) {
turtleP.score++;
dfp.playCoin();
if (isRandomMode) {
randomSwitchCoop(turtleP, kindTurtleVal, false);
} else {
initSectionCells();
setupLayoutCoopSide_MA(poolMax, 2, turtleDigits, turtleLit);
}
}

prevInput16_crane = leftBits;
prevInput16_turtle = rightBits;

// 時間切れ判定
if (t > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
}
}

// SINGLE用ラッパー
void handleMinimumSingle(uint16_t input16) {
handleSingle_MA(input16, 10, minimumDigits_S, minimumLit_S, digitToSeg);
}

void handleAceSingle(uint16_t input16) {
handleSingle_MA(input16, 12, aceDigits_S, aceLit_S, aceToSeg);
}

// COOP用ラッパー (引数に kindCrane と kindTurtle を渡す)
void handleMinimumCoop(uint16_t input16) {
handleCoop_MA(input16, 10, minimumDigits_Crane, minimumLit_Crane,
minimumDigits_Turtle, minimumLit_Turtle, digitToSeg, kindCrane, kindTurtle);
}

void handleAceCoop(uint16_t input16) {
handleCoop_MA(input16, 12, aceDigits_Crane, aceLit_Crane,
aceDigits_Turtle, aceLit_Turtle, aceToSeg, kindCrane, kindTurtle);
}

// PANIC SINGLE レイアウト
void setupPanicLayoutSingle() {
panicCell = randomCell();
panicSkipColor = COLOR3[random(0, 3)];

panicSegLastStep = millis();
}

// PANIC COOP レイアウト(CRANE)
void setupPanicLayoutCrane() {
do {
panicCell_Crane = randomCell();
} while (panicCell_Crane.col > 1);

panicSkipColor_Crane = COLOR3[random(0, 3)];
panicSegLastStep_Crane = 0;
}

// PANIC COOP レイアウト(TURTLE)
void setupPanicLayoutTurtle() {
do {
panicCell_Turtle = randomCell();
} while (panicCell_Turtle.col < 2);

panicSkipColor_Turtle = COLOR3[random(0, 3)];
panicSegLastStep_Turtle = 0;
}

// PANIC SINGLE 描画(400ms独立ランダム / メモリ最軽量版)
void drawPanicSingle() {
unsigned long now = millis();

bool shouldUpdateRandom = false;
if (now - panicSegLastStep >= 400) {
panicSegLastStep = now;
shouldUpdateRandom = true;
}

for (int i = 2; i <= 9; i++) {
if (shouldUpdateRandom) {
if (random(0, 2)) led.data[i] |= 0x10;
else led.data[i] &= ~0x10;
}
led.data[i] |= 0xEF;
}

for (int si = 0; si < 4; si++) {
int chip = segChips[si];
if (shouldUpdateRandom) {
led.data[chip] = random(0, 256);
}
}
forceKeepCellOneColorDark(panicCell, panicSkipColor);
led.update();
}

// Crane用 描画(左側:チップ 2〜5 / 7セグ 0,1)
void drawPanicCraneOnly() {
static uint8_t panicCraneSeg0;
static uint8_t panicCraneSeg1;
static uint8_t panicCraneMatrix[4];

unsigned long now = millis();

if (now - panicSegLastStep_Crane >= 400) {
panicSegLastStep_Crane = now;

for (int i = 0; i < 4; i++) {
uint8_t q4 = random(0, 2) ? 0x10 : 0x00;
panicCraneMatrix[i] = q4 | 0xEF;
}
panicCraneSeg0 = random(0, 256);
panicCraneSeg1 = random(0, 256);
}

for (int i = 0; i < 4; i++) {
led.data[2 + i] |= panicCraneMatrix[i];
}
led.data[segChips[0]] = panicCraneSeg0;
led.data[segChips[1]] = panicCraneSeg1;

int r = panicCell_Crane.row;
int c = panicCell_Crane.col;
if (c <= 1) {
int chip = ledChip[r][c];
if (panicSkipColor_Crane == 'G') led.data[chip] &= ~(1 << ledBitG[r][c]);
if (panicSkipColor_Crane == 'Y') led.data[chip] &= ~(1 << ledBitY[r][c]);
if (panicSkipColor_Crane == 'W') led.data[chip] &= ~(1 << ledBitW[r][c]);
}
}

// Turtle用 描画(右側:チップ 6〜9 / 7セグ 2,3)
void drawPanicTurtleOnly() {
static uint8_t panicTurtleSeg2;
static uint8_t panicTurtleSeg3;
static uint8_t panicTurtleMatrix[4];

unsigned long now = millis();

if (now - panicSegLastStep_Turtle >= 400) {
panicSegLastStep_Turtle = now;

for (int i = 0; i < 4; i++) {
uint8_t q4 = random(0, 2) ? 0x10 : 0x00;
panicTurtleMatrix[i] = q4 | 0xEF;
}
panicTurtleSeg2 = random(0, 256);
panicTurtleSeg3 = random(0, 256);
}

for (int i = 0; i < 4; i++) {
led.data[6 + i] |= panicTurtleMatrix[i];
}
led.data[segChips[2]] = panicTurtleSeg2;
led.data[segChips[3]] = panicTurtleSeg3;

int r = panicCell_Turtle.row;
int c = panicCell_Turtle.col;
if (c >= 2) {
int chip = ledChip[r][c];
if (panicSkipColor_Turtle == 'G') led.data[chip] &= ~(1 << ledBitG[r][c]);
if (panicSkipColor_Turtle == 'Y') led.data[chip] &= ~(1 << ledBitY[r][c]);
if (panicSkipColor_Turtle == 'W') led.data[chip] &= ~(1 << ledBitW[r][c]);
}
}

// PANIC SINGLE ヒット判定
bool checkPanicHitSingle(uint16_t changed) {
int bit = touchBit[panicCell.row][panicCell.col];
return (changed & (1 << bit));
}

// PANIC COOP ヒット判定(CRANE)
bool checkPanicHitCrane(uint16_t changedC) {
int bit = touchBit[panicCell_Crane.row][panicCell_Crane.col];
return (changedC & (1 << bit));
}

// PANIC COOP ヒット判定(TURTLE)
bool checkPanicHitTurtle(uint16_t changedT) {
int bit = touchBit[panicCell_Turtle.row][panicCell_Turtle.col];
return (changedT & (1 << bit));
}

// SINGLE ハンドラー(安全かつ美しく統一された完全版)
void handleSingle(uint16_t input16) {
if (millis() - stateStart > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
return;
}

// 各モードの専用ハンドルへ振り分ける(returnで途中で抜けず、最後にprevInput16を通す)
if (kindSingle == GAME_MINIMUM) {
handleMinimumSingle(input16);
} else if (kindSingle == GAME_ACE) {
handleAceSingle(input16);
} else if (kindSingle == GAME_UNLOCK) {
handleUnlockSingle(input16);
} else if (kindSingle == GAME_PANIC) {
// PANICはまだ専用ハンドルがないため、ここに直接書くか、必要なら後で関数化します
uint16_t changed = (~prevInput16) & input16;
drawPanicSingle();
if (checkPanicHitSingle(changed)) {
singleP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchSingle();
else setupPanicLayoutSingle();
}
} else {
// その他の通常モード(BASIC / 3COLORS / DOUBLEなど)
uint16_t changed = (~prevInput16) & input16;
led.setAllOff();
drawPlayer(singleP, kindSingle);
led.update();

if (checkHit(singleP, kindSingle, changed, input16)) {
if (isRandomMode) randomSwitchSingle();
else chooseTargets(singleP, kindSingle, 0, 3);
}
}

// ここで必ず1回だけ前回の入力を更新する
prevInput16 = input16;
}

// COOP ハンドラー(M&A共通化&最適化版)
void GameManager::handleCoop(uint16_t input16) {
if (millis() - stateStart > (unsigned long)gameTime * 1000UL) {
lcd.display();
state = RESULT;
stateStart = millis();
lcd.clear();
led.setAllOff();
led.update();
return;
}

uint16_t leftBits = input16 & 0x00FF;
uint16_t rightBits = input16 & 0xFF00;

uint16_t changedC = (~prevInput16_crane) & leftBits;
uint16_t changedT = (~prevInput16_turtle) & rightBits;

if (kindCrane == GAME_MINIMUM) {
if (checkMinimumHitCrane(changedC)) {
craneP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else setupMinimumLayoutCrane();
}
} else if (kindCrane == GAME_ACE) {
if (checkAceHitCrane(changedC)) {
craneP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else setupAceLayoutCrane();
}
} else if (kindCrane == GAME_UNLOCK) {
if (updateUnlockCrane(leftBits)) {
craneP.score += 2;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else chooseUnlockCrane();
}
} else if (kindCrane == GAME_PANIC) {
if (checkPanicHitCrane(changedC)) {
craneP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else setupPanicLayoutCrane();
}
} else { // BASIC / 3COLORS / DOUBLE
if (checkHit(craneP, kindCrane, changedC, leftBits)) {
if (isRandomMode) randomSwitchCoop(craneP, kindCrane, true);
else chooseTargets(craneP, kindCrane, 0, 1);
}
}

if (kindTurtle == GAME_MINIMUM) {
if (checkMinimumHitTurtle(changedT)) {
turtleP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else setupMinimumLayoutTurtle();
}
} else if (kindTurtle == GAME_ACE) {
if (checkAceHitTurtle(changedT)) {
turtleP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else setupAceLayoutTurtle();
}
} else if (kindTurtle == GAME_UNLOCK) {
if (updateUnlockTurtle(rightBits)) {
turtleP.score += 2;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else chooseUnlockTurtle();
}
} else if (kindTurtle == GAME_PANIC) {
if (checkPanicHitTurtle(changedT)) {
turtleP.score++;
dfp.playCoin();
if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else setupPanicLayoutTurtle();
}
} else {
if (checkHit(turtleP, kindTurtle, changedT, rightBits)) {
if (isRandomMode) randomSwitchCoop(turtleP, kindTurtle, false);
else chooseTargets(turtleP, kindTurtle, 2, 3);
}
}

led.setAllOff();

if (kindCrane == GAME_MINIMUM) {
drawMinimumCraneOnly();
} else if (kindCrane == GAME_ACE) {
drawTriple(aceLit_Crane[0]);
drawTriple(aceLit_Crane[1]);
setLeft7Seg(aceToSeg(aceDigits_Crane[0]), aceToSeg(aceDigits_Crane[1]));
} else if (kindCrane == GAME_UNLOCK) {
drawUnlockCraneOnly();
} else if (kindCrane == GAME_PANIC) {
drawPanicCraneOnly();
} else {
drawPlayer(craneP, kindCrane);
}

if (kindTurtle == GAME_MINIMUM) {
drawMinimumTurtleOnly();
} else if (kindTurtle == GAME_ACE) {
drawTriple(aceLit_Turtle[0]);
drawTriple(aceLit_Turtle[1]);
setRight7Seg(aceToSeg(aceDigits_Turtle[0]), aceToSeg(aceDigits_Turtle[1]));
} else if (kindTurtle == GAME_UNLOCK) {
drawUnlockTurtleOnly();
} else if (kindTurtle == GAME_PANIC) {
drawPanicTurtleOnly();
} else {
drawPlayer(turtleP, kindTurtle);
}

led.update();

prevInput16_crane = leftBits;
prevInput16_turtle = rightBits;
}

// WAIT
void handleWait(uint16_t input16) {
bool swA0 = !digitalRead(A0);
bool swA1 = !digitalRead(A1);
bool swA2 = !digitalRead(A2);
bool swA3 = !digitalRead(A3);

int modeBits = (swA3 << 2) | (swA2 << 1) | swA1;
bool modeValid = true;
GameKind dipKind = GAME_BASIC;

if (modeBits == 0b000) dipKind = GAME_BASIC;
else if (modeBits == 0b010) dipKind = GAME_MINIMUM;
else if (modeBits == 0b100) dipKind = GAME_3COLORS;
else if (modeBits == 0b110) dipKind = GAME_DOUBLE;
else if (modeBits == 0b111) dipKind = GAME_RANDOM;
else if (modeBits == 0b001) dipKind = GAME_UNLOCK;
else if (modeBits == 0b101) dipKind = GAME_ACE;
else if (modeBits == 0b011) dipKind = GAME_PANIC;
else modeValid = false;

bool coopMode = swA0;

bool nowD2 = !digitalRead(2);
bool nowD3 = !digitalRead(3);
bool nowStart = !digitalRead(4);

lcd.setCursor(0, 0);
lcd.print(coopMode ? F("C ") : F("S "));

if (!modeValid) lcd.print(F("VOID "));
else {
if (dipKind == GAME_BASIC) lcd.print(F("BASIC "));
else if (dipKind == GAME_MINIMUM) lcd.print(F("MINIMUM"));
else if (dipKind == GAME_3COLORS) lcd.print(F("3COLORS"));
else if (dipKind == GAME_DOUBLE) lcd.print(F("DOUBLE "));
else if (dipKind == GAME_RANDOM) lcd.print(F("RANDOM "));
else if (dipKind == GAME_UNLOCK) lcd.print(F("UNLOCK "));
else if (dipKind == GAME_ACE) lcd.print(F("ACE "));
else if (dipKind == GAME_PANIC) lcd.print(F("PANIC "));
}

lcd.setCursor(12, 0);
lcd.print(F("T"));
if (gameTime < 10) lcd.print(F("0"));
lcd.print(gameTime);

lcd.setCursor(0, 1);
for (int i = 0; i < 16; i++) {
lcd.print((input16 & (1 << i)) ? F("1") : F("0"));
}

if (nowD2 && nowD3) led.setAllOn();
else led.showLedTestPattern();
led.update();

int oldGameTime = gameTime;
if (!(nowD2 && nowD3)) {
if (!prevD2 && nowD2) gameTime++;
if (!prevD3 && nowD3 && gameTime > 1) gameTime--;
if (gameTime > 99) gameTime = 99;
if (gameTime < 1) gameTime = 1;
}
if (gameTime != oldGameTime) EEPROM.update(4, gameTime);

prevD2 = nowD2;
prevD3 = nowD3;

if (nowStart && !prevStart && modeValid) {
dfp.playMusic();

lcd.clear();
prevInput16 = input16;
state = COUNTDOWN;
stateStart = millis();

singleP.score = 0;
craneP.score = 0;
turtleP.score = 0;

isRandomMode = (modeBits == 0b111);
gameKind = dipKind;
lastCoopMode = coopMode;
lastGameKind = isRandomMode ? GAME_RANDOM : gameKind;

if (coopMode && isRandomMode) {
randomSwitchCoop(craneP, kindCrane, true);
randomSwitchCoop(turtleP, kindTurtle, false);
} else {
kindCrane = gameKind;
kindTurtle = gameKind;
}

prevInput16_crane = input16 & 0x00FF;
prevInput16_turtle = input16 & 0xFF00;

prevStart = nowStart;
return;
}

static unsigned long lastChange = 0;
static int index = 0;

if (millis() - lastChange > 200) {
lastChange = millis();
index++;
if (index >= testCount) index = 0;
}

led.show7Seg(pgm_read_byte(&testPatterns[index]));
prevStart = nowStart;
}

// COUNTDOWN
void handleCountdown() {
unsigned long t = millis() - stateStart;

led.setAllOff();
led.update();
led.clear7Seg();
randomSeed(micros() + analogRead(A0));

if (t < 1000) {
lcd.setCursor(0, 0);
lcd.print(F("Game Start in 3 "));
lcd.setCursor(0, 1);
lcd.print(F("( ^O^ )/ "));
return;
} else if (t < 2000) {
lcd.setCursor(0, 0);
lcd.print(F("Game Start in 2 "));
lcd.setCursor(0, 1);
lcd.print(F("( >▽< )b "));
return;
} else if (t < 3000) {
lcd.setCursor(0, 0);
lcd.print(F("Game Start in 1 "));
lcd.setCursor(0, 1);
lcd.print(F("( ^皿^ )ノ "));
return;
}

bool coopMode = !digitalRead(A0);

prevInput16 = in165.read16();
lastInput16_forRandom = prevInput16;

lcd.noDisplay();

if (!coopMode) {
state = PLAYING_SINGLE;

if (isRandomMode) {
randomSwitchSingle();
} else {
kindSingle = gameKind;
if (kindSingle == GAME_MINIMUM) {
setupMinimumLayoutSingle();
} else if (kindSingle == GAME_UNLOCK) {
chooseUnlockSingle();
} else if (kindSingle == GAME_ACE) {
setupAceLayoutSingle();
} else if (kindSingle == GAME_PANIC) {
setupPanicLayoutSingle();
} else {
chooseTargets(singleP, kindSingle, 0, 3);
}
}
stateStart = millis();
return;
}

state = PLAYING_COOP;

if (isRandomMode) {
randomSwitchCoop(craneP, kindCrane, true);
randomSwitchCoop(turtleP, kindTurtle, false);
} else {
kindCrane = gameKind;
kindTurtle = gameKind;

if (gameKind == GAME_MINIMUM) {
setupMinimumLayoutCrane();
setupMinimumLayoutTurtle();
} else if (gameKind == GAME_UNLOCK) {
chooseUnlockCrane();
chooseUnlockTurtle();
} else if (gameKind == GAME_ACE) {
setupAceLayoutCrane();
setupAceLayoutTurtle();
} else if (gameKind == GAME_PANIC) {
setupPanicLayoutCrane();
setupPanicLayoutTurtle();
} else {
chooseTargets(craneP, kindCrane, 0, 1);
chooseTargets(turtleP, kindTurtle, 2, 3);
}
}
stateStart = millis();
}

// ハイスコア参照
int *getHighScorePtr(bool coop, GameKind kind, int &addr) {
int addr_S, addr_C;
int *ptr_S, *ptr_C;

switch (kind) {
case GAME_BASIC:
addr_S = 0;
addr_C = 1;
ptr_S = &highScore_basic_S;
ptr_C = &highScore_basic_C;
break;
case GAME_3COLORS:
addr_S = 2;
addr_C = 3;
ptr_S = &highScore_3c_S;
ptr_C = &highScore_3c_C;
break;
case GAME_DOUBLE:
addr_S = 5;
addr_C = 6;
ptr_S = &highScore_double_S;
ptr_C = &highScore_double_C;
break;
case GAME_RANDOM:
addr_S = 7;
addr_C = 8;
ptr_S = &highScore_random_S;
ptr_C = &highScore_random_C;
break;
case GAME_MINIMUM:
addr_S = 9;
addr_C = 10;
ptr_S = &highScore_minimum_S;
ptr_C = &highScore_minimum_C;
break;
case GAME_UNLOCK:
addr_S = 11;
addr_C = 12;
ptr_S = &highScore_unlock_S;
ptr_C = &highScore_unlock_C;
break;
case GAME_ACE:
addr_S = 13;
addr_C = 14;
ptr_S = &highScore_ace_S;
ptr_C = &highScore_ace_C;
break;
case GAME_PANIC:
addr_S = 15;
addr_C = 16;
ptr_S = &highScore_panic_S;
ptr_C = &highScore_panic_C;
break;
default:
addr_S = 0;
addr_C = 1;
ptr_S = &highScore_basic_S;
ptr_C = &highScore_basic_C;
break;
}

if (coop) {
addr = addr_C;
return ptr_C;
} else {
addr = addr_S;
return ptr_S;
}
}

// RESULT
void handleResult() {
static bool initialized = false;

led.clear7Seg();

if (!initialized) {
initialized = true;
lcd.clear();

int sc = lastCoopMode ? (craneP.score + turtleP.score) : singleP.score;

lcd.setCursor(0, 0);
if (lastCoopMode) {
lcd.print(F("C:"));
if (craneP.score < 10) lcd.print(F("0"));
lcd.print(craneP.score);

lcd.print(F(" T:"));
if (turtleP.score < 10) lcd.print(F("0"));
lcd.print(turtleP.score);

lcd.print(F(" SC:"));
if (sc < 100) lcd.print(F("0"));
if (sc < 10) lcd.print(F("0"));
lcd.print(sc);
} else {
lcd.print(F("SC:"));
if (sc < 100) lcd.print(F("0"));
if (sc < 10) lcd.print(F("0"));
lcd.print(sc);
}

int addr;
int *pHS = getHighScorePtr(lastCoopMode, lastGameKind, addr);
int hs = *pHS;
bool updated = false;

if (sc > hs) {
*pHS = sc;
EEPROM.update(addr, sc);
hs = sc;
updated = true;
}

lcd.setCursor(0, 1);
lcd.print(lastCoopMode ? F("C ") : F("S "));

if (lastGameKind == GAME_BASIC) lcd.print(F("BASIC "));
else if (lastGameKind == GAME_3COLORS) lcd.print(F("3COLOR "));
else if (lastGameKind == GAME_DOUBLE) lcd.print(F("DOUBLE "));
else if (lastGameKind == GAME_RANDOM) lcd.print(F("RANDOM "));
else if (lastGameKind == GAME_MINIMUM) lcd.print(F("MINIMU "));
else if (lastGameKind == GAME_ACE) lcd.print(F("ACE "));
else if (lastGameKind == GAME_PANIC) lcd.print(F("PANIC "));
else lcd.print(F("UNLOCK "));

lcd.print(F("HS:"));
if (hs < 100) lcd.print(F("0"));
if (hs < 10) lcd.print(F("0"));
lcd.print(hs);
if (updated) lcd.print(F("!"));

prevD2_result = !digitalRead(2);
prevD3_result = !digitalRead(3);
}

bool nowStart = !digitalRead(4);
bool nowD2 = !digitalRead(2);
bool nowD3 = !digitalRead(3);

if ((nowD2 && !prevD2_result) || (nowD3 && !prevD3_result)) {
int sc = lastCoopMode ? (craneP.score + turtleP.score) : singleP.score;
int addr;
int *pHS = getHighScorePtr(lastCoopMode, lastGameKind, addr);
*pHS = sc;
EEPROM.update(addr, sc);

lcd.setCursor(0, 1);
lcd.print(lastCoopMode ? F("C ") : F("S "));

if (lastGameKind == GAME_BASIC) lcd.print(F("BASIC "));
else if (lastGameKind == GAME_3COLORS) lcd.print(F("3COLOR "));
else if (lastGameKind == GAME_DOUBLE) lcd.print(F("DOUBLE "));
else if (lastGameKind == GAME_RANDOM) lcd.print(F("RANDOM "));
else if (lastGameKind == GAME_MINIMUM) lcd.print(F("MINIMU "));
else if (lastGameKind == GAME_ACE) lcd.print(F("ACE "));
else if (lastGameKind == GAME_PANIC) lcd.print(F("PANIC "));
else lcd.print(F("UNLOCK "));

lcd.print(F("HS:"));
if (sc < 100) lcd.print(F("0"));
if (sc < 10) lcd.print(F("0"));
lcd.print(sc);
lcd.print(F("!"));
}

prevD2_result = nowD2;
prevD3_result = nowD3;

if (nowStart) {
initialized = false;
lcd.clear();
state = WAIT;
prevStart = true;
}
}
};

// グローバルインスタンス
LedDriver gLed;
DFPlayerManager gDFP;
Shift165 g165;
GameManager gGame(gLed, gDFP, g165);

// SETUP / LOOP
void setup() {
g165.begin();
gLed.begin();
gGame.begin();
}

void loop() {
uint16_t input16 = g165.read16();
gGame.update(input16);
}