本帖最后由 创客编程张 于 2025-10-15 17:19 编辑
前两天在B站上刷到了井字棋的起源和发展,忽然来了灵感,正愁没素材呢,这不,咱用新板子配合着OLED制作一个井字棋玩玩。
关于FireBeetle 2 ESP32-C5
FireBeetle 2 ESP32-C5 IO套装包括两部分:Firebeetle 2 ESP32-C5 开发板和其专用的IO扩展底板。IO扩展板方便快速连接各种传感器外设,让Firebeetle 2 ESP32-C5开发板到手即用,无需焊接。

FireBeetle 2 ESP32-C5是一款搭载乐鑫 ESP32-C5 模组的低功耗 IoT 开发板,面向智能家居和广泛物联网场景,集高性能计算、多协议支持与智能电源管理于一体,为各种部署需求提供高可靠性、高灵活性与长续航的解决方案。
前期准备
1.软件准备
下载Arduino IDE,打开安装ESP32开发板,然后安装U8g2库(用于驱动OLED显示屏)
2.硬件清单
- ESP32-C5
- OLED显示屏(128*64)
- Keyboard模拟按键
准备完成,我们开始干正事
将OLED连接到开发板的I2C引脚上,将Keyboard模拟按键连接至引脚2
接下来编写程序
首先初始化
- #include <Arduino.h>
- #include <U8g2lib.h>
- #define I2C_SDA 9 // ESP32-C5 专用 I2C 数据引脚(SDA)
- #define I2C_SCL 10 // ESP32-C5 专用 I2C 时钟引脚(SCL)
- #define AD_KEY_PIN A2 // ADKeyboard模拟输入引脚
- #define EMPTY 0 // 棋盘空状态
- #define PLAYER 1 // 玩家(空心圆)
- #define AI 2 // 人机(实心圆)
- #define LONG_PRESS_TIME 1000 // 新增:长按判定时长(1000ms=1秒,可调整)
- // 1. OLED初始化(0.96寸单色I2C,根据屏幕芯片调整)
- U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, I2C_SCL, I2C_SDA);
复制代码
变量
- int chessBoard[3][3] = {0}; // 3x3棋盘,存储落子状态
- int currX = 1, currY = 1; // 当前选中的棋盘坐标(0-2,初始居中)
- bool isPlayerTurn = true; // 是否玩家回合(true=玩家,false=人机)
- bool gameOver = false; // 游戏是否结束
- int winner = EMPTY; // 赢家(0=未分胜负,1=玩家,2=人机)
- int winLine[4] = {0}; // 赢棋连线坐标(x1,y1,x2,y2)
复制代码
keyboard模拟按键
- unsigned long pressStartTime = 0; // 记录S5按键按下的起始时间(毫秒)
- bool isKey5Pressed = false; // 标记S5是否处于按下状态(避免重复触发)
- // 3. ADKeyboard按键识别
- int readADKey() {
- int adVal = analogRead(AD_KEY_PIN);
- delay(100); // 消抖:避免按键机械抖动导致的误读数
- if (adVal >= 2500 && adVal <= 2599) return 1; // S1(上)
- else if (adVal >= 2600 && adVal <= 2699) return 2; // S2(左)
- else if (adVal >= 2700 && adVal <= 2799) return 3; // S3(下)
- else if (adVal >= 2800 && adVal <= 2899) return 4; // S4(右)
- else if (adVal >= 3000 && adVal <= 3099) return 5; // S5(确定/长按重启)
- else return -1; // 无按键按下,返回-1
复制代码
棋盘显示
- // 4. 棋盘坐标转OLED像素坐标
- void chessToOled(int chessX, int chessY, int &oledX, int &oledY) {
- oledX = 20 + chessX * 28; // 横向:左偏移20(居中),每个格子宽28像素
- oledY = 20 + chessY * 22; // 纵向:上偏移20(留提示栏),每个格子高22像素
- }
- // 5. 绘制棋盘线(无修改,确保3x3格子清晰)
- void drawChessBoard() {
- u8g2.drawLine(48, 20, 48, 62); // 第一条竖线(分割第1、2列)
- u8g2.drawLine(76, 20, 76, 62); // 第二条竖线(分割第2、3列)
- u8g2.drawLine(20, 42, 98, 42); // 第一条横线(分割第1、2行)
- u8g2.drawLine(20, 64, 98, 64); // 第二条横线(分割第2、3行)
- }
- // 6. 绘制棋子(无修改,保留闪烁选中效果)
- void drawChess() {
- static unsigned long lastFlashTime = 0;
- static bool flashFlag = true; // 闪烁标志(300ms切换一次,实现选中闪烁)
- if (millis() - lastFlashTime > 300) {
- flashFlag = !flashFlag;
- lastFlashTime = millis();
- }
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- int oledX, oledY;
- chessToOled(x, y, oledX, oledY);
- if (chessBoard[y][x] == PLAYER) u8g2.drawCircle(oledX, oledY, 8); // 玩家:空心圆
- else if (chessBoard[y][x] == AI) u8g2.drawDisc(oledX, oledY, 8); // 人机:实心圆
- else if (x == currX && y == currY && !gameOver && flashFlag) u8g2.drawCircle(oledX, oledY, 8); // 选中空位:闪烁
- }
- }
- }
- // 7. 绘制赢棋连线
- void drawWinLine() {
- if (winner == EMPTY) return; // 无赢家时,不绘制连线
- u8g2.drawLine(winLine[0], winLine[1], winLine[2], winLine[3]); // 绘制提前记录的赢棋连线
- }
- // 8. 绘制顶部提示栏(新增:gameOver时显示“长按S5重启”提示,让用户知晓操作)
- void drawTipBar() {
- u8g2.setFont(u8g2_font_6x12_tf); // 适配128x64屏幕的小字体,避免文字溢出
- if (gameOver) {
- // 先显示输赢/平局结果
- if (winner == PLAYER) u8g2.drawStr(30, 12, "你赢了!");
- else if (winner == AI) u8g2.drawStr(30, 12, "人机赢了!");
- else u8g2.drawStr(30, 12, "平局!");
- // 新增:显示重启提示,位置在结果下方,用户一眼能看到
- u8g2.drawStr(15, 22, "长按S5 重新开始");
- } else {
- // 非游戏结束时,显示回合提示
- if (isPlayerTurn) u8g2.drawStr(20, 12, "你的回合(空心圆)");
- else u8g2.drawStr(20, 12, "人机回合(实心圆)");
- }
- }
复制代码
输赢检查
- // 9. 检查是否赢棋
- bool checkWin(int player) {
- // 检查3行
- for (int y = 0; y < 3; y++) {
- if (chessBoard[y][0] == player && chessBoard[y][1] == player && chessBoard[y][2] == player) {
- chessToOled(0, y, winLine[0], winLine[1]); // 记录行连线起点
- chessToOled(2, y, winLine[2], winLine[3]); // 记录行连线终点
- return true;
- }
- }
- // 检查3列
- for (int x = 0; x < 3; x++) {
- if (chessBoard[0][x] == player && chessBoard[1][x] == player && chessBoard[2][x] == player) {
- chessToOled(x, 0, winLine[0], winLine[1]); // 记录列连线起点
- chessToOled(x, 2, winLine[2], winLine[3]); // 记录列连线终点
- return true;
- }
- }
- // 检查左上-右下对角线
- if (chessBoard[0][0] == player && chessBoard[1][1] == player && chessBoard[2][2] == player) {
- chessToOled(0, 0, winLine[0], winLine[1]); // 记录对角线起点
- chessToOled(2, 2, winLine[2], winLine[3]); // 记录对角线终点
- return true;
- }
- // 检查右上-左下对角线
- if (chessBoard[0][2] == player && chessBoard[1][1] == player && chessBoard[2][0] == player) {
- chessToOled(2, 0, winLine[0], winLine[1]); // 记录对角线起点
- chessToOled(0, 2, winLine[2], winLine[3]); // 记录对角线终点
- return true;
- }
- return false;
- }
- // 10. 检查是否平局
- bool checkDraw() {
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) return false; // 只要有一个空位,就不是平局
- }
- }
- return true; // 棋盘满且无赢家,判定为平局
- }
复制代码
落子逻辑
- // 11. 人机落子逻辑
- void aiMove() {
- if (gameOver) return; // 游戏结束,不执行AI落子
- // 第一步:优先自己赢(试落子,能赢就落)
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = AI; // 试落AI棋子
- if (checkWin(AI)) { // 试落后果:AI赢
- winner = AI; // 标记AI为赢家
- gameOver = true; // 标记游戏结束
- isPlayerTurn = true; // 重置回合,不影响重启
- return;
- }
- chessBoard[y][x] = EMPTY; // 回溯:取消试落,避免影响后续判断
- }
- }
- }
- // 第二步:防玩家赢(试落玩家棋子,堵玩家赢点)
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = PLAYER; // 试落玩家棋子
- if (checkWin(PLAYER)) { // 试落后果:玩家要赢
- chessBoard[y][x] = AI; // 落AI棋子,堵住这个赢点
- isPlayerTurn = true; // 切换回玩家回合
- return;
- }
- chessBoard[y][x] = EMPTY; // 回溯:取消试落
- }
- }
- }
- // 第三步:随机落子(无赢/防赢机会时,避免AI落子固定)
- while (true) {
- int x = random(0, 3);
- int y = random(0, 3);
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = AI;
- isPlayerTurn = true;
- return;
- }
- }
- }
复制代码
游戏参数重置
- void resetGame() {
- memset(chessBoard, 0, sizeof(chessBoard)); // 重置棋盘:所有位置变为“空”
- currX = 1; currY = 1; // 重置选中位置:回到棋盘正中间
- isPlayerTurn = true; // 重置回合:玩家先落子(符合常规习惯)
- gameOver = false; // 重置游戏状态:从“结束”变为“进行中”
- winner = EMPTY; // 重置赢家:无赢家
- memset(winLine, 0, sizeof(winLine)); // 重置赢棋连线:清空连线坐标
- // 重置长按相关变量:避免重启后残留按键状态,导致误触发
- pressStartTime = 0;
- isKey5Pressed = false;
- }
复制代码
初始化函数
- // 12. 初始化函数
- void setup() {
- Serial.begin(115200); // 初始化串口,方便调试AD按键值(可选关闭)
- u8g2.begin(); // 初始化OLED(I2C通信,自动识别设备地址)
- u8g2.clearBuffer(); // 清空OLED缓存,避免残留乱码
- resetGame(); // 调用新增的重置函数,初始化首次游戏参数(替代原重复代码)
- randomSeed(analogRead(A1)); // 用空闲模拟引脚做随机种子,让AI落子更随机
复制代码
主循环
- void loop() {
- int key = readADKey(); // 读取当前按键(先获取按键值,再分状态处理)
- if (!gameOver && isPlayerTurn) {
- // 状态1:游戏进行中+玩家回合
- switch (key) {
- case 1: // S1:向上(y坐标-1,避免超出棋盘上边界0)
- if (currY > 0) currY--;
- break;
- case 2: // S2:向左(x坐标-1,避免超出棋盘左边界0)
- if (currX > 0) currX--;
- break;
- case 3: // S3:向下(y坐标+1,避免超出棋盘下边界2)
- if (currY < 2) currY++;
- break;
- case 4: // S4:向右(x坐标+1,避免超出棋盘右边界2)
- if (currX < 2) currX++;
- break;
- case 5: // S5:确定落子(仅当前位置为空时有效,避免重复落子)
- if (chessBoard[currY][currX] == EMPTY) {
- chessBoard[currY][currX] = PLAYER; // 落玩家棋子(空心圆)
- if (checkWin(PLAYER)) { // 检查玩家是否赢
- winner = PLAYER;
- gameOver = true;
- } else if (checkDraw()) { // 检查是否平局
- gameOver = true;
- } else {
- isPlayerTurn = false; // 切换到人机回合
- }
- }
- break;
- }
- } else if (!gameOver && !isPlayerTurn) {
- // 状态2:游戏进行中+人机回合
- delay(500);
- aiMove(); // 执行AI落子
- // 落子后检查结果:人机赢或平局
- if (checkWin(AI)) {
- winner = AI;
- gameOver = true;
- } else if (checkDraw()) {
- gameOver = true;
- }
- } else {
- // 新增:状态3:游戏结束(赢/输/平局通用),处理S5长按重启
- switch (key) {
- case 5: // 检测到S5按键按下
- if (!isKey5Pressed) { // 仅当按键未被标记为“按下”时,记录起始时间(避免重复记录)
- isKey5Pressed = true; // 标记S5已按下
- pressStartTime = millis(); // 记录按下的起始时间(单位:毫秒)
- }
- break;
- case -1: // 检测到无按键按下(即S5已松开,判断是否为有效长按)
- if (isKey5Pressed) { // 之前S5被按下过,现在松开,开始计算时长
- unsigned long pressDuration = millis() - pressStartTime; // 计算按键按下的总时长
- if (pressDuration >= LONG_PRESS_TIME) { // 时长≥1秒,判定为有效长按
- resetGame(); // 调用新增的重置函数,重启游戏
- }
- // 无论是否长按成功,都重置长按相关变量,为下次长按做准备
- isKey5Pressed = false;
- pressStartTime = 0;
- }
- break;
- }
- }
- // OLED显示更新(原逻辑不变,按“提示栏→棋盘→棋子→连线”顺序,避免遮挡)
- u8g2.clearBuffer();
- drawTipBar();
- drawChessBoard();
- drawChess();
- drawWinLine();
- u8g2.sendBuffer();
- }
复制代码
完整代码
- #include <Arduino.h>
- #include <U8g2lib.h>
- #define I2C_SDA 9 // ESP32-C5 专用 I2C 数据引脚(SDA)
- #define I2C_SCL 10 // ESP32-C5 专用 I2C 时钟引脚(SCL)
- #define AD_KEY_PIN A2 // ADKeyboard模拟输入引脚
- #define EMPTY 0 // 棋盘空状态
- #define PLAYER 1 // 玩家(空心圆)
- #define AI 2 // 人机(实心圆)
- #define LONG_PRESS_TIME 1000
- // 1. OLED初始化
- U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, I2C_SCL, I2C_SDA);
-
- int chessBoard[3][3] = {0}; // 3x3棋盘,存储落子状态
- int currX = 1, currY = 1; // 当前选中的棋盘坐标(0-2,初始居中)
- bool isPlayerTurn = true; // 是否玩家回合(true=玩家,false=人机)
- bool gameOver = false; // 游戏是否结束
- int winner = EMPTY; // 赢家(0=未分胜负,1=玩家,2=人机)
- int winLine[4] = {0}; // 赢棋连线坐标(x1,y1,x2,y2)
- // 长按重启相关全局变量(跟踪按键按下时长,避免误触)
- unsigned long pressStartTime = 0; // 记录S5按键按下的起始时间(毫秒)
- bool isKey5Pressed = false; // 标记S5是否处于按下状态(避免重复触发)
- // 3. ADKeyboard按键识别
- int readADKey() {
- int adVal = analogRead(AD_KEY_PIN);
- delay(100); // 消抖:避免按键机械抖动导致的误读数
- if (adVal >= 2500 && adVal <= 2599) return 1; // S1(上)
- else if (adVal >= 2600 && adVal <= 2699) return 2; // S2(左)
- else if (adVal >= 2700 && adVal <= 2799) return 3; // S3(下)
- else if (adVal >= 2800 && adVal <= 2899) return 4; // S4(右)
- else if (adVal >= 3000 && adVal <= 3099) return 5; // S5(确定/长按重启)
- else return -1; // 无按键按下,返回-1
- }
- // 4. 棋盘坐标转OLED像素坐标
- void chessToOled(int chessX, int chessY, int &oledX, int &oledY) {
- oledX = 20 + chessX * 28; // 横向:左偏移20(居中),每个格子宽28像素
- oledY = 20 + chessY * 22; // 纵向:上偏移20(留提示栏),每个格子高22像素
- }
- // 5. 绘制棋盘线(无修改,确保3x3格子清晰)
- void drawChessBoard() {
- u8g2.drawLine(48, 20, 48, 62); // 第一条竖线(分割第1、2列)
- u8g2.drawLine(76, 20, 76, 62); // 第二条竖线(分割第2、3列)
- u8g2.drawLine(20, 42, 98, 42); // 第一条横线(分割第1、2行)
- u8g2.drawLine(20, 64, 98, 64); // 第二条横线(分割第2、3行)
- }
- // 6. 绘制棋子
- void drawChess() {
- static unsigned long lastFlashTime = 0;
- static bool flashFlag = true; // 闪烁标志(300ms切换一次,实现选中闪烁)
- if (millis() - lastFlashTime > 300) {
- flashFlag = !flashFlag;
- lastFlashTime = millis();
- }
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- int oledX, oledY;
- chessToOled(x, y, oledX, oledY);
- if (chessBoard[y][x] == PLAYER) u8g2.drawCircle(oledX, oledY, 8); // 玩家:空心圆
- else if (chessBoard[y][x] == AI) u8g2.drawDisc(oledX, oledY, 8); // 人机:实心圆
- else if (x == currX && y == currY && !gameOver && flashFlag) u8g2.drawCircle(oledX, oledY, 8); // 选中空位:闪烁
- }
- }
- }
- // 7. 绘制赢棋连线
- void drawWinLine() {
- if (winner == EMPTY) return; // 无赢家时,不绘制连线
- u8g2.drawLine(winLine[0], winLine[1], winLine[2], winLine[3]); // 绘制提前记录的赢棋连线
- }
- // 8. 绘制顶部提示栏(新增:gameOver时显示“长按S5重启”提示,让用户知晓操作)
- void drawTipBar() {
- u8g2.setFont(u8g2_font_6x12_tf); // 适配128x64屏幕的小字体,避免文字溢出
- if (gameOver) {
- // 先显示输赢/平局结果
- if (winner == PLAYER) u8g2.drawStr(30, 12, "你赢了!");
- else if (winner == AI) u8g2.drawStr(30, 12, "人机赢了!");
- else u8g2.drawStr(30, 12, "平局!");
- // 新增:显示重启提示,位置在结果下方,用户一眼能看到
- u8g2.drawStr(15, 22, "长按S5 重新开始");
- } else {
- // 非游戏结束时,显示回合提示(原逻辑不变)
- if (isPlayerTurn) u8g2.drawStr(20, 12, "你的回合(空心圆)");
- else u8g2.drawStr(20, 12, "人机回合(实心圆)");
- }
- }
- // 9. 检查是否赢棋(无修改,确保赢棋判定准确)
- bool checkWin(int player) {
- // 检查3行
- for (int y = 0; y < 3; y++) {
- if (chessBoard[y][0] == player && chessBoard[y][1] == player && chessBoard[y][2] == player) {
- chessToOled(0, y, winLine[0], winLine[1]); // 记录行连线起点
- chessToOled(2, y, winLine[2], winLine[3]); // 记录行连线终点
- return true;
- }
- }
- // 检查3列
- for (int x = 0; x < 3; x++) {
- if (chessBoard[0][x] == player && chessBoard[1][x] == player && chessBoard[2][x] == player) {
- chessToOled(x, 0, winLine[0], winLine[1]); // 记录列连线起点
- chessToOled(x, 2, winLine[2], winLine[3]); // 记录列连线终点
- return true;
- }
- }
- // 检查左上-右下对角线
- if (chessBoard[0][0] == player && chessBoard[1][1] == player && chessBoard[2][2] == player) {
- chessToOled(0, 0, winLine[0], winLine[1]); // 记录对角线起点
- chessToOled(2, 2, winLine[2], winLine[3]); // 记录对角线终点
- return true;
- }
- // 检查右上-左下对角线
- if (chessBoard[0][2] == player && chessBoard[1][1] == player && chessBoard[2][0] == player) {
- chessToOled(2, 0, winLine[0], winLine[1]); // 记录对角线起点
- chessToOled(0, 2, winLine[2], winLine[3]); // 记录对角线终点
- return true;
- }
- return false;
- }
- // 10. 检查是否平局
- bool checkDraw() {
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) return false; // 只要有一个空位,就不是平局
- }
- }
- return true; // 棋盘满且无赢家,判定为平局
- }
- // 11. 人机落子逻辑(无修改,保持“优先赢、次防输、最后随机”策略)
- void aiMove() {
- if (gameOver) return; // 游戏结束,不执行AI落子
- // 第一步:优先自己赢(试落子,能赢就落)
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = AI; // 试落AI棋子
- if (checkWin(AI)) { // 试落后果:AI赢
- winner = AI; // 标记AI为赢家
- gameOver = true; // 标记游戏结束
- isPlayerTurn = true; // 重置回合,不影响重启
- return;
- }
- chessBoard[y][x] = EMPTY; // 回溯:取消试落,避免影响后续判断
- }
- }
- }
- // 第二步:防玩家赢(试落玩家棋子,堵玩家赢点)
- for (int y = 0; y < 3; y++) {
- for (int x = 0; x < 3; x++) {
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = PLAYER; // 试落玩家棋子
- if (checkWin(PLAYER)) { // 试落后果:玩家要赢
- chessBoard[y][x] = AI; // 落AI棋子,堵住这个赢点
- isPlayerTurn = true; // 切换回玩家回合
- return;
- }
- chessBoard[y][x] = EMPTY; // 回溯:取消试落
- }
- }
- }
- // 第三步:随机落子(无赢/防赢机会时,避免AI落子固定)
- while (true) {
- int x = random(0, 3);
- int y = random(0, 3);
- if (chessBoard[y][x] == EMPTY) {
- chessBoard[y][x] = AI;
- isPlayerTurn = true;
- return;
- }
- }
- }
- // 新增:游戏参数重置函数(单独封装,避免代码重复,长按重启时调用)
- void resetGame() {
- memset(chessBoard, 0, sizeof(chessBoard)); // 重置棋盘:所有位置变为“空”
- currX = 1; currY = 1; // 重置选中位置:回到棋盘正中间
- isPlayerTurn = true; // 重置回合:玩家先落子(符合常规习惯)
- gameOver = false; // 重置游戏状态:从“结束”变为“进行中”
- winner = EMPTY; // 重置赢家:无赢家
- memset(winLine, 0, sizeof(winLine)); // 重置赢棋连线:清空连线坐标
- // 重置长按相关变量:避免重启后残留按键状态,导致误触发
- pressStartTime = 0;
- isKey5Pressed = false;
- }
- // 12. 初始化函数
- void setup() {
- Serial.begin(115200); // 初始化串口,方便调试AD按键值
- u8g2.begin(); // 初始化OLED(I2C通信,自动识别设备地址)
- u8g2.clearBuffer(); // 清空OLED缓存,避免残留乱码
- resetGame(); // 调用新增的重置函数,初始化首次游戏参数(替代原重复代码)
- randomSeed(analogRead(A1)); // 用空闲模拟引脚做随机种子,让AI落子更随机
- }
- // 13. 主循环(修改:新增gameOver状态下的长按S5处理逻辑)
- void loop() {
- int key = readADKey(); // 读取当前按键(先获取按键值,再分状态处理)
- if (!gameOver && isPlayerTurn) {
- // 状态1:游戏进行中+玩家回合(原逻辑不变,处理上下左右移动和确定落子)
- switch (key) {
- case 1: // S1:向上(y坐标-1,避免超出棋盘上边界0)
- if (currY > 0) currY--;
- break;
- case 2: // S2:向左(x坐标-1,避免超出棋盘左边界0)
- if (currX > 0) currX--;
- break;
- case 3: // S3:向下(y坐标+1,避免超出棋盘下边界2)
- if (currY < 2) currY++;
- break;
- case 4: // S4:向右(x坐标+1,避免超出棋盘右边界2)
- if (currX < 2) currX++;
- break;
- case 5: // S5:确定落子(仅当前位置为空时有效,避免重复落子)
- if (chessBoard[currY][currX] == EMPTY) {
- chessBoard[currY][currX] = PLAYER; // 落玩家棋子(空心圆)
- if (checkWin(PLAYER)) { // 检查玩家是否赢
- winner = PLAYER;
- gameOver = true;
- } else if (checkDraw()) { // 检查是否平局
- gameOver = true;
- } else {
- isPlayerTurn = false; // 切换到人机回合
- }
- }
- break;
- }
- } else if (!gameOver && !isPlayerTurn) {
- // 状态2:游戏进行中+人机回合
- delay(500);
- aiMove(); // 执行AI落子
- // 落子后检查结果:人机赢或平局
- if (checkWin(AI)) {
- winner = AI;
- gameOver = true;
- } else if (checkDraw()) {
- gameOver = true;
- }
- } else {
- // 新增:状态3:游戏结束(赢/输/平局通用),处理S5长按重启
- switch (key) {
- case 5: // 检测到S5按键按下
- if (!isKey5Pressed) { // 仅当按键未被标记为“按下”时,记录起始时间(避免重复记录)
- isKey5Pressed = true; // 标记S5已按下
- pressStartTime = millis(); // 记录按下的起始时间(单位:毫秒)
- }
- break;
- case -1: // 检测到无按键按下(即S5已松开,判断是否为有效长按)
- if (isKey5Pressed) { // 之前S5被按下过,现在松开,开始计算时长
- unsigned long pressDuration = millis() - pressStartTime; // 计算按键按下的总时长
- if (pressDuration >= LONG_PRESS_TIME) { // 时长≥1秒,判定为有效长按
- resetGame(); // 调用新增的重置函数,重启游戏
- }
- // 无论是否长按成功,都重置长按相关变量,为下次长按做准备
- isKey5Pressed = false;
- pressStartTime = 0;
- }
- break;
- }
- }
- // OLED显示更新
- u8g2.clearBuffer();
- drawTipBar();
- drawChessBoard();
- drawChess();
- drawWinLine();
- u8g2.sendBuffer();
- }
复制代码
进行上传后即可问题说明:1.若按键反馈有问题,你可以修改按键输入阈值。2.当前代码为第一版本,可能代码中会有部分bug,最终版已完成,暂时没有时间发表,请期待最终稳定板。
|