54浏览
查看: 54|回复: 4

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

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

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

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

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

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

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

驴友花雕  中级技神
 楼主|

发表于 3 天前

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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 天前

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 天前

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

项目代码

  1. //-----------------------------------------------------
  2. // 15 Puzzle Game  3.2"-Display
  3. // by: mircemk
  4. // License: GNU GPl 3.0
  5. // Created: 2025-03-27 20:52:34
  6. //-----------------------------------------------------
  7. #include <SPI.h>
  8. #include <TFT_eSPI.h>
  9. TFT_eSPI tft = TFT_eSPI();
  10. // Game board configuration
  11. #define GRID_SIZE 4
  12. #define TILE_SIZE 54     // Increased by 1 pixel
  13. #define TILE_SPACING 2
  14. #define GRID_START_X 10
  15. #define GRID_START_Y 9
  16. // Button configuration
  17. #define BUTTON_W 100
  18. #define BUTTON_H 50
  19. #define START_BTN_X 110   // Centered start button for welcome screen
  20. #define START_BTN_Y 145
  21. // Right side menu buttons
  22. #define MENU_BTN_W 75
  23. #define MENU_BTN_H 71
  24. #define MENU_BTN_X 236
  25. #define MENU_BTN_Y1 9    // First button
  26. #define MENU_BTN_Y2 85   // Second button
  27. #define MENU_BTN_Y3 160   // Third button
  28. // Sound configuration
  29. #define SOUND_PIN 2
  30. #define GAME_START_DELAY 1000
  31. // Colors
  32. #define TILE_COLOR TFT_BLUE
  33. #define TILE_TEXT_COLOR TFT_WHITE
  34. #define EMPTY_COLOR TFT_BLACK
  35. #define GRID_COLOR TFT_DARKGREY
  36. // Board colors array
  37. const uint16_t boardColors[] = {
  38.     0x001F,  // Blue
  39.     0xFDA0,  // Orange
  40.     0xF800,  // Red
  41.     0x7BE0,  //  Olivie
  42.     0xF81F   // Magenta
  43. };
  44. int currentColorIndex = 0;
  45. // Game state variables
  46. uint16_t pixel_x, pixel_y;
  47. byte gameBoard[GRID_SIZE][GRID_SIZE];
  48. int emptyTileX = GRID_SIZE - 1;
  49. int emptyTileY = GRID_SIZE - 1;
  50. bool gameStarted = false;
  51. int moves = 0;
  52. int iEnableButtons = 1;
  53. unsigned long lastSoundTime = 0;
  54. bool soundEnabled = true;
  55. unsigned long lastButtonPress = 0;
  56. #define BUTTON_DEBOUNCE_TIME 250  // 250ms debounce
  57. // Function prototypes
  58. void showWelcomeScreen();
  59. void drawFrame(int size, uint16_t color);
  60. void initializeGame();
  61. void drawBoard();
  62. void drawTile(int x, int y);
  63. void drawMenuButtons();
  64. bool isValidMove(int x, int y);
  65. void moveTile(int x, int y);
  66. void silentMoveTile(int x, int y);
  67. void shuffleBoard();
  68. void handleGameTouch();
  69. void checkMenuButtons();
  70. bool checkWin();
  71. void gameWon();
  72. void playSound(int type);
  73. void updateMovesDisplay();
  74. void changeBoardColor();
  75. void setup() {
  76.     uint16_t calibrationData[5];
  77.     pinMode(15, OUTPUT);
  78.     digitalWrite(15, LOW);
  79.     Serial.begin(115200);
  80.    
  81.     tft.init();
  82.     tft.setRotation(1);
  83.     tft.fillScreen((0xFFFF));
  84.    
  85.     // Calibration screen
  86.     tft.setCursor(40, 20, 2);
  87.     tft.setTextColor(TFT_RED, TFT_WHITE);
  88.     tft.setTextSize(2);
  89.     tft.println("Calibration of");
  90.     tft.setCursor(40, 60, 2);
  91.     tft.println("Display");
  92.     tft.setTextColor(TFT_BLACK, TFT_WHITE);
  93.     tft.setCursor(40, 100, 2);
  94.     tft.println("Touch");
  95.     tft.setCursor(40, 140, 2);
  96.     tft.println("the indicated corners");
  97.     tft.calibrateTouch(calibrationData, TFT_GREEN, TFT_RED, 15);
  98.    
  99.     showWelcomeScreen();
  100. }
  101. void loop() {
  102.     static uint16_t color;
  103.     if (tft.getTouch(&pixel_x, &pixel_y) && iEnableButtons) {
  104.         if (!gameStarted) {
  105.             if (pixel_x > START_BTN_X && pixel_x < (START_BTN_X + BUTTON_W) &&
  106.                 pixel_y > START_BTN_Y && pixel_y < (START_BTN_Y + BUTTON_H)) {
  107.                
  108.                 iEnableButtons = 0;
  109.                
  110.                 while(tft.getTouch(&pixel_x, &pixel_y)) {
  111.                     delay(10);
  112.                 }
  113.                
  114.                 playSound(1);
  115.                 delay(GAME_START_DELAY);
  116.                
  117.                 tft.fillScreen(TFT_BLACK);
  118.                 initializeGame();
  119.                 shuffleBoard();
  120.                 gameStarted = true;
  121.                
  122.                 while(tft.getTouch(&pixel_x, &pixel_y)) {
  123.                     delay(10);
  124.                 }
  125.                
  126.                 delay(250);
  127.                 iEnableButtons = 1;
  128.             }
  129.         } else {
  130.             handleGameTouch();
  131.             checkMenuButtons();
  132.         }
  133.     }
  134. }
  135. void changeBoardColor() {
  136.     currentColorIndex = (currentColorIndex + 1) % 5;
  137.     for (int y = 0; y < GRID_SIZE; y++) {
  138.         for (int x = 0; x < GRID_SIZE; x++) {
  139.             if (gameBoard[y][x] != 0) {
  140.                 int pixelX = GRID_START_X + x * (TILE_SIZE + TILE_SPACING);
  141.                 int pixelY = GRID_START_Y + y * (TILE_SIZE + TILE_SPACING);
  142.                 tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, boardColors[currentColorIndex]);
  143.                
  144.                 tft.setTextColor(TILE_TEXT_COLOR);
  145.                 tft.setTextSize(2);
  146.                 String number = String(gameBoard[y][x]);
  147.                 int textWidth = number.length() * 12;
  148.                 int textHeight = 14;
  149.                 int textX = pixelX + (TILE_SIZE - textWidth) / 2;
  150.                 int textY = pixelY + (TILE_SIZE - textHeight) / 2;
  151.                 tft.setCursor(textX, textY);
  152.                 tft.print(number);
  153.             }
  154.         }
  155.     }
  156. }
  157. void playSound(int type) {
  158.     if (!soundEnabled) return;
  159.    
  160.     switch(type) {
  161.         case 0: // Tile move sound
  162.             tone(SOUND_PIN, 200, 50);  // Short 200Hz beep
  163.             break;
  164.             
  165.         case 1: // Game start sound
  166.             soundEnabled = false;
  167.             tone(SOUND_PIN, 400, 200);
  168.             delay(250);
  169.             tone(SOUND_PIN, 600, 200);
  170.             delay(250);
  171.             tone(SOUND_PIN, 800, 400);
  172.             delay(500);
  173.             soundEnabled = true;
  174.             break;
  175.             
  176.         case 2: // Win sound
  177.             soundEnabled = false;
  178.             tone(SOUND_PIN, 800, 200);
  179.             delay(200);
  180.             tone(SOUND_PIN, 1000, 200);
  181.             delay(200);
  182.             tone(SOUND_PIN, 1200, 400);
  183.             delay(500);
  184.             soundEnabled = true;
  185.             break;
  186.     }
  187. }
  188. void updateMovesDisplay() {
  189.     // Clear the previous number with MAROON background
  190.     tft.fillRect(MENU_BTN_X + 1, MENU_BTN_Y1 + 35, MENU_BTN_W - 2, 20, TFT_MAROON);
  191.    
  192.     tft.setTextColor(TFT_WHITE);
  193.     tft.setTextSize(1);
  194.    
  195.     String movesStr = String(moves);
  196.     int textWidth = movesStr.length() * 6;
  197.    
  198.     tft.setCursor(MENU_BTN_X + (MENU_BTN_W - textWidth) / 2, MENU_BTN_Y1 + 35);
  199.     tft.print(movesStr);
  200. }
  201. void showWelcomeScreen() {
  202.     tft.fillScreen(TFT_BLACK);
  203.     drawFrame(5, TFT_RED);
  204.    
  205.     tft.setTextColor(TFT_YELLOW);
  206.     tft.setTextSize(4);
  207.     tft.setCursor(130, 10);
  208.     tft.print("15");
  209.     tft.setCursor(75, 70);
  210.     tft.print("PUZZLE");
  211.    
  212.     tft.fillRect(START_BTN_X, START_BTN_Y, BUTTON_W, BUTTON_H, TFT_RED);
  213.     tft.setTextColor(TFT_WHITE);
  214.     tft.setTextSize(2);
  215.     tft.setCursor(START_BTN_X + 10, START_BTN_Y + 10);
  216.     tft.print("START");
  217. }
  218. void drawFrame(int size, uint16_t color) {
  219.     for (int i = 0; i < size; i++) {
  220.         tft.drawRect(i, i, 320-i*2, 240-i*2, color);
  221.     }
  222. }
  223. void initializeGame() {
  224.     tft.fillScreen(TFT_BLACK);
  225.     drawFrame(5, TFT_RED);
  226.    
  227.     emptyTileX = GRID_SIZE - 1;
  228.     emptyTileY = GRID_SIZE - 1;
  229.     moves = 0;
  230.     currentColorIndex = 0;  // Reset color to first color
  231.    
  232.     int value = 1;
  233.     for (int y = 0; y < GRID_SIZE; y++) {
  234.         for (int x = 0; x < GRID_SIZE; x++) {
  235.             if (x == GRID_SIZE-1 && y == GRID_SIZE-1) {
  236.                 gameBoard[y][x] = 0;
  237.             } else {
  238.                 gameBoard[y][x] = value++;
  239.             }
  240.         }
  241.     }
  242.    
  243.     drawBoard();
  244.     drawMenuButtons();
  245. }
  246. void drawBoard() {
  247.     for (int y = 0; y < GRID_SIZE; y++) {
  248.         for (int x = 0; x < GRID_SIZE; x++) {
  249.             drawTile(x, y);
  250.         }
  251.     }
  252. }
  253. void drawTile(int x, int y) {
  254.     int pixelX = GRID_START_X + x * (TILE_SIZE + TILE_SPACING);
  255.     int pixelY = GRID_START_Y + y * (TILE_SIZE + TILE_SPACING);
  256.    
  257.     if (gameBoard[y][x] == 0) {
  258.         tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, EMPTY_COLOR);
  259.     } else {
  260.         tft.fillRect(pixelX, pixelY, TILE_SIZE, TILE_SIZE, boardColors[currentColorIndex]);
  261.         tft.setTextColor(TILE_TEXT_COLOR);
  262.         tft.setTextSize(2);
  263.         
  264.         String number = String(gameBoard[y][x]);
  265.         int textWidth = number.length() * 12;
  266.         int textHeight = 14;
  267.         
  268.         int textX = pixelX + (TILE_SIZE - textWidth) / 2;
  269.         int textY = pixelY + (TILE_SIZE - textHeight) / 2;
  270.         
  271.         tft.setCursor(textX, textY);
  272.         tft.print(number);
  273.     }
  274. }
  275. void drawMenuButtons() {
  276.     // Draw three menu buttons on the right
  277.     for(int i = 0; i < 3; i++) {
  278.         int y_pos;
  279.         switch(i) {
  280.             case 0: y_pos = MENU_BTN_Y1; break;
  281.             case 1: y_pos = MENU_BTN_Y2; break;
  282.             case 2: y_pos = MENU_BTN_Y3; break;
  283.         }
  284.         
  285.         // Set different colors for each button
  286.         uint16_t buttonColor;
  287.         if (i == 0) buttonColor = TFT_MAROON;      // Moves button
  288.         else if (i == 1) buttonColor = TFT_BLUE;   // Color change button
  289.         else buttonColor = TFT_DARKGREEN;          // New game button
  290.         
  291.         tft.fillRect(MENU_BTN_X, y_pos, MENU_BTN_W, MENU_BTN_H, buttonColor);
  292.         tft.drawRect(MENU_BTN_X, y_pos, MENU_BTN_W, MENU_BTN_H, TFT_WHITE);
  293.         
  294.         if (i == 0) {  // Top button - Moves counter
  295.             tft.setTextColor(TFT_WHITE);
  296.             tft.setTextSize(1);
  297.             tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 30) / 2, y_pos + 15);
  298.             tft.print("MOVES");
  299.             updateMovesDisplay();
  300.         }
  301.         else if (i == 1) {  // Middle button - Color changer
  302.             tft.setTextColor(TFT_WHITE);
  303.             tft.setTextSize(1);
  304.             tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 30) / 2, y_pos + 15);
  305.             tft.print("COLOR");
  306.             tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 36) / 2, y_pos + 35);
  307.             tft.print("CHANGE");
  308.         }
  309.         else if (i == 2) {  // Bottom button - NEW GAME
  310.             tft.setTextColor(TFT_WHITE);
  311.             tft.setTextSize(1);
  312.             tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 18) / 2, y_pos + 20);
  313.             tft.print("NEW");
  314.             tft.setCursor(MENU_BTN_X + (MENU_BTN_W - 24) / 2, y_pos + 40);
  315.             tft.print("GAME");
  316.         }
  317.     }
  318. }
  319. void handleGameTouch() {
  320.     int tileX = (pixel_x - GRID_START_X) / (TILE_SIZE + TILE_SPACING);
  321.     int tileY = (pixel_y - GRID_START_Y) / (TILE_SIZE + TILE_SPACING);
  322.    
  323.     if (tileX >= 0 && tileX < GRID_SIZE && tileY >= 0 && tileY < GRID_SIZE) {
  324.         if (isValidMove(tileX, tileY)) {
  325.             moveTile(tileX, tileY);
  326.             moves++;
  327.             updateMovesDisplay();
  328.             
  329.             if (checkWin()) {
  330.                 gameWon();
  331.             }
  332.         }
  333.     }
  334. }
  335. void checkMenuButtons() {
  336.     unsigned long currentTime = millis();
  337.    
  338.     if (currentTime - lastButtonPress < BUTTON_DEBOUNCE_TIME) {
  339.         return;
  340.     }
  341.    
  342.     if (pixel_x >= MENU_BTN_X && pixel_x < (MENU_BTN_X + MENU_BTN_W)) {
  343.         if (pixel_y >= MENU_BTN_Y1 && pixel_y < (MENU_BTN_Y1 + MENU_BTN_H)) {
  344.             playSound(0);
  345.             lastButtonPress = currentTime;
  346.             
  347.             while(tft.getTouch(&pixel_x, &pixel_y)) {
  348.                 delay(10);
  349.             }
  350.         }
  351.         else if (pixel_y >= MENU_BTN_Y2 && pixel_y < (MENU_BTN_Y2 + MENU_BTN_H)) {
  352.             playSound(0);
  353.             changeBoardColor();
  354.             lastButtonPress = currentTime;
  355.             
  356.             while(tft.getTouch(&pixel_x, &pixel_y)) {
  357.                 delay(10);
  358.             }
  359.         }
  360.         else if (pixel_y >= MENU_BTN_Y3 && pixel_y < (MENU_BTN_Y3 + MENU_BTN_H)) {
  361.             iEnableButtons = 0;
  362.             
  363.             while(tft.getTouch(&pixel_x, &pixel_y)) {
  364.                 delay(10);
  365.             }
  366.             
  367.             playSound(1);
  368.             delay(GAME_START_DELAY);
  369.             
  370.             initializeGame();
  371.             shuffleBoard();
  372.             gameStarted = true;
  373.             
  374.             while(tft.getTouch(&pixel_x, &pixel_y)) {
  375.                 delay(10);
  376.             }
  377.             
  378.             delay(250);
  379.             iEnableButtons = 1;
  380.             lastButtonPress = currentTime;
  381.         }
  382.     }
  383. }
  384. bool isValidMove(int x, int y) {
  385.     return (
  386.         (abs(x - emptyTileX) == 1 && y == emptyTileY) ||
  387.         (abs(y - emptyTileY) == 1 && x == emptyTileX)
  388.     );
  389. }
  390. void moveTile(int x, int y) {
  391.     gameBoard[emptyTileY][emptyTileX] = gameBoard[y][x];
  392.     gameBoard[y][x] = 0;
  393.     drawTile(emptyTileX, emptyTileY);
  394.     drawTile(x, y);
  395.     emptyTileX = x;
  396.     emptyTileY = y;
  397.     playSound(0);
  398. }
  399. void silentMoveTile(int x, int y) {
  400.     gameBoard[emptyTileY][emptyTileX] = gameBoard[y][x];
  401.     gameBoard[y][x] = 0;
  402.     emptyTileX = x;
  403.     emptyTileY = y;
  404. }
  405. void shuffleBoard() {
  406.     iEnableButtons = 0;
  407.     soundEnabled = false;
  408.    
  409.     randomSeed(analogRead(34));
  410.     for (int i = 0; i < 200; i++) {
  411.         int direction = random(4);
  412.         int newX = emptyTileX;
  413.         int newY = emptyTileY;
  414.         
  415.         switch (direction) {
  416.             case 0: newY--; break;
  417.             case 1: newY++; break;
  418.             case 2: newX--; break;
  419.             case 3: newX++; break;
  420.         }
  421.         
  422.         if (newX >= 0 && newX < GRID_SIZE && newY >= 0 && newY < GRID_SIZE) {
  423.             silentMoveTile(newX, newY);
  424.         }
  425.     }
  426.    
  427.     drawBoard();
  428.    
  429.     delay(250);
  430.     soundEnabled = true;
  431.     iEnableButtons = 1;
  432. }
  433. bool checkWin() {
  434.     int value = 1;
  435.     for (int y = 0; y < GRID_SIZE; y++) {
  436.         for (int x = 0; x < GRID_SIZE; x++) {
  437.             if (y == GRID_SIZE-1 && x == GRID_SIZE-1) {
  438.                 if (gameBoard[y][x] != 0) return false;
  439.             } else {
  440.                 if (gameBoard[y][x] != value++) return false;
  441.             }
  442.         }
  443.     }
  444.     return true;
  445. }
  446. void gameWon() {
  447.     tft.fillScreen(TFT_BLACK);
  448.     drawFrame(10, TFT_GREEN);
  449.    
  450.     tft.setTextColor(TFT_YELLOW);
  451.     tft.setTextSize(2);
  452.     tft.setCursor(60, 60);
  453.     tft.print("PUZZLE SOLVED!");
  454.    
  455.     tft.setTextSize(2);
  456.     tft.setCursor(80, 120);
  457.     tft.print("Moves: ");
  458.     tft.print(moves);
  459.    
  460.     tft.setTextSize(1);
  461.     tft.setCursor(85, 180);
  462.     tft.print("Touch screen to continue");
  463.    
  464.     playSound(2);
  465.    
  466.     while(tft.getTouch(&pixel_x, &pixel_y)) {
  467.         delay(10);
  468.     }
  469.    
  470.     while(!tft.getTouch(&pixel_x, &pixel_y)) {
  471.         delay(10);
  472.     }
  473.    
  474.     gameStarted = false;
  475.     moves = 0;
  476.     showWelcomeScreen();
  477. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 天前

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



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

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


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

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail