驴友花雕 发表于 3 天前

【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏

这是一个制作有趣的益智游戏的极其简单的方法,只需要三个组件。

“15拼图”是一款经典的滑动拼图游戏,由一个4×4的网格组成,网格中有15个标有数字的方格,中间有一个空白处。游戏的目标是将方格从乱码的初始状态重新排列成数字顺序(从1到15,从左到右,从上到下),然后将它们滑入空白处。

游戏的目标是将牌按升序排列,空白处位于右下角。牌的排列方式有 15! = 1.3 万亿种,但只有一半是可以解决的。

在这个项目中,我将向您展示这个拼图的电子版本,与原始机械版本相比,它也有许多有用的改进和选项。

游戏是在彩色触摸显示屏上进行的,因此不会出现瓷砖粘连或移动困难的情况,这是机械版本中常见的情况。



驴友花雕 发表于 3 天前

【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏

硬件部分极其简单,仅由三个组件组成:

ESP32 开发板
3.2英寸TFT LCD显示屏ILI9341驱动器带触摸屏
和小蜂鸣器



驴友花雕 发表于 3 天前

【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏

需要强调的是,如果您以与给定原理图相同的方式使用显示器与微控制器的连接,则还必须使用专门为该项目适配的TFTesPI库。另外,别忘了告诉您,我使用Arduino IDE版本1.8.16编译了代码,更重要的是,使用ESP32核心版本2.0.4编译了代码。

首先,我将介绍如何加入以及游戏中的选项。打开设备后,显示屏上会出现校准触摸屏的指南。

这一点很重要,因为游戏本身是通过触摸屏幕进行的,因此校准屏幕至关重要。我们通过按指定顺序按下显示屏的四个角来进行校准。然后会出现游戏名称,按下按钮即可开始游戏。游戏区域占据了大部分显示屏,右侧有三个控制信息按钮。

顶部按钮显示当前已进行的移动步数。实际上,我们在这里监控游戏的进程和结果。也就是说,游戏的目标是用尽可能少的步数排列数字。
使用中间的按钮,我们可以从棋盘的五种颜色中选择一种。这是一个非常实用的选项,我们可以在游戏的任何时刻使用它,而不会影响结果。
底部按钮也用于开始新游戏。此按钮在游戏开始时以及当前游戏进行到任何阶段(例如,如果我们遇到某些情况)时均可使用。每次按下此按钮,都会随机生成一个新游戏,因此我们可以选择一个至少在视觉上看起来更容易解决的游戏。屏幕上的所有触摸都会伴有相应的声音,使游戏更轻松、更有趣。
最后,当我们完成游戏时,屏幕上会出现一个信息,显示谜题解决了多少步,以及一个开始新游戏的按钮。

关于代码,我只想说几句话。

您可以看到,它的设计方式允许您轻松更改大多数参数,从图块间距和大小到调整游戏中的声音和颜色。

最后,简短总结一下。这是一个制作这款有趣益智游戏的极其简单的方法,只需要三个组件,就能让你享受数小时的乐趣。















驴友花雕 发表于 3 天前

【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏

项目代码

//-----------------------------------------------------
// 15 Puzzle Game3.2"-Display
// by: mircemk
// License: GNU GPl 3.0
// Created: 2025-03-27 20:52:34
//-----------------------------------------------------

#include <SPI.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();

// Game board configuration
#define GRID_SIZE 4
#define TILE_SIZE 54   // Increased by 1 pixel
#define TILE_SPACING 2
#define GRID_START_X 10
#define GRID_START_Y 9

// Button configuration
#define BUTTON_W 100
#define BUTTON_H 50
#define START_BTN_X 110   // Centered start button for welcome screen
#define START_BTN_Y 145

// Right side menu buttons
#define MENU_BTN_W 75
#define MENU_BTN_H 71
#define MENU_BTN_X 236
#define MENU_BTN_Y1 9    // First button
#define MENU_BTN_Y2 85   // Second button
#define MENU_BTN_Y3 160   // Third button

// Sound configuration
#define SOUND_PIN 2
#define GAME_START_DELAY 1000

// Colors
#define TILE_COLOR TFT_BLUE
#define TILE_TEXT_COLOR TFT_WHITE
#define EMPTY_COLOR TFT_BLACK
#define GRID_COLOR TFT_DARKGREY

// Board colors array
const uint16_t boardColors[] = {
    0x001F,// Blue
    0xFDA0,// Orange
    0xF800,// Red
    0x7BE0,//Olivie
    0xF81F   // Magenta
};
int currentColorIndex = 0;

// Game state variables
uint16_t pixel_x, pixel_y;
byte gameBoard;
int emptyTileX = GRID_SIZE - 1;
int emptyTileY = GRID_SIZE - 1;
bool gameStarted = false;
int moves = 0;
int iEnableButtons = 1;
unsigned long lastSoundTime = 0;
bool soundEnabled = true;
unsigned long lastButtonPress = 0;
#define BUTTON_DEBOUNCE_TIME 250// 250ms debounce

// Function prototypes
void showWelcomeScreen();
void drawFrame(int size, uint16_t color);
void initializeGame();
void drawBoard();
void drawTile(int x, int y);
void drawMenuButtons();
bool isValidMove(int x, int y);
void moveTile(int x, int y);
void silentMoveTile(int x, int y);
void shuffleBoard();
void handleGameTouch();
void checkMenuButtons();
bool checkWin();
void gameWon();
void playSound(int type);
void updateMovesDisplay();
void changeBoardColor();

void setup() {
    uint16_t calibrationData;
    pinMode(15, OUTPUT);
    digitalWrite(15, LOW);
    Serial.begin(115200);
   
    tft.init();
    tft.setRotation(1);
    tft.fillScreen((0xFFFF));
   
    // Calibration screen
    tft.setCursor(40, 20, 2);
    tft.setTextColor(TFT_RED, TFT_WHITE);
    tft.setTextSize(2);
    tft.println("Calibration of");
    tft.setCursor(40, 60, 2);
    tft.println("Display");
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    tft.setCursor(40, 100, 2);
    tft.println("Touch");
    tft.setCursor(40, 140, 2);
    tft.println("the indicated corners");
    tft.calibrateTouch(calibrationData, TFT_GREEN, TFT_RED, 15);
   
    showWelcomeScreen();
}

void loop() {
    static uint16_t color;
    if (tft.getTouch(&pixel_x, &pixel_y) && iEnableButtons) {
      if (!gameStarted) {
            if (pixel_x > START_BTN_X && pixel_x < (START_BTN_X + BUTTON_W) &&
                pixel_y > START_BTN_Y && pixel_y < (START_BTN_Y + BUTTON_H)) {
               
                iEnableButtons = 0;
               
                while(tft.getTouch(&pixel_x, &pixel_y)) {
                  delay(10);
                }
               
                playSound(1);
                delay(GAME_START_DELAY);
               
                tft.fillScreen(TFT_BLACK);
                initializeGame();
                shuffleBoard();
                gameStarted = true;
               
                while(tft.getTouch(&pixel_x, &pixel_y)) {
                  delay(10);
                }
               
                delay(250);
                iEnableButtons = 1;
            }
      } else {
            handleGameTouch();
            checkMenuButtons();
      }
    }
}

void changeBoardColor() {
    currentColorIndex = (currentColorIndex + 1) % 5;
    for (int y = 0; y < GRID_SIZE; y++) {
      for (int x = 0; x < GRID_SIZE; x++) {
            if (gameBoard != 0) {
                int pixelX = GRID_START_X + x * (TILE_SIZE + TILE_SPACING);
                int pixelY = GRID_START_Y + y * (TILE_SIZE + TILE_SPACING);
                tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, boardColors);
               
                tft.setTextColor(TILE_TEXT_COLOR);
                tft.setTextSize(2);
                String number = String(gameBoard);
                int textWidth = number.length() * 12;
                int textHeight = 14;
                int textX = pixelX + (TILE_SIZE - textWidth) / 2;
                int textY = pixelY + (TILE_SIZE - textHeight) / 2;
                tft.setCursor(textX, textY);
                tft.print(number);
            }
      }
    }
}

void playSound(int type) {
    if (!soundEnabled) return;
   
    switch(type) {
      case 0: // Tile move sound
            tone(SOUND_PIN, 200, 50);// Short 200Hz beep
            break;
            
      case 1: // Game start sound
            soundEnabled = false;
            tone(SOUND_PIN, 400, 200);
            delay(250);
            tone(SOUND_PIN, 600, 200);
            delay(250);
            tone(SOUND_PIN, 800, 400);
            delay(500);
            soundEnabled = true;
            break;
            
      case 2: // Win sound
            soundEnabled = false;
            tone(SOUND_PIN, 800, 200);
            delay(200);
            tone(SOUND_PIN, 1000, 200);
            delay(200);
            tone(SOUND_PIN, 1200, 400);
            delay(500);
            soundEnabled = true;
            break;
    }
}

void updateMovesDisplay() {
    // Clear the previous number with MAROON background
    tft.fillRect(MENU_BTN_X + 1, MENU_BTN_Y1 + 35, MENU_BTN_W - 2, 20, TFT_MAROON);
   
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(1);
   
    String movesStr = String(moves);
    int textWidth = movesStr.length() * 6;
   
    tft.setCursor(MENU_BTN_X + (MENU_BTN_W - textWidth) / 2, MENU_BTN_Y1 + 35);
    tft.print(movesStr);
}

