ESP32-C6 专业游戏合集系统 - 打造你的掌上游戏机
分享作者:XIUYUAN
作者昵称:XIUYUAN
评测品牌:iCEasy
评测型号:无源蜂鸣器模块/5V低电平
发布时间:2025-10-15 10:55:30
1
视频链接
【ESP32-C6 专业游戏合集系统 - 打造你的掌上游戏机-哔哩哔哩】 https://b23.tv/tFx79hB
前言
在这个充满创意的项目中,我使用 ESP32-C6 微控制器打造了一个完整的掌上游戏机系统。这不仅仅是一个简单的游戏演示,而是一个功能完备的游戏平台,包含: 🎮 10 款经典游戏:从贪吃蛇到俄罗斯方块,应有尽有 🎵 独立配乐系统:每款游戏都有专属的背景音乐 🏆 双难度模式:简单和困难模式满足不同玩家需求 💾 最高分记录:自动保存每款游戏的最佳成绩 🎨 精美UI界面:流畅的菜单动画和用户体验
开源口碑分享内容

📖 项目简介

在这个充满创意的项目中,我使用 ESP32-C6 微控制器打造了一个完整的掌上游戏机系统。这不仅仅是一个简单的游戏演示,而是一个功能完备的游戏平台,包含:

       🎮 10 款经典游戏:从贪吃蛇到俄罗斯方块,应有尽有

       🎵 独立配乐系统:每款游戏都有专属的背景音乐

       🏆 双难度模式:简单和困难模式满足不同玩家需求

       💾 最高分记录:自动保存每款游戏的最佳成绩

       🎨 精美UI界面:流畅的菜单动画和用户体验


🔧 硬件配置

核心组件

| 组件 | 型号/规格 | 用途 |
|------|-----------|------|
| 主控 | ESP32-C6 | 系统核心处理器 |
| 显示屏 | SSD1306 OLED (128x64) | 游戏画面显示 |
| 输入 | 双轴摇杆 + 按键 | 游戏控制 |
| 音频 | 无源蜂鸣器 | 音效和背景音乐 |
| 通信 | I2C总线 | OLED屏幕通信 |

引脚连接

```cpp
#define BUZZER_PIN 8        // 蜂鸣器引脚
#define JOYSTICK_X 1        // 摇杆X轴 (ADC)
#define JOYSTICK_Y 2        // 摇杆Y轴 (ADC)
#define JOYSTICK_SW 3       // 摇杆按键
I2C: SDA=21, SCL=22         // OLED显示屏

🎮 游戏阵容

1. Snake (贪吃蛇)🐍

经典的贪吃蛇游戏,控制蛇吃食物变长,避免撞墙和自己。

       音乐:超级马里奥主题

       难度差异:困难模式速度更快

2. Runner (跑酷)🏃

横版跑酷游戏,按时跳跃避开障碍物。

       音乐:俄罗斯方块旋律

       特色:动态地面效果

3. Flappy Bird (飞扬小鸟)🐦

点击控制小鸟飞行,穿越管道间隙。

       音乐:塞尔达传说风格

       物理:真实的重力系统

4. Breakout (打砖块)🧱

使用挡板反弹小球击碎所有砖块。

       音乐:神庙逃亡风格

       特点:40块砖墙 (10x4)

5. Tetris (俄罗斯方块)🟦

经典的方块消除游戏,7种不同形状的方块。

       音乐:吃豆人旋律

       功能:完整的旋转和消行系统

6. Space Invaders (太空入侵者)👾

射击外星人保卫地球!

       音乐:太空入侵者原版音效

       敌人:3排x8列共24个外星人

7. Pong (乒乓)🏓

对战AI的经典乒乓球游戏。

       音乐:简约电子音

       AI:困难模式的AI更加聪明

8. Maze (迷宫)🌀

随机生成的迷宫探险游戏。

       音乐:迷宫探险风格

       特色:每次游戏地图都不同

9. Racing (赛车)🏎️

躲避道路上的其他车辆,尽可能跑得更远。

       音乐:赛车狂飙节奏

       效果:流动的道路边缘动画

10. Catcher (接星星)

移动篮子接住下落的星星,不能失误超过5次。

       音乐:轻快的上升音阶

       挑战:同时5颗星星下落


💡 核心技术特性

1. 状态机架构

       系统使用清晰的状态机管理不同界面:

```cpp
enum GameState {
  SPLASH_SCREEN,        // 启动画面
  MAIN_MENU,            // 主菜单
  DIFFICULTY_SELECT,    // 难度选择
  GAME_SNAKE,           // 游戏1-10
  // ... 其他游戏状态
  GAME_OVER_SCREEN,     // 游戏结束
  HIGH_SCORE_SCREEN,    // 高分榜
  SETTINGS_SCREEN       // 设置界面
};

2. 音乐系统

       每款游戏都配备独立的背景音乐,使用音符频率和时长数组:

```cpp
struct MusicNote {
  int freq;  // 频率 (Hz)
  int dur;   // 时长 (ms)
};

       音乐循环播放,不阻塞游戏主循环,保证流畅的游戏体验。

3. 数据持久化

       使用 ESP32 的 `Preferences` 库保存:

               ✅ 每款游戏的简单模式最高分 (10个)

               ✅ 每款游戏的困难模式最高分 (10个)

               ✅ 音效开关状态

```cpp
preferences.begin("gameBox", false);
preferences.putInt("hs0", score);  // 保存分数
preferences.putBool("sound", true); // 保存设置
```

4. 输入防抖处理

       实现了时间阈值的输入防抖,避免误操作:

```cpp
const int INPUT_DELAY = 180;  // 毫秒
if (now - lastInputTime > INPUT_DELAY) {
  // 处理输入
  lastInputTime = now;
}

5. 精美的UI设计

启动动画

       渐进式加载效果

       进度条显示

       音效配合

主菜单

       正弦波动的标题动画

       滚动的游戏列表

       实时显示最高分

游戏界面

        帧率优化的动画效果

       粒子效果和闪烁动画

       实时分数显示


🎯 开发亮点

1. 内存优化

对于ESP32-C6的内存管理至关重要:

       ✅ 使用 `byte` 类型存储网格数据 (Tetris, Maze)

       ✅ 固定大小的数组避免动态分配

       ✅ 复用显示缓冲区

2. 性能优化

       ✅ 不同游戏采用不同的更新频率

       ✅ 音乐播放采用非阻塞实现

       ✅ 碰撞检测使用早期退出优化

3. 代码结构

       ✅ 清晰的函数命名和模块划分

       ✅ 每个游戏独立的 `init``update` 函数

       ✅ 统一的游戏调度系统

```cpp
void initGame() {
  switch(currentState) {
    case GAME_SNAKE: initSnake(); break;
    // ...
  }
}

void updateGame() {
  switch(currentState) {
    case GAME_SNAKE: updateSnake(); break;
    // ...
  }
}
```

🚀 快速上手

环境准备

1. Arduino IDE(这里我使用的是Arduino IDE)PlatformIO

2. 安装依赖库:

   ```
   - Adafruit_GFX
   - Adafruit_SSD1306
   - Preferences (ESP32内置)
   ```

烧录步骤

1. 打开 Arduino IDE

2. 选择开发板:`ESP32C6 Dev Module`

3. 配置参数:

  - Flash Size: 4MB

  - Partition Scheme: Default

4. 连接ESP32-C6并上传代码


项目完整代码

/*
 * ╔═══════════════════════════════════════════════════════════════╗
 * ║   ESP32-C6 专业游戏合集系统 v2.0                                 ║
 * ║   10个完整游戏 + 双难度 + 独立配乐 + 最高分系统                   ║
 * ║   作者: XIU YUAN                                                 ║
 * ║   日期:2025-10-14                                               ║
 * ╚═══════════════════════════════════════════════════════════════╝
 */

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Preferences.h>

