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

Comments
Only logged in users can leave comments