void showWelcomeScreen() {
    tft.fillScreen(TFT_BLACK);
    drawFrame(5, TFT_RED);
   
    tft.setTextColor(TFT_YELLOW);
    tft.setTextSize(4);
    tft.setCursor(130, 10);
    tft.print("15");
    tft.setCursor(75, 70);
    tft.print("PUZZLE");
   
    tft.fillRect(START_BTN_X, START_BTN_Y, BUTTON_W, BUTTON_H, TFT_RED);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(2);
    tft.setCursor(START_BTN_X + 10, START_BTN_Y + 10);
    tft.print("START");
}

void drawFrame(int size, uint16_t color) {
    for (int i = 0; i < size; i++) {
      tft.drawRect(i, i, 320-i*2, 240-i*2, color);
    }
}

void initializeGame() {
    tft.fillScreen(TFT_BLACK);
    drawFrame(5, TFT_RED);
   
    emptyTileX = GRID_SIZE - 1;
    emptyTileY = GRID_SIZE - 1;
    moves = 0;
    currentColorIndex = 0;// Reset color to first color
   
    int value = 1;
    for (int y = 0; y < GRID_SIZE; y++) {
      for (int x = 0; x < GRID_SIZE; x++) {
            if (x == GRID_SIZE-1 && y == GRID_SIZE-1) {
                gameBoard = 0;
            } else {
                gameBoard = value++;
            }
      }
    }
   
    drawBoard();
    drawMenuButtons();
}

void drawBoard() {
    for (int y = 0; y < GRID_SIZE; y++) {
      for (int x = 0; x < GRID_SIZE; x++) {
            drawTile(x, y);
      }
    }
}

void drawTile(int x, int y) {
    int pixelX = GRID_START_X + x * (TILE_SIZE + TILE_SPACING);
    int pixelY = GRID_START_Y + y * (TILE_SIZE + TILE_SPACING);
   
    if (gameBoard == 0) {
      tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, EMPTY_COLOR);
    } else {
      tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, boardColors);
      tft.setTextColor(TILE_TEXT_COLOR);
      tft.setTextSize(2);
      
      String number = String(gameBoard);
      int textWidth = number.length() * 12;
      int textHeight = 14;
      
      int textX = pixelX + (TILE_SIZE - textWidth) / 2;
      int textY = pixelY + (TILE_SIZE - textHeight) / 2;
      
      tft.setCursor(textX, textY);
      tft.print(number);
    }
}

void drawMenuButtons() {
    // Draw three menu buttons on the right
    for(int i = 0; i < 3; i++) {
      int y_pos;
      switch(i) {
            case 0: y_pos = MENU_BTN_Y1; break;
            case 1: y_pos = MENU_BTN_Y2; break;
            case 2: y_pos = MENU_BTN_Y3; break;
      }
      
      // Set different colors for each button
      uint16_t buttonColor;
      if (i == 0) buttonColor = TFT_MAROON;      // Moves button
      else if (i == 1) buttonColor = TFT_BLUE;   // Color change button
      else buttonColor = TFT_DARKGREEN;          // New game button
      
      tft.fillRect(MENU_BTN_X, y_pos, MENU_BTN_W, MENU_BTN_H, buttonColor);
      tft.drawRect(MENU_BTN_X, y_pos, MENU_BTN_W, MENU_BTN_H, TFT_WHITE);
      
      if (i == 0) {// Top button - Moves counter
            tft.setTextColor(TFT_WHITE);
            tft.setTextSize(1);
            tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 30) / 2, y_pos + 15);
            tft.print("MOVES");
            updateMovesDisplay();
      }
      else if (i == 1) {// Middle button - Color changer
            tft.setTextColor(TFT_WHITE);
            tft.setTextSize(1);
            tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 30) / 2, y_pos + 15);
            tft.print("COLOR");
            tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 36) / 2, y_pos + 35);
            tft.print("CHANGE");
      }
      else if (i == 2) {// Bottom button - NEW GAME
            tft.setTextColor(TFT_WHITE);
            tft.setTextSize(1);
            tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 18) / 2, y_pos + 20);
            tft.print("NEW");
            tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 24) / 2, y_pos + 40);
            tft.print("GAME");
      }
    }
}

void handleGameTouch() {
    int tileX = (pixel_x - GRID_START_X) / (TILE_SIZE + TILE_SPACING);
    int tileY = (pixel_y - GRID_START_Y) / (TILE_SIZE + TILE_SPACING);
   
    if (tileX >= 0 && tileX < GRID_SIZE && tileY >= 0 && tileY < GRID_SIZE) {
      if (isValidMove(tileX, tileY)) {
            moveTile(tileX, tileY);
            moves++;
            updateMovesDisplay();
            
            if (checkWin()) {
                gameWon();
            }
      }
    }
}