// ═══════════════════════════════════════════════════════════════
// 硬件配置
// ═══════════════════════════════════════════════════════════════
#define BUZZER_PIN 8
#define JOYSTICK_X 1
#define JOYSTICK_Y 2
#define JOYSTICK_SW 3
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
Preferences preferences;

// ═══════════════════════════════════════════════════════════════
// 音符频率定义
// ═══════════════════════════════════════════════════════════════
#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978
#define REST 0

// ═══════════════════════════════════════════════════════════════
// 游戏状态
// ═══════════════════════════════════════════════════════════════
enum GameState {
  SPLASH_SCREEN,
  MAIN_MENU,
  DIFFICULTY_SELECT,
  GAME_SNAKE,
  GAME_RUNNER,
  GAME_FLAPPY,
  GAME_BREAKOUT,
  GAME_TETRIS,
  GAME_INVADERS,
  GAME_PONG,
  GAME_MAZE,
  GAME_RACING,
  GAME_CATCHER,
  GAME_OVER_SCREEN,
  HIGH_SCORE_SCREEN,
  SETTINGS_SCREEN,
  PAUSE_SCREEN
};

GameState currentState = SPLASH_SCREEN;
GameState pausedFrom = MAIN_MENU;

// ═══════════════════════════════════════════════════════════════
// 全局变量
// ═══════════════════════════════════════════════════════════════
const int GAME_COUNT = 10;
int menuSelection = 0;
int difficultySelection = 0;
bool isDifficultMode = false;
bool soundEnabled = true;
int currentScore = 0;
int currentGameIndex = 0;

const char* gameNames[] = {
  "Snake", "Runner", "Flappy", "Breakout", "Tetris",
  "Invaders", "Pong", "Maze", "Racing", "Catcher"
};

int highScores[20]; // [0-9]简单, [10-19]困难

unsigned long lastInputTime = 0;
unsigned long musicTimer = 0;
const int INPUT_DELAY = 180;

// ═══════════════════════════════════════════════════════════════
// 音乐系统
// ═══════════════════════════════════════════════════════════════
struct MusicNote {
  int freq;
  int dur;
};

int musicIndex = 0;
MusicNote* currentMusic = nullptr;
int currentMusicLen = 0;

// 超级马里奥主题
MusicNote music1[] = {
  {NOTE_E5,150},{NOTE_E5,150},{REST,150},{NOTE_E5,150},{REST,150},
  {NOTE_C5,150},{NOTE_E5,150},{REST,150},{NOTE_G5,300},{REST,300}
};
int music1_len = 10;

// 俄罗斯方块
MusicNote music2[] = {
  {NOTE_E5,200},{NOTE_B4,100},{NOTE_C5,100},{NOTE_D5,200},{NOTE_C5,100},{NOTE_B4,100},
  {NOTE_A4,200},{NOTE_A4,100},{NOTE_C5,100},{NOTE_E5,200},{NOTE_D5,100},{NOTE_C5,100},
  {NOTE_B4,300},{NOTE_C5,100},{NOTE_D5,200},{NOTE_E5,200}
};
int music2_len = 16;

// 塞尔达
MusicNote music3[] = {
  {NOTE_A4,200},{NOTE_D5,400},{NOTE_F5,200},{NOTE_G5,150},{NOTE_A5,200},
  {NOTE_AS5,200},{NOTE_G5,300},{NOTE_F5,400}
};
int music3_len = 8;

// 神庙逃亡
MusicNote music4[] = {
  {NOTE_C5,150},{NOTE_D5,150},{NOTE_E5,150},{NOTE_G5,300},
  {NOTE_E5,150},{NOTE_D5,150},{NOTE_C5,300}
};
int music4_len = 7;

// 吃豆人
MusicNote music5[] = {
  {NOTE_B4,100},{NOTE_B5,100},{NOTE_FS5,100},{NOTE_DS5,100},
  {NOTE_B5,200},{NOTE_FS5,200},{NOTE_DS5,100},{NOTE_C5,100}
};
int music5_len = 8;

// 太空入侵者
MusicNote music6[] = {
  {NOTE_D4,100},{NOTE_D4,100},{NOTE_D4,100},{NOTE_D4,100},
  {NOTE_A3,100},{NOTE_A3,100},{NOTE_A3,100},{NOTE_A3,100}
};
int music6_len = 8;

// Pong简约音
MusicNote music7[] = {
  {NOTE_C5,200},{NOTE_G4,200},{NOTE_C5,200},{REST,200}
};
int music7_len = 4;

// 迷宫探险
MusicNote music8[] = {
  {NOTE_E4,200},{NOTE_G4,200},{NOTE_A4,200},{NOTE_C5,400},{REST,200}
};
int music8_len = 5;

// 赛车狂飙
MusicNote music9[] = {
  {NOTE_G5,100},{NOTE_E5,100},{NOTE_C5,100},{NOTE_G4,100},
  {NOTE_G5,100},{NOTE_E5,100},{NOTE_C5,100},{NOTE_G4,100}
};
int music9_len = 8;

// 接星星
MusicNote music10[] = {
  {NOTE_C5,150},{NOTE_D5,150},{NOTE_E5,150},{NOTE_F5,150},{NOTE_G5,150},{NOTE_A5,150}
};
int music10_len = 6;

void setMusic(int gameIdx) {
  musicIndex = 0;
  switch(gameIdx) {
    case 0: currentMusic = music1; currentMusicLen = music1_len; break;
    case 1: currentMusic = music2; currentMusicLen = music2_len; break;
    case 2: currentMusic = music3; currentMusicLen = music3_len; break;
    case 3: currentMusic = music4; currentMusicLen = music4_len; break;
    case 4: currentMusic = music5; currentMusicLen = music5_len; break;
    case 5: currentMusic = music6; currentMusicLen = music6_len; break;
    case 6: currentMusic = music7; currentMusicLen = music7_len; break;
    case 7: currentMusic = music8; currentMusicLen = music8_len; break;
    case 8: currentMusic = music9; currentMusicLen = music9_len; break;
    case 9: currentMusic = music10; currentMusicLen = music10_len; break;
  }
}

void playMusic() {
  if (!soundEnabled || currentMusic == nullptr) return;
  
  unsigned long now = millis();
  if (now - musicTimer >= currentMusic[musicIndex].dur) {
    ledcWriteTone(BUZZER_PIN, 0);
    musicIndex = (musicIndex + 1) % currentMusicLen;
    if (currentMusic[musicIndex].freq != REST) {
      ledcWriteTone(BUZZER_PIN, currentMusic[musicIndex].freq);
    }
    musicTimer = now;
  }
}

void stopMusic() {
  ledcWriteTone(BUZZER_PIN, 0);
  currentMusic = nullptr;
}

void playTone(int f, int d) {
  if (!soundEnabled) return;
  ledcWriteTone(BUZZER_PIN, f);
  delay(d);
  ledcWriteTone(BUZZER_PIN, 0);
}

void beepSelect() { playTone(NOTE_E5, 40); }
void beepStart() { playTone(NOTE_C5, 60); playTone(NOTE_E5, 60); playTone(NOTE_G5, 100); }
void beepScore() { playTone(NOTE_G5, 50); playTone(NOTE_A5, 50); }
void beepGameOver() { playTone(NOTE_E4,150); playTone(NOTE_C4,150); playTone(NOTE_A3,300); }

// ═══════════════════════════════════════════════════════════════
// 输入系统
// ═══════════════════════════════════════════════════════════════
int readJoystick() {
  int x = analogRead(JOYSTICK_X);
  int y = analogRead(JOYSTICK_Y);
  if (x > 3000) return 0; // 右
  if (x < 1000) return 2; // 左
  if (y > 3000) return 3; // 上
  if (y < 1000) return 1; // 下
  return -1;
}

bool btnPressed() {
  static bool last = false;
  bool now = digitalRead(JOYSTICK_SW) == LOW;
  if (now && !last) {
    last = true;
    return true;
  }
  if (!now) last = false;
  return false;
}

// ═══════════════════════════════════════════════════════════════
// 数据存储
// ═══════════════════════════════════════════════════════════════
void loadScores() {
  preferences.begin("gameBox", false);
  for (int i = 0; i < 20; i++) {
    char key[10];
    sprintf(key, "hs%d", i);
    highScores[i] = preferences.getInt(key, 0);
  }
  soundEnabled = preferences.getBool("sound", true);
  preferences.end();
}

void saveScore(int gameIdx, bool hard, int score) {
  int idx = gameIdx + (hard ? 10 : 0);
  if (score > highScores[idx]) {
    highScores[idx] = score;
    preferences.begin("gameBox", false);
    char key[10];
    sprintf(key, "hs%d", idx);
    preferences.putInt(key, score);
    preferences.end();
  }
}

void saveSound() {
  preferences.begin("gameBox", false);
  preferences.putBool("sound", soundEnabled);
  preferences.end();
}

int getHighScore(int gameIdx, bool hard) {
  return highScores[gameIdx + (hard ? 10 : 0)];
}

// ═══════════════════════════════════════════════════════════════
// UI工具
// ═══════════════════════════════════════════════════════════════
void drawBorder() {
  display.drawRect(0, 0, OLED_WIDTH, OLED_HEIGHT, SSD1306_WHITE);
}

void drawTitle(const char* txt, int y = 8) {
  display.setTextSize(2);
  int16_t x1, y1;
  uint16_t w, h;
  display.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);
  display.setCursor((OLED_WIDTH - w) / 2, y);
  display.print(txt);
}

void drawCenter(const char* txt, int y, int sz = 1) {
  display.setTextSize(sz);
  int16_t x1, y1;
  uint16_t w, h;
  display.getTextBounds(txt, 0, 0, &x1, &y1, &w, &h);
  display.setCursor((OLED_WIDTH - w) / 2, y);
  display.print(txt);
}

void drawProgress(int x, int y, int w, int h, int val, int max) {
  display.drawRect(x, y, w, h, SSD1306_WHITE);
  int fill = (val * (w - 2)) / max;
  if (fill > 0) display.fillRect(x + 1, y + 1, fill, h - 2, SSD1306_WHITE);
}

// ═══════════════════════════════════════════════════════════════
// 启动画面
// ═══════════════════════════════════════════════════════════════
void showSplash() {
  static int frame = 0;
  
  if (frame == 0) {
    display.clearDisplay();
    drawBorder();
    drawTitle("GAME", 12);
    drawTitle("BOX", 30);
    display.display();
    playTone(NOTE_C5, 80);
  } else if (frame == 8) {
    display.setTextSize(1);
    drawCenter("v2.0 Professional", 50, 1);
    display.display();
    playTone(NOTE_E5, 80);
  } else if (frame == 16) {
    drawCenter("Loading...", 58, 1);
    display.display();
  } else if (frame > 16 && frame < 36) {
    drawProgress(15, 56, 98, 6, frame - 16, 20);
    display.display();
  } else if (frame >= 36) {
    currentState = MAIN_MENU;
    playTone(NOTE_G5, 100);
    frame = -1;
  }
  
  frame++;
  delay(50);
}

// ═══════════════════════════════════════════════════════════════
// 主菜单
// ═══════════════════════════════════════════════════════════════
void handleMainMenu() {
  display.clearDisplay();
  drawBorder();
  
  // 标题动画
  display.setTextSize(1);
  int titleY = 3 + sin(millis() / 200.0);
  display.setCursor(8, titleY);
  display.print("< SELECT GAME >");
  
  // 游戏列表
  int start = max(0, menuSelection - 2);
  int end = min(GAME_COUNT, start + 5);
  
  for (int i = start; i < end; i++) {
    int y = 14 + (i - start) * 10;
    
    if (i == menuSelection) {
      display.fillRoundRect(2, y - 1, OLED_WIDTH - 4, 9, 2, SSD1306_WHITE);
      display.setTextColor(SSD1306_BLACK);
    } else {
      display.setTextColor(SSD1306_WHITE);
    }
    
    display.setCursor(6, y);
    display.print(i + 1);
    display.print(".");
    display.print(gameNames[i]);
    
    // 最高分
    int hs = getHighScore(i, false);
    display.setCursor(90, y);
    if (hs > 0) display.print(hs);
  }
  
  display.setTextColor(SSD1306_WHITE);
  display.drawLine(0, 57, OLED_WIDTH, 57, SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(2, 58);
  display.print("<Scores  Set>");
  
  display.display();
  
  // 输入
  int dir = readJoystick();
  unsigned long now = millis();
  
  if (now - lastInputTime > INPUT_DELAY) {
    if (dir == 3) {
      menuSelection = (menuSelection - 1 + GAME_COUNT) % GAME_COUNT;
      beepSelect();
      lastInputTime = now;
    } else if (dir == 1) {
      menuSelection = (menuSelection + 1) % GAME_COUNT;
      beepSelect();
      lastInputTime = now;
    } else if (dir == 2) {
      currentState = HIGH_SCORE_SCREEN;
      beepSelect();
      lastInputTime = now;
    } else if (dir == 0) {
      currentState = SETTINGS_SCREEN;
      beepSelect();
      lastInputTime = now;
    }
  }
  
  if (btnPressed()) {
    currentGameIndex = menuSelection;
    currentState = DIFFICULTY_SELECT;
    beepStart();
    delay(200);
  }
}

// ═══════════════════════════════════════════════════════════════
// 难度选择
// ═══════════════════════════════════════════════════════════════
void handleDifficulty() {
  display.clearDisplay();
  drawBorder();
  
  display.setTextSize(1);
  drawCenter(gameNames[currentGameIndex], 6, 1);
  display.drawLine(10, 15, OLED_WIDTH - 10, 15, SSD1306_WHITE);
  
  drawCenter("SELECT DIFFICULTY", 20, 1);
  
  // 简单
  if (difficultySelection == 0) {
    display.fillRoundRect(12, 30, 104, 11, 2, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.drawRoundRect(12, 30, 104, 11, 2, SSD1306_WHITE);
    display.setTextColor(SSD1306_WHITE);
  }
  drawCenter("EASY MODE", 32, 1);
  
  // 困难
  display.setTextColor(SSD1306_WHITE);
  if (difficultySelection == 1) {
    display.fillRoundRect(12, 43, 104, 11, 2, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.drawRoundRect(12, 43, 104, 11, 2, SSD1306_WHITE);
    display.setTextColor(SSD1306_WHITE);
  }
  drawCenter("HARD MODE", 45, 1);
  
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(20, 58);
  display.print("Press to start");
  
  display.display();
  
  // 输入
  int dir = readJoystick();
  if (millis() - lastInputTime > INPUT_DELAY) {
    if (dir == 3 || dir == 1) {
      difficultySelection = 1 - difficultySelection;
      beepSelect();
      lastInputTime = millis();
    }
  }
  
  if (btnPressed()) {
    isDifficultMode = (difficultySelection == 1);
    currentScore = 0;
    currentState = (GameState)(GAME_SNAKE + currentGameIndex);
    setMusic(currentGameIndex);
    
    // 初始化游戏
    initGame();
    
    beepStart();
    delay(300);
  }
}

// ═══════════════════════════════════════════════════════════════
// 游戏结束
// ═══════════════════════════════════════════════════════════════
void handleGameOver() {
  stopMusic();
  
  int hs = getHighScore(currentGameIndex, isDifficultMode);
  bool newRecord = (currentScore > hs);
  
  if (newRecord) {
    saveScore(currentGameIndex, isDifficultMode, currentScore);
  }
  
  display.clearDisplay();
  drawBorder();
  
  drawTitle("GAME", 5);
  drawTitle("OVER", 23);
  
  display.setTextSize(1);
  display.setCursor(10, 42);
  display.print("Score:");
  display.setCursor(70, 42);
  display.print(currentScore);
  
  display.setCursor(10, 50);
  display.print("Best:");
  display.setCursor(70, 50);
  display.print(newRecord ? currentScore : hs);
  
  if (newRecord) {
    drawCenter("NEW RECORD!", 58, 1);
  } else {
    drawCenter("Press to menu", 58, 1);
  }
  
  display.display();
  
  if (btnPressed()) {
    delay(300);
    currentState = MAIN_MENU;
  }
}

// ═══════════════════════════════════════════════════════════════
// 高分榜
// ═══════════════════════════════════════════════════════════════
void handleHighScores() {
  static int page = 0;
  
  display.clearDisplay();
  drawBorder();
  
  display.setTextSize(1);
  drawCenter("HIGH SCORES", 4, 1);
  display.drawLine(5, 13, OLED_WIDTH - 5, 13, SSD1306_WHITE);
  
  // 表头
  display.setCursor(5, 16);
  display.print("Game");
  display.setCursor(70, 16);
  display.print("Easy");
  display.setCursor(100, 16);
  display.print("Hard");
  
  // 数据
  int start = page * 4;
  for (int i = 0; i < 4 && (start + i) < GAME_COUNT; i++) {
    int idx = start + i;
    int y = 26 + i * 9;
    
    display.setCursor(5, y);
    display.print(gameNames[idx]);
    
    display.setCursor(70, y);
    int easy = getHighScore(idx, false);
    if (easy > 0) display.print(easy);
    else display.print("-");
    
    display.setCursor(100, y);
    int hard = getHighScore(idx, true);
    if (hard > 0) display.print(hard);
    else display.print("-");
  }
  
  display.drawLine(5, 56, OLED_WIDTH - 5, 56, SSD1306_WHITE);
  display.setCursor(5, 58);
  display.print("U/D:Pg ");
  display.print(page + 1);
  display.print("/3  B:Back");
  
  display.display();
  
  // 输入
  int dir = readJoystick();
  if (millis() - lastInputTime > INPUT_DELAY) {
    if (dir == 3 && page > 0) {
      page--;
      beepSelect();
      lastInputTime = millis();
    } else if (dir == 1 && page < 2) {
      page++;
      beepSelect();
      lastInputTime = millis();
    }
  }
  
  if (btnPressed()) {
    currentState = MAIN_MENU;
    page = 0;
    beepSelect();
    delay(200);
  }
}

// ═══════════════════════════════════════════════════════════════
// 设置
// ═══════════════════════════════════════════════════════════════
void handleSettings() {
  static int sel = 0;
  
  display.clearDisplay();
  drawBorder();
  
  drawTitle("SETTINGS", 6);
  display.drawLine(10, 24, OLED_WIDTH - 10, 24, SSD1306_WHITE);
  
  display.setTextSize(1);
  
  // 声音
  if (sel == 0) {
    display.fillRect(10, 28, 108, 10, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.setTextColor(SSD1306_WHITE);
  }
  display.setCursor(15, 30);
  display.print("Sound: ");
  display.print(soundEnabled ? "ON" : "OFF");
  
  // 清除分数
  display.setTextColor(SSD1306_WHITE);
  if (sel == 1) {
    display.fillRect(10, 40, 108, 10, SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  }
  display.setCursor(15, 42);
  display.print("Clear Scores");
  
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 56);
  display.print("U/D:Sel B:Menu");
  
  display.display();
  
  // 输入
  int dir = readJoystick();
  if (millis() - lastInputTime > INPUT_DELAY) {
    if (dir == 3 || dir == 1) {
      sel = 1 - sel;
      beepSelect();
      lastInputTime = millis();
    } else if (dir == 0 || dir == 2) {
      if (sel == 0) {
        soundEnabled = !soundEnabled;
        saveSound();
        beepSelect();
      }
      lastInputTime = millis();
    }
  }
  
  if (btnPressed()) {
    if (sel == 1) {
      // 清除分数确认
      display.clearDisplay();
      drawBorder();
      drawCenter("Clear all scores?", 25, 1);
      drawCenter("Press again", 38, 1);
      display.display();
      delay(1000);
      if (btnPressed()) {
        preferences.begin("gameBox", false);
        for (int i = 0; i < 20; i++) {
          char key[10];
          sprintf(key, "hs%d", i);
          preferences.putInt(key, 0);
          highScores[i] = 0;
        }
        preferences.end();
        beepSelect();
        delay(500);
      }
    } else {
      currentState = MAIN_MENU;
      beepSelect();
      delay(200);
    }
  }
}

// ═══════════════════════════════════════════════════════════════
// 游戏 1: 贪吃蛇
// ═══════════════════════════════════════════════════════════════
struct Point { int x, y; };
Point snake[100];
int snakeLen;
Point food;
int snakeDir;
bool snakeOver;
unsigned long snakeTime;

void initSnake() {
  snakeLen = 3;
  snakeDir = 0;
  snakeOver = false;
  currentScore = 0;
  snakeTime = millis();
  
  snake[0] = {60, 30};
  snake[1] = {57, 30};
  snake[2] = {54, 30};
  
  food.x = random(0, OLED_WIDTH / 3) * 3;
  food.y = random(0, OLED_HEIGHT / 3) * 3;
}

void updateSnake() {
  if (snakeOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  // 控制
  int dir = readJoystick();
  int speed = isDifficultMode ? 80 : 130;
  
  if (dir != -1 && millis() - lastInputTime > 100) {
    if ((dir == 0 && snakeDir != 2) || (dir == 2 && snakeDir != 0) ||
        (dir == 1 && snakeDir != 3) || (dir == 3 && snakeDir != 1)) {
      snakeDir = dir;
      lastInputTime = millis();
    }
  }
  
  // 移动
  if (millis() - snakeTime > speed) {
    for (int i = snakeLen - 1; i > 0; i--) {
      snake[i] = snake[i - 1];
    }
    
    switch (snakeDir) {
      case 0: snake[0].x += 3; break;
      case 1: snake[0].y += 3; break;
      case 2: snake[0].x -= 3; break;
      case 3: snake[0].y -= 3; break;
    }
    
    // 边界
    if (snake[0].x < 0 || snake[0].x >= OLED_WIDTH || snake[0].y < 0 || snake[0].y >= OLED_HEIGHT) {
      snakeOver = true;
      return;
    }
    
    // 自撞
    for (int i = 1; i < snakeLen; i++) {
      if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
        snakeOver = true;
        return;
      }
    }
    
    // 吃食物
    if (snake[0].x == food.x && snake[0].y == food.y) {
      snakeLen++;
      currentScore += 10;
      food.x = random(0, OLED_WIDTH / 3) * 3;
      food.y = random(0, OLED_HEIGHT / 3) * 3;
      beepScore();
    }
    
    snakeTime = millis();
  }
  
  // 绘制
  display.clearDisplay();
  
  // 蛇
  for (int i = 0; i < snakeLen; i++) {
    if (i == 0) {
      display.fillRect(snake[i].x, snake[i].y, 3, 3, SSD1306_WHITE);
    } else {
      display.drawRect(snake[i].x, snake[i].y, 3, 3, SSD1306_WHITE);
    }
  }
  
  // 食物闪烁
  if (millis() % 400 < 200) {
    display.fillRect(food.x, food.y, 3, 3, SSD1306_WHITE);
  }
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 2: 跑酷
// ═══════════════════════════════════════════════════════════════
int runY;
int runVel;
bool runJump;
int runObsX;
int runObsH;
bool runOver;
unsigned long runTime;

void initRunner() {
  runY = 50;
  runVel = 0;
  runJump = false;
  runObsX = OLED_WIDTH;
  runObsH = random(8, 16);
  runOver = false;
  currentScore = 0;
  runTime = millis();
}

void updateRunner() {
  if (runOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 25 : 40;
  if (millis() - runTime < delay_ms) return;
  runTime = millis();
  
  // 跳跃
  if (btnPressed() && !runJump && runY >= 50) {
    runVel = -8;
    runJump = true;
    playTone(NOTE_C5, 30);
  }
  
  // 物理
  runVel++;
  runY += runVel;
  
  if (runY >= 50) {
    runY = 50;
    runVel = 0;
    runJump = false;
  }
  
  if (runY < 10) {
    runY = 10;
    runVel = 1;
  }
  
  // 障碍物
  runObsX -= (isDifficultMode ? 3 : 2);
  if (runObsX < -10) {
    runObsX = OLED_WIDTH;
    runObsH = random(8, 16);
    currentScore += 10;
    beepScore();
  }
  
  // 碰撞
  if (runObsX >= 8 && runObsX <= 18 && runY + 8 >= 58 - runObsH) {
    runOver = true;
    return;
  }
  
  // 绘制
  display.clearDisplay();
  
  // 地面
  display.drawLine(0, 58, OLED_WIDTH, 58, SSD1306_WHITE);
  for (int i = 0; i < OLED_WIDTH; i += 8) {
    display.drawPixel((i + (millis() / 50) % 8), 59, SSD1306_WHITE);
  }
  
  // 玩家
  display.fillRect(10, runY, 8, 8, SSD1306_WHITE);
  
  // 障碍
  display.fillRect(runObsX, 58 - runObsH, 8, runObsH, SSD1306_WHITE);
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 3: Flappy Bird
// ═══════════════════════════════════════════════════════════════
int birdY;
int birdVel;
int pipeX;
int pipeGap;
int pipeY;
bool flappyOver;
unsigned long flappyTime;

void initFlappy() {
  birdY = 32;
  birdVel = 0;
  pipeX = OLED_WIDTH;
  pipeGap = isDifficultMode ? 20 : 25;
  pipeY = random(10, 40);
  flappyOver = false;
  currentScore = 0;
  flappyTime = millis();
}

void updateFlappy() {
  if (flappyOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 30 : 45;
  if (millis() - flappyTime < delay_ms) return;
  flappyTime = millis();
  
  // 飞行
  if (btnPressed()) {
    birdVel = -3;
    playTone(NOTE_E5, 25);
    delay(80);
  }
  
  // 物理
  birdVel++;
  birdY += birdVel;
  
  // 边界
  if (birdY < 0 || birdY > OLED_HEIGHT - 5) {
    flappyOver = true;
    return;
  }
  
  // 管道移动
  pipeX -= 2;
  if (pipeX < -10) {
    pipeX = OLED_WIDTH;
    pipeY = random(10, 40);
    currentScore += 10;
    beepScore();
  }
  
  // 碰撞
  if (pipeX >= 18 && pipeX <= 30) {
    if (birdY < pipeY || birdY > pipeY + pipeGap) {
      flappyOver = true;
      return;
    }
  }
  
  // 绘制
  display.clearDisplay();
  
  // 小鸟
  display.fillCircle(24, birdY, 3, SSD1306_WHITE);
  display.drawPixel(26, birdY - 1, SSD1306_WHITE);
  
  // 管道
  display.fillRect(pipeX, 0, 10, pipeY, SSD1306_WHITE);
  display.fillRect(pipeX, pipeY + pipeGap, 10, OLED_HEIGHT, SSD1306_WHITE);
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 4: 打砖块
// ═══════════════════════════════════════════════════════════════
int paddleX;
int ballX, ballY;
int ballDX, ballDY;
bool bricks[10][4];
bool breakoutOver;
unsigned long breakoutTime;

void initBreakout() {
  paddleX = OLED_WIDTH / 2 - 10;
  ballX = OLED_WIDTH / 2;
  ballY = 50;
  ballDX = 2;
  ballDY = -2;
  breakoutOver = false;
  currentScore = 0;
  breakoutTime = millis();
  
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 4; j++) {
      bricks[i][j] = true;
    }
  }
}

void updateBreakout() {
  if (breakoutOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 25 : 35;
  if (millis() - breakoutTime < delay_ms) return;
  breakoutTime = millis();
  
  // 挡板控制
  int x = analogRead(JOYSTICK_X);
  if (x > 3000) paddleX += (isDifficultMode ? 5 : 4);
  if (x < 1000) paddleX -= (isDifficultMode ? 5 : 4);
  paddleX = constrain(paddleX, 0, OLED_WIDTH - 20);
  
  // 球移动
  ballX += ballDX;
  ballY += ballDY;
  
  // 边界
  if (ballX <= 0 || ballX >= OLED_WIDTH) ballDX = -ballDX;
  if (ballY <= 0) ballDY = -ballDY;
  
  // 挡板反弹
  if (ballY >= OLED_HEIGHT - 6 && ballY <= OLED_HEIGHT - 3 &&
      ballX >= paddleX && ballX <= paddleX + 20) {
    ballDY = -ballDY;
    playTone(NOTE_C5, 25);
  }
  
  // 掉落
  if (ballY >= OLED_HEIGHT) {
    breakoutOver = true;
    return;
  }
  
  // 砖块碰撞
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 4; j++) {
      if (bricks[i][j]) {
        int bx = i * 13;
        int by = j * 6;
        if (ballX >= bx && ballX <= bx + 12 && ballY >= by && ballY <= by + 5) {
          bricks[i][j] = false;
          ballDY = -ballDY;
          currentScore += 10;
          beepScore();
        }
      }
    }
  }
  
  // 绘制
  display.clearDisplay();
  
  // 砖块
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 4; j++) {
      if (bricks[i][j]) {
        display.fillRect(i * 13, j * 6, 12, 5, SSD1306_WHITE);
      }
    }
  }
  
  // 挡板
  display.fillRect(paddleX, OLED_HEIGHT - 3, 20, 3, SSD1306_WHITE);
  
  // 球
  display.fillCircle(ballX, ballY, 2, SSD1306_WHITE);
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 56);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 5: 俄罗斯方块
// ═══════════════════════════════════════════════════════════════
const int GRID_W = 10;
const int GRID_H = 16;
const int BLOCK_SIZE = 4;

byte grid[GRID_H][GRID_W];
int tetromino[4][4];
int tetX, tetY;
int tetType;
bool tetrisOver;
unsigned long tetrisTime;
int tetrisSpeed;

const byte shapes[7][4][4] = {
  {{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}, // I
  {{1,1,0,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}}, // O
  {{0,1,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}, // S
  {{1,0,0,0},{1,1,0,0},{0,1,0,0},{0,0,0,0}}, // Z
  {{1,0,0,0},{1,1,0,0},{1,0,0,0},{0,0,0,0}}, // T
  {{1,0,0,0},{1,0,0,0},{1,1,0,0},{0,0,0,0}}, // L
  {{0,1,0,0},{0,1,0,0},{1,1,0,0},{0,0,0,0}}  // J
};

void newTetromino() {
  tetType = random(7);
  tetX = 3;
  tetY = 0;
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      tetromino[i][j] = shapes[tetType][i][j];
    }
  }
  
  // 检查游戏结束
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (tetromino[i][j] && grid[tetY + i][tetX + j]) {
        tetrisOver = true;
      }
    }
  }
}

bool checkCollision(int dx, int dy) {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (tetromino[i][j]) {
        int nx = tetX + j + dx;
        int ny = tetY + i + dy;
        if (nx < 0 || nx >= GRID_W || ny >= GRID_H) return true;
        if (ny >= 0 && grid[ny][nx]) return true;
      }
    }
  }
  return false;
}