void checkMenuButtons() {
    unsigned long currentTime = millis();
   
    if (currentTime - lastButtonPress < BUTTON_DEBOUNCE_TIME) {
      return;
    }
   
    if (pixel_x >= MENU_BTN_X && pixel_x < (MENU_BTN_X + MENU_BTN_W)) {
      if (pixel_y >= MENU_BTN_Y1 && pixel_y < (MENU_BTN_Y1 + MENU_BTN_H)) {
            playSound(0);
            lastButtonPress = currentTime;
            
            while(tft.getTouch(&pixel_x, &pixel_y)) {
                delay(10);
            }
      }
      else if (pixel_y >= MENU_BTN_Y2 && pixel_y < (MENU_BTN_Y2 + MENU_BTN_H)) {
            playSound(0);
            changeBoardColor();
            lastButtonPress = currentTime;
            
            while(tft.getTouch(&pixel_x, &pixel_y)) {
                delay(10);
            }
      }
      else if (pixel_y >= MENU_BTN_Y3 && pixel_y < (MENU_BTN_Y3 + MENU_BTN_H)) {
            iEnableButtons = 0;
            
            while(tft.getTouch(&pixel_x, &pixel_y)) {
                delay(10);
            }
            
            playSound(1);
            delay(GAME_START_DELAY);
            
            initializeGame();
            shuffleBoard();
            gameStarted = true;
            
            while(tft.getTouch(&pixel_x, &pixel_y)) {
                delay(10);
            }
            
            delay(250);
            iEnableButtons = 1;
            lastButtonPress = currentTime;
      }
    }
}

bool isValidMove(int x, int y) {
    return (
      (abs(x - emptyTileX) == 1 && y == emptyTileY) ||
      (abs(y - emptyTileY) == 1 && x == emptyTileX)
    );
}

void moveTile(int x, int y) {
    gameBoard = gameBoard;
    gameBoard = 0;
    drawTile(emptyTileX, emptyTileY);
    drawTile(x, y);
    emptyTileX = x;
    emptyTileY = y;
    playSound(0);
}

void silentMoveTile(int x, int y) {
    gameBoard = gameBoard;
    gameBoard = 0;
    emptyTileX = x;
    emptyTileY = y;
}

void shuffleBoard() {
    iEnableButtons = 0;
    soundEnabled = false;
   
    randomSeed(analogRead(34));
    for (int i = 0; i < 200; i++) {
      int direction = random(4);
      int newX = emptyTileX;
      int newY = emptyTileY;
      
      switch (direction) {
            case 0: newY--; break;
            case 1: newY++; break;
            case 2: newX--; break;
            case 3: newX++; break;
      }
      
      if (newX >= 0 && newX < GRID_SIZE && newY >= 0 && newY < GRID_SIZE) {
            silentMoveTile(newX, newY);
      }
    }
   
    drawBoard();
   
    delay(250);
    soundEnabled = true;
    iEnableButtons = 1;
}

bool checkWin() {
    int value = 1;
    for (int y = 0; y < GRID_SIZE; y++) {
      for (int x = 0; x < GRID_SIZE; x++) {
            if (y == GRID_SIZE-1 && x == GRID_SIZE-1) {
                if (gameBoard != 0) return false;
            } else {
                if (gameBoard != value++) return false;
            }
      }
    }
    return true;
}

void gameWon() {
    tft.fillScreen(TFT_BLACK);
    drawFrame(10, TFT_GREEN);
   
    tft.setTextColor(TFT_YELLOW);
    tft.setTextSize(2);
    tft.setCursor(60, 60);
    tft.print("PUZZLE SOLVED!");
   
    tft.setTextSize(2);
    tft.setCursor(80, 120);
    tft.print("Moves: ");
    tft.print(moves);
   
    tft.setTextSize(1);
    tft.setCursor(85, 180);
    tft.print("Touch screen to continue");
   
    playSound(2);
   
    while(tft.getTouch(&pixel_x, &pixel_y)) {
      delay(10);
    }
   
    while(!tft.getTouch(&pixel_x, &pixel_y)) {
      delay(10);
    }
   
    gameStarted = false;
    moves = 0;
    showWelcomeScreen();
}

驴友花雕 发表于 3 天前

【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏



附录
【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏
项目链接:https://www.hackster.io/mircemk/diy-esp32-15-puzzle-game-on-tft-touch-dispaly-8c5817
项目作者:北马其顿 米尔塞姆克(Mirko Pavleski)

项目视频 :https://www.youtube.com/watch?v=3YXgka5V8iE
项目代码:https://www.hackster.io/code_files/668381/download







页: [1]
查看完整版本: 【Arduino 动手做】DIY ESP32 TFT 触摸显示屏上的 15 益智游戏