void lockTetromino() {
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (tetromino[i][j] && tetY + i >= 0) {
        grid[tetY + i][tetX + j] = 1;
      }
    }
  }
  
  // 消行
  int lines = 0;
  for (int i = GRID_H - 1; i >= 0; i--) {
    bool full = true;
    for (int j = 0; j < GRID_W; j++) {
      if (!grid[i][j]) {
        full = false;
        break;
      }
    }
    if (full) {
      lines++;
      for (int k = i; k > 0; k--) {
        for (int j = 0; j < GRID_W; j++) {
          grid[k][j] = grid[k - 1][j];
        }
      }
      for (int j = 0; j < GRID_W; j++) {
        grid[0][j] = 0;
      }
      i++;
    }
  }
  
  if (lines > 0) {
    currentScore += lines * 100;
    beepScore();
  }
  
  newTetromino();
}

void rotateTetromino() {
  int temp[4][4];
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      temp[i][j] = tetromino[3 - j][i];
    }
  }
  
  // 临时保存
  int old[4][4];
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      old[i][j] = tetromino[i][j];
      tetromino[i][j] = temp[i][j];
    }
  }
  
  // 检查碰撞
  if (checkCollision(0, 0)) {
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        tetromino[i][j] = old[i][j];
      }
    }
  } else {
    playTone(NOTE_E5, 30);
  }
}

void initTetris() {
  for (int i = 0; i < GRID_H; i++) {
    for (int j = 0; j < GRID_W; j++) {
      grid[i][j] = 0;
    }
  }
  tetrisOver = false;
  currentScore = 0;
  tetrisSpeed = isDifficultMode ? 300 : 500;
  tetrisTime = millis();
  newTetromino();
}

void updateTetris() {
  if (tetrisOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  // 控制
  int dir = readJoystick();
  if (millis() - lastInputTime > 150) {
    if (dir == 2 && !checkCollision(-1, 0)) {
      tetX--;
      lastInputTime = millis();
    } else if (dir == 0 && !checkCollision(1, 0)) {
      tetX++;
      lastInputTime = millis();
    } else if (dir == 1) {
      tetrisSpeed = 50;
    } else if (dir == 3) {
      rotateTetromino();
      lastInputTime = millis();
    }
  }
  
  if (btnPressed()) {
    rotateTetromino();
    delay(150);
  }
  
  // 下落
  if (millis() - tetrisTime > tetrisSpeed) {
    if (!checkCollision(0, 1)) {
      tetY++;
    } else {
      lockTetromino();
    }
    tetrisTime = millis();
    tetrisSpeed = isDifficultMode ? 300 : 500;
  }
  
  // 绘制
  display.clearDisplay();
  
  // 网格边框
  display.drawRect(0, 0, GRID_W * 6 + 2, OLED_HEIGHT, SSD1306_WHITE);
  
  // 固定方块
  for (int i = 0; i < GRID_H; i++) {
    for (int j = 0; j < GRID_W; j++) {
      if (grid[i][j]) {
        display.fillRect(j * 6 + 1, i * 4, 5, 3, SSD1306_WHITE);
      }
    }
  }
  
  // 当前方块
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      if (tetromino[i][j] && tetY + i >= 0) {
        display.fillRect((tetX + j) * 6 + 1, (tetY + i) * 4, 5, 3, SSD1306_WHITE);
      }
    }
  }
  
  // 分数
  display.setTextSize(1);
  display.setCursor(65, 20);
  display.print("Score");
  display.setCursor(65, 30);
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 6: 太空入侵者
// ═══════════════════════════════════════════════════════════════
int shipX;
int bulletX, bulletY;
bool bulletActive;
int aliens[3][8];
int alienX, alienY;
int alienDX;
bool invadersOver;
unsigned long invadersTime;

void initInvaders() {
  shipX = OLED_WIDTH / 2;
  bulletActive = false;
  alienX = 0;
  alienY = 10;
  alienDX = 1;
  invadersOver = false;
  currentScore = 0;
  invadersTime = millis();
  
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 8; j++) {
      aliens[i][j] = 1;
    }
  }
}

void updateInvaders() {
  if (invadersOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 40 : 60;
  if (millis() - invadersTime < delay_ms) return;
  invadersTime = millis();
  
  // 飞船控制
  int x = analogRead(JOYSTICK_X);
  if (x > 3000) shipX += 3;
  if (x < 1000) shipX -= 3;
  shipX = constrain(shipX, 0, OLED_WIDTH - 8);
  
  // 发射子弹
  if (btnPressed() && !bulletActive) {
    bulletX = shipX + 4;
    bulletY = OLED_HEIGHT - 10;
    bulletActive = true;
    playTone(NOTE_E5, 30);
    delay(100);
  }
  
  // 子弹移动
  if (bulletActive) {
    bulletY -= 3;
    if (bulletY < 0) {
      bulletActive = false;
    }
    
    // 碰撞检测
    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 8; j++) {
        if (aliens[i][j]) {
          int ax = alienX + j * 12;
          int ay = alienY + i * 10;
          if (bulletX >= ax && bulletX <= ax + 8 && bulletY >= ay && bulletY <= ay + 6) {
            aliens[i][j] = 0;
            bulletActive = false;
            currentScore += 10;
            beepScore();
          }
        }
      }
    }
  }
  
  // 外星人移动
  alienX += alienDX;
  if (alienX <= 0 || alienX >= 32) {
    alienDX = -alienDX;
    alienY += 5;
  }
  
  // 外星人到达底部
  if (alienY >= OLED_HEIGHT - 20) {
    invadersOver = true;
    return;
  }
  
  // 检查胜利
  bool anyAlive = false;
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 8; j++) {
      if (aliens[i][j]) anyAlive = true;
    }
  }
  if (!anyAlive) {
    invadersOver = true;
    currentScore += 100;
    return;
  }
  
  // 绘制
  display.clearDisplay();
  
  // 外星人
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 8; j++) {
      if (aliens[i][j]) {
        int ax = alienX + j * 12;
        int ay = alienY + i * 10;
        display.fillRect(ax, ay, 8, 6, SSD1306_WHITE);
      }
    }
  }
  
  // 飞船
  display.fillRect(shipX, OLED_HEIGHT - 8, 8, 6, SSD1306_WHITE);
  display.drawPixel(shipX + 4, OLED_HEIGHT - 9, SSD1306_WHITE);
  
  // 子弹
  if (bulletActive) {
    display.fillRect(bulletX, bulletY, 2, 4, SSD1306_WHITE);
  }
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 7: Pong
// ═══════════════════════════════════════════════════════════════
int paddle1Y, paddle2Y;
int pongBallX, pongBallY;
int pongDX, pongDY;
int score1, score2;
bool pongOver;
unsigned long pongTime;

void initPong() {
  paddle1Y = OLED_HEIGHT / 2 - 8;
  paddle2Y = OLED_HEIGHT / 2 - 8;
  pongBallX = OLED_WIDTH / 2;
  pongBallY = OLED_HEIGHT / 2;
  pongDX = 2;
  pongDY = 1;
  score1 = 0;
  score2 = 0;
  pongOver = false;
  currentScore = 0;
  pongTime = millis();
}

void updatePong() {
  if (pongOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 25 : 35;
  if (millis() - pongTime < delay_ms) return;
  pongTime = millis();
  
  // 玩家控制
  int y = analogRead(JOYSTICK_Y);
  if (y > 3000) paddle1Y += 3;
  if (y < 1000) paddle1Y -= 3;
  paddle1Y = constrain(paddle1Y, 0, OLED_HEIGHT - 16);
  
  // AI控制
  if (pongBallY < paddle2Y + 8) paddle2Y -= (isDifficultMode ? 2 : 1);
  if (pongBallY > paddle2Y + 8) paddle2Y += (isDifficultMode ? 2 : 1);
  paddle2Y = constrain(paddle2Y, 0, OLED_HEIGHT - 16);
  
  // 球移动
  pongBallX += pongDX;
  pongBallY += pongDY;
  
  // 上下边界
  if (pongBallY <= 0 || pongBallY >= OLED_HEIGHT) {
    pongDY = -pongDY;
    playTone(NOTE_C5, 30);
  }
  
  // 左挡板
  if (pongBallX <= 5 && pongBallY >= paddle1Y && pongBallY <= paddle1Y + 16) {
    pongDX = -pongDX;
    playTone(NOTE_E5, 30);
  }
  
  // 右挡板
  if (pongBallX >= OLED_WIDTH - 5 && pongBallY >= paddle2Y && pongBallY <= paddle2Y + 16) {
    pongDX = -pongDX;
    playTone(NOTE_E5, 30);
  }
  
  // 得分
  if (pongBallX < 0) {
    score2++;
    pongBallX = OLED_WIDTH / 2;
    pongBallY = OLED_HEIGHT / 2;
    pongDX = 2;
  }
  
  if (pongBallX > OLED_WIDTH) {
    score1++;
    currentScore += 10;
    pongBallX = OLED_WIDTH / 2;
    pongBallY = OLED_HEIGHT / 2;
    pongDX = -2;
    beepScore();
  }
  
  // 游戏结束
  if (score2 >= 5) {
    pongOver = true;
    return;
  }
  
  // 绘制
  display.clearDisplay();
  
  // 中线
  for (int i = 0; i < OLED_HEIGHT; i += 4) {
    display.drawPixel(OLED_WIDTH / 2, i, SSD1306_WHITE);
  }
  
  // 挡板
  display.fillRect(2, paddle1Y, 3, 16, SSD1306_WHITE);
  display.fillRect(OLED_WIDTH - 5, paddle2Y, 3, 16, SSD1306_WHITE);
  
  // 球
  display.fillCircle(pongBallX, pongBallY, 2, SSD1306_WHITE);
  
  // 分数
  display.setTextSize(1);
  display.setCursor(OLED_WIDTH / 2 - 20, 2);
  display.print(score1);
  display.setCursor(OLED_WIDTH / 2 + 15, 2);
  display.print(score2);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 8: 迷宫
// ═══════════════════════════════════════════════════════════════
const int MAZE_W = 21;
const int MAZE_H = 16;
byte maze[MAZE_H][MAZE_W];
int playerMX, playerMY;
int exitMX, exitMY;
bool mazeOver;
unsigned long mazeTime;

void generateMaze() {
  // 简单迷宫生成
  for (int i = 0; i < MAZE_H; i++) {
    for (int j = 0; j < MAZE_W; j++) {
      if (i == 0 || i == MAZE_H - 1 || j == 0 || j == MAZE_W - 1) {
        maze[i][j] = 1;
      } else if (i % 2 == 0 && j % 2 == 0) {
        maze[i][j] = random(2);
      } else {
        maze[i][j] = random(3) == 0 ? 1 : 0;
      }
    }
  }
  
  // 确保起点和终点可达
  playerMX = 1;
  playerMY = 1;
  maze[1][1] = 0;
  
  exitMX = MAZE_W - 2;
  exitMY = MAZE_H - 2;
  maze[exitMY][exitMX] = 0;
}

void initMaze() {
  generateMaze();
  mazeOver = false;
  currentScore = 0;
  mazeTime = millis();
}

void updateMaze() {
  if (mazeOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  // 控制
  int dir = readJoystick();
  if (millis() - lastInputTime > 150) {
    int nx = playerMX;
    int ny = playerMY;
    
    if (dir == 0) nx++;
    if (dir == 2) nx--;
    if (dir == 1) ny++;
    if (dir == 3) ny--;
    
    if (nx >= 0 && nx < MAZE_W && ny >= 0 && ny < MAZE_H && maze[ny][nx] == 0) {
      playerMX = nx;
      playerMY = ny;
      currentScore++;
      playTone(NOTE_C5, 20);
      lastInputTime = millis();
    }
  }
  
  // 到达终点
  if (playerMX == exitMX && playerMY == exitMY) {
    mazeOver = true;
    currentScore += 500;
    beepScore();
    return;
  }
  
  // 绘制
  display.clearDisplay();
  
  // 迷宫
  for (int i = 0; i < MAZE_H; i++) {
    for (int j = 0; j < MAZE_W; j++) {
      if (maze[i][j]) {
        display.drawPixel(j * 6, i * 4, SSD1306_WHITE);
      }
    }
  }
  
  // 玩家
  display.fillRect(playerMX * 6 - 1, playerMY * 4 - 1, 3, 3, SSD1306_WHITE);
  
  // 终点闪烁
  if (millis() % 500 < 250) {
    display.fillRect(exitMX * 6 - 1, exitMY * 4 - 1, 3, 3, SSD1306_WHITE);
  }
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("M:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 9: 赛车
// ═══════════════════════════════════════════════════════════════
int carX;
int road[4];
int roadY[4];
bool racingOver;
unsigned long racingTime;

void initRacing() {
  carX = OLED_WIDTH / 2 - 4;
  for (int i = 0; i < 4; i++) {
    road[i] = random(20, OLED_WIDTH - 28);
    roadY[i] = -i * 20;
  }
  racingOver = false;
  currentScore = 0;
  racingTime = millis();
}

void updateRacing() {
  if (racingOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 30 : 45;
  if (millis() - racingTime < delay_ms) return;
  racingTime = millis();
  
  // 控制
  int x = analogRead(JOYSTICK_X);
  if (x > 3000) carX += 3;
  if (x < 1000) carX -= 3;
  carX = constrain(carX, 10, OLED_WIDTH - 18);
  
  // 道路移动
  for (int i = 0; i < 4; i++) {
    roadY[i] += (isDifficultMode ? 3 : 2);
    if (roadY[i] > OLED_HEIGHT) {
      roadY[i] = -16;
      road[i] = random(20, OLED_WIDTH - 28);
      currentScore += 10;
      if (currentScore % 50 == 0) beepScore();
    }
    
    // 碰撞
    if (roadY[i] >= OLED_HEIGHT - 20 && roadY[i] <= OLED_HEIGHT - 5) {
      if (carX >= road[i] - 8 && carX <= road[i] + 8) {
        racingOver = true;
        return;
      }
    }
  }
  
  // 绘制
  display.clearDisplay();
  
  // 道路边缘
  for (int i = 0; i < OLED_HEIGHT; i += 8) {
    display.drawPixel(5, i + (millis() / 50) % 8, SSD1306_WHITE);
    display.drawPixel(OLED_WIDTH - 6, i + (millis() / 50) % 8, SSD1306_WHITE);
  }
  
  // 障碍车
  for (int i = 0; i < 4; i++) {
    display.fillRect(road[i], roadY[i], 8, 12, SSD1306_WHITE);
  }
  
  // 玩家车
  display.fillRect(carX, OLED_HEIGHT - 12, 8, 12, SSD1306_WHITE);
  
  // 分数
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏 10: 接星星
// ═══════════════════════════════════════════════════════════════
int basketX;
int stars[5][2]; // x, y
bool catcherOver;
unsigned long catcherTime;
int missCount;

void initCatcher() {
  basketX = OLED_WIDTH / 2 - 10;
  for (int i = 0; i < 5; i++) {
    stars[i][0] = random(5, OLED_WIDTH - 5);
    stars[i][1] = -i * 15 - random(20);
  }
  catcherOver = false;
  currentScore = 0;
  missCount = 0;
  catcherTime = millis();
}

void updateCatcher() {
  if (catcherOver) {
    currentState = GAME_OVER_SCREEN;
    beepGameOver();
    return;
  }
  
  playMusic();
  
  int delay_ms = isDifficultMode ? 30 : 45;
  if (millis() - catcherTime < delay_ms) return;
  catcherTime = millis();
  
  // 控制
  int x = analogRead(JOYSTICK_X);
  if (x > 3000) basketX += 4;
  if (x < 1000) basketX -= 4;
  basketX = constrain(basketX, 0, OLED_WIDTH - 20);
  
  // 星星下落
  for (int i = 0; i < 5; i++) {
    stars[i][1] += (isDifficultMode ? 2 : 1);
    
    if (stars[i][1] >= OLED_HEIGHT) {
      missCount++;
      stars[i][0] = random(5, OLED_WIDTH - 5);
      stars[i][1] = -10;
      
      if (missCount >= 5) {
        catcherOver = true;
        return;
      }
    }
    
    // 接住星星
    if (stars[i][1] >= OLED_HEIGHT - 8 && stars[i][1] <= OLED_HEIGHT - 3) {
      if (stars[i][0] >= basketX && stars[i][0] <= basketX + 20) {
        currentScore += 10;
        stars[i][0] = random(5, OLED_WIDTH - 5);
        stars[i][1] = -10;
        beepScore();
      }
    }
  }
  
  // 绘制
  display.clearDisplay();
  
  // 篮子
  display.drawRect(basketX, OLED_HEIGHT - 5, 20, 5, SSD1306_WHITE);
  display.fillRect(basketX + 1, OLED_HEIGHT - 4, 18, 1, SSD1306_WHITE);
  
  // 星星
  for (int i = 0; i < 5; i++) {
    int sx = stars[i][0];
    int sy = stars[i][1];
    display.drawPixel(sx, sy, SSD1306_WHITE);
    display.drawPixel(sx - 1, sy, SSD1306_WHITE);
    display.drawPixel(sx + 1, sy, SSD1306_WHITE);
    display.drawPixel(sx, sy - 1, SSD1306_WHITE);
    display.drawPixel(sx, sy + 1, SSD1306_WHITE);
  }
  
  // 分数和失误
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("S:");
  display.print(currentScore);
  display.setCursor(OLED_WIDTH - 30, 0);
  display.print("X:");
  display.print(missCount);
  
  display.display();
}

// ═══════════════════════════════════════════════════════════════
// 游戏调度
// ═══════════════════════════════════════════════════════════════
void initGame() {
  switch(currentState) {
    case GAME_SNAKE: initSnake(); break;
    case GAME_RUNNER: initRunner(); break;
    case GAME_FLAPPY: initFlappy(); break;
    case GAME_BREAKOUT: initBreakout(); break;
    case GAME_TETRIS: initTetris(); break;
    case GAME_INVADERS: initInvaders(); break;
    case GAME_PONG: initPong(); break;
    case GAME_MAZE: initMaze(); break;
    case GAME_RACING: initRacing(); break;
    case GAME_CATCHER: initCatcher(); break;
  }
}

void updateGame() {
  switch(currentState) {
    case GAME_SNAKE: updateSnake(); break;
    case GAME_RUNNER: updateRunner(); break;
    case GAME_FLAPPY: updateFlappy(); break;
    case GAME_BREAKOUT: updateBreakout(); break;
    case GAME_TETRIS: updateTetris(); break;
    case GAME_INVADERS: updateInvaders(); break;
    case GAME_PONG: updatePong(); break;
    case GAME_MAZE: updateMaze(); break;
    case GAME_RACING: updateRacing(); break;
    case GAME_CATCHER: updateCatcher(); break;
  }
}

// ═══════════════════════════════════════════════════════════════
// 主程序
// ═══════════════════════════════════════════════════════════════
void setup() {
  Serial.begin(115200);
  Serial.println("\n╔═══════════════════════════════════╗");
  Serial.println("║  ESP32-C6 Game Box v2.0          ║");
  Serial.println("║  Professional Edition            ║");
  Serial.println("╚═══════════════════════════════════╝\n");
  
  // 初始化OLED
  Wire.begin(21, 22);
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println("❌ OLED initialization failed!");
    while (1);
  }
  Serial.println("✓ OLED initialized");
  
  // 初始化蜂鸣器
  ledcAttach(BUZZER_PIN, 2000, 8);
  Serial.println("✓ Buzzer initialized");
  
  // 初始化摇杆
  pinMode(JOYSTICK_SW, INPUT_PULLUP);
  analogReadResolution(12);
  Serial.println("✓ Joystick initialized");
  
  // 加载数据
  loadScores();
  Serial.println("✓ Scores loaded");
  
  display.clearDisplay();
  display.display();
  
  randomSeed(analogRead(0));
  
  Serial.println("\n🎮 System ready! Enjoy gaming!\n");
}

void loop() {
  switch(currentState) {
    case SPLASH_SCREEN:
      showSplash();
      break;
    case MAIN_MENU:
      handleMainMenu();
      break;
    case DIFFICULTY_SELECT:
      handleDifficulty();
      break;
    case GAME_OVER_SCREEN:
      handleGameOver();
      break;
    case HIGH_SCORE_SCREEN:
      handleHighScores();
      break;
    case SETTINGS_SCREEN:
      handleSettings();
      break;
    default:
      updateGame();
      break;
  }
  
  delay(10);
}

🎨 自定义和扩展

添加新游戏

       1.`GameState` 枚举中添加新状态

       2. 创建 `initYourGame()``updateYourGame()` 函数

       3.`initGame()``updateGame()` 中添加对应的 case

       4. 设计专属的背景音乐

调整难度

       修改游戏中的 `isDifficultMode` 判断:

```cpp
int speed = isDifficultMode ? 80 : 130;  // 速度调整
int pipeGap = isDifficultMode ? 20 : 25; // 间隙调整

更换音乐

       定义新的音符数组:

```cpp
MusicNote myMusic[] = {
  {NOTE_C5, 200}, {NOTE_E5, 200}, 
  {NOTE_G5, 400}
};
int myMusic_len = 3;

🐛 已知问题与解决方案

问题1: OLED显示模糊

       解决:检查I2C地址 (0x3C或0x3D),调整对比度

问题2: 摇杆漂移

       解决:增加死区判断或校准中心值

```cpp
if (abs(x - 2048) < 200) x = 2048; // 死区
```

问题3: 音乐卡顿

       解决:降低游戏更新频率或简化音符数组


🌟 未来改进计划

       添加双人对战模式

       实现蓝牙手柄支持

       增加彩色OLED支持 (SSD1351)

       添加关卡编辑器

       实现云端排行榜 (WiFi)

       加入震动反馈

       支持自定义皮肤


🎓 适用人群

       ✅ Arduino/ESP32 初学者

       ✅ 游戏开发爱好者

       ✅ 创客和硬件DIY爱好者

       ✅ 嵌入式系统学习者

       ✅ 复古游戏收藏家


💬 总结

这个 ESP32-C6 游戏合集系统展示了微控制器的强大潜力。通过合理的架构设计和优化,我们在小小的芯片上实现了10款完整的游戏,每款都有独特的玩法和音乐。

这个项目不仅是一个有趣的玩具,更是学习嵌入式开发、游戏编程和系统设计的绝佳实践。无论你是想学习技术,还是单纯享受复古游戏的乐趣,这都是一个值得尝试的项目。


🔗 相关资源

ESP32-C6 文档: [Espressif官网](https://www.espressif.com/en/products/socs/esp32-c6)

SSD1306 库文档: [Adafruit GFX库](https://github.com/adafruit/Adafruit-GFX-Library)

音符频率表: [Arduino音符定义](https://github.com/robsoncouto/arduino-songs)


> 💡 提示: 如果这个项目对你有帮助,欢迎点赞、收藏和分享!有任何问题或建议,欢迎在评论区讨论。

🎮 开始你的游戏之旅吧!

代码即艺术,创造即快乐! 🚀✨

全部评论
暂无评论
0/144