1#include <MCUFRIEND_kbv.h>
2#include <TouchScreen.h>
3#include <Adafruit_GFX.h>
4
5MCUFRIEND_kbv tft;
6
7// ===== PINS & CALIBRATION (MEGA) =====
8#define XP 6
9#define YP A1
10#define XM A2
11#define YM 7
12#define TS_MINX 248
13#define TS_MAXX 920
14#define TS_MINY 251
15#define TS_MAXY 878
16
17#define MINPRESSURE 10
18#define MAXPRESSURE 1000
19
20TouchScreen ts(XP, YP, XM, YM, 300);
21#define BUZZER 49
22
23// ===== COLORS =====
24#define BLACK 0x0000
25#define WHITE 0xFFFF
26#define RED 0xF800
27#define BLUE 0x001F
28#define GREEN 0x07E0
29#define YELLOW 0xFFE0
30#define SIDEBAR_BG 0x2124
31
32// ===== GAME CONFIG =====
33int CW = 16;
34int turn = 0;
35int totalPlayers = 4;
36int diceVal = 1;
37
38int pPos[4][4];
39bool hasWon[4];
40int winOrder[4];
41int winnersCount = 0;
42
43enum State { MENU, WAIT_ROLL, SELECT_PIECE, ANIM, GAME_OVER };
44State currentState = MENU;
45
46void setup() {
47 pinMode(BUZZER, OUTPUT);
48 uint16_t id = tft.readID();
49 if (id == 0xD3D3) id = 0x9486;
50 tft.begin(id);
51 tft.setRotation(1);
52
53 randomSeed(analogRead(A8) + analogRead(A9) + micros());
54
55 // STRONG SOUND TEST (Standard Tone)
56 // If you don't hear this, your buzzer is broken or wiring is loose
57 tone(BUZZER, 1000); delay(100); noTone(BUZZER);
58 delay(50);
59 tone(BUZZER, 2000); delay(100); noTone(BUZZER);
60
61 drawMenu();
62}
63
64void loop() {
65 int x, y;
66
67 if (touch(&x, &y)) {
68 if (currentState == MENU) {
69 if (x > 60 && x < 260) {
70 if (y > 80 && y < 120) startGame(2);
71 else if (y > 130 && y < 170) startGame(3);
72 else if (y > 180 && y < 220) startGame(4);
73 }
74 }
75 else if (currentState == WAIT_ROLL) {
76 if (x > 240 && y > 100 && y < 200) rollRealDice();
77 }
78 else if (currentState == SELECT_PIECE) {
79 int pIdx = getTouchedPiece(x, y);
80 if (pIdx != -1) movePiece(turn, pIdx, diceVal);
81 }
82 else if (currentState == GAME_OVER) {
83 drawMenu();
84 }
85 delay(120);
86 }
87}
88
89// ================= SAFE AUDIO WRAPPER =================
90// Uses standard tone() but forces it OFF immediately to save power
91void playSafeTone(int freq, int duration) {
92 tone(BUZZER, freq);
93 delay(duration); // Play for this long
94 noTone(BUZZER); // CUT POWER IMMEDIATELY
95}
96
97// ================= DICE ENGINE =================
98void rollRealDice() {
99 int dx = 280; int dy = 150; int size = 50;
100
101 // Animation
102 for(int i=0; i<12; i++) {
103 int r = random(1, 7);
104 drawRealDice(dx, dy, size, r, WHITE, BLACK);
105
106 // Play VERY SHORT standard tone (10ms)
107 // Short enough to not flicker, Long enough to hear
108 playSafeTone(500 + (i*100), 10);
109
110 delay(30 + (i*5));
111 }
112
113 // Result
114 diceVal = random(1, 7);
115 drawRealDice(dx, dy, size, diceVal, YELLOW, BLACK);
116
117 // Success Beep
118 playSafeTone(2000, 100);
119
120 int moves = countMovablePieces(turn, diceVal);
121 if (moves == 0) {
122 delay(800);
123 nextTurn();
124 } else if (moves == 1) {
125 int idx = getFirstMovable(turn, diceVal);
126 delay(500);
127 movePiece(turn, idx, diceVal);
128 } else {
129 currentState = SELECT_PIECE;
130 updateStatus("SELECT");
131 }
132}
133
134void movePiece(int p, int idx, int steps) {
135 currentState = ANIM;
136 updateStatus("MOVING");
137
138 // Leave Base
139 if (pPos[p][idx] == -1) {
140 if (steps == 6) {
141 erasePiece(p, idx);
142 pPos[p][idx] = 0;
143 drawPiece(p, idx);
144
145 playSafeTone(1500, 100);
146
147 currentState = WAIT_ROLL;
148 updateSidebar();
149 return;
150 }
151 }
152
153 // Move
154 for(int i=0; i<steps; i++) {
155 erasePiece(p, idx);
156 pPos[p][idx]++;
157 drawPiece(p, idx);
158
159 // Optional: Tiny tick (Uncomment if you want walking sound)
160 // playSafeTone(1000, 5);
161
162 delay(100);
163 if(pPos[p][idx]==57) break;
164 }
165
166 checkKill(p, idx);
167
168 if (checkPlayerWin(p)) {
169 handleWin(p);
170 return;
171 }
172
173 if (steps == 6) {
174 currentState = WAIT_ROLL;
175 updateStatus("ROLL 6!");
176 } else {
177 nextTurn();
178 }
179}
180
181void checkKill(int p, int idx) {
182 int myPos = pPos[p][idx];
183 if (myPos > 51) return;
184 if (myPos==0||myPos==8||myPos==13||myPos==21||myPos==26||myPos==34||myPos==39||myPos==47) return;
185
186 int myGrid = getGlobalStep(p, myPos);
187
188 for(int op=0; op<totalPlayers; op++) {
189 if(op == p) continue;
190 for(int oi=0; oi<4; oi++) {
191 if(pPos[op][oi] != -1 && pPos[op][oi] <= 51) {
192 int opGrid = getGlobalStep(op, pPos[op][oi]);
193 if(myGrid == opGrid) {
194 // Kill Sound
195 playSafeTone(150, 100);
196 delay(50);
197 playSafeTone(100, 150);
198
199 erasePiece(op, oi);
200 pPos[op][oi] = -1;
201 drawPiece(op, oi);
202 }
203 }
204 }
205 }
206}
207
208// ================= GAME LOGIC =================
209void startGame(int p) {
210 totalPlayers = p;
211 turn = 0;
212 winnersCount = 0;
213 for(int i=0; i<4; i++) {
214 hasWon[i] = false;
215 winOrder[i] = -1;
216 for(int j=0; j<4; j++) pPos[i][j] = -1;
217 }
218 tft.fillScreen(BLACK);
219 drawBoard();
220 drawSidebar();
221 updateSidebar();
222 drawAllPieces();
223 currentState = WAIT_ROLL;
224}
225
226bool checkPlayerWin(int p) {
227 int count = 0;
228 for(int i=0; i<4; i++) if (pPos[p][i] == 57) count++;
229 return (count == 4);
230}
231
232void handleWin(int p) {
233 hasWon[p] = true;
234 winOrder[winnersCount] = p;
235 winnersCount++;
236
237 playSafeTone(1000, 100); delay(50);
238 playSafeTone(2000, 100); delay(50);
239 playSafeTone(3000, 300);
240
241 if (totalPlayers == 2) {
242 drawGameOverScreen();
243 return;
244 }
245
246 if (winnersCount == totalPlayers - 1) {
247 for(int i=0; i<totalPlayers; i++) if(!hasWon[i]) winOrder[winnersCount] = i;
248 drawGameOverScreen();
249 } else {
250 tft.fillRect(60, 100, 200, 40, BLACK);
251 tft.setCursor(70, 110);
252 tft.setTextColor(WHITE); tft.setTextSize(2);
253 tft.print("P"); tft.print(p+1); tft.print(" FINISHED!");
254 delay(2000);
255 drawBoard();
256 drawAllPieces();
257 nextTurn();
258 }
259}
260
261void nextTurn() {
262 int loopGuard = 0;
263 do {
264 turn++;
265 if (turn >= totalPlayers) turn = 0;
266 loopGuard++;
267 } while (hasWon[turn] && loopGuard < 10);
268 currentState = WAIT_ROLL;
269 updateSidebar();
270}
271
272// ================= GRAPHICS & HELPERS =================
273void drawSidebar() {
274 tft.fillRect(240, 0, 80, 240, SIDEBAR_BG);
275 tft.drawFastVLine(240, 0, 240, WHITE);
276 tft.setCursor(253, 110);
277 tft.setTextColor(WHITE); tft.setTextSize(2); tft.print("TAP");
278}
279
280void updateSidebar() {
281 tft.fillRect(245, 10, 70, 80, SIDEBAR_BG);
282 tft.setCursor(250, 20); tft.setTextColor(WHITE); tft.setTextSize(2); tft.print("TURN");
283 uint16_t c = (turn==0)?RED : (turn==1)?BLUE : (turn==2)?YELLOW : GREEN;
284 tft.fillCircle(280, 60, 20, c);
285 tft.drawCircle(280, 60, 20, WHITE);
286 drawRealDice(280, 150, 50, diceVal, WHITE, BLACK);
287 updateStatus("WAITING");
288}
289
290void updateStatus(char* msg) {
291 tft.fillRect(242, 210, 76, 30, SIDEBAR_BG);
292 tft.setCursor(245, 215); tft.setTextColor(WHITE); tft.setTextSize(1); tft.print(msg);
293}
294
295void drawRealDice(int cx, int cy, int s, int n, uint16_t bgCol, uint16_t dotCol) {
296 tft.fillRoundRect(cx - s/2, cy - s/2, s, s, 8, bgCol);
297 tft.drawRoundRect(cx - s/2, cy - s/2, s, s, 8, BLACK);
298 int r = s/10; int d = s/4;
299 if (n==1 || n==3 || n==5) tft.fillCircle(cx, cy, r, dotCol);
300 if (n!=1) { tft.fillCircle(cx - d, cy - d, r, dotCol); tft.fillCircle(cx + d, cy + d, r, dotCol); }
301 if (n>=4) { tft.fillCircle(cx + d, cy - d, r, dotCol); tft.fillCircle(cx - d, cy + d, r, dotCol); }
302 if (n==6) { tft.fillCircle(cx - d, cy, r, dotCol); tft.fillCircle(cx + d, cy, r, dotCol); }
303}
304
305void drawBoard() {
306 tft.fillRect(0, 0, 240, 240, WHITE);
307 drawBaseSquare(0, 0, RED); drawBaseSquare(144, 0, BLUE);
308 drawBaseSquare(0, 144, GREEN); drawBaseSquare(144, 144, YELLOW);
309
310 tft.fillTriangle(120,120, 96,96, 144,96, BLUE);
311 tft.fillTriangle(120,120, 144,96, 144,144, YELLOW);
312 tft.fillTriangle(120,120, 144,144, 96,144, GREEN);
313 tft.fillTriangle(120,120, 96,144, 96,96, RED);
314
315 for(int y=0; y<15; y++) {
316 for(int x=0; x<15; x++) {
317 if ((x<6 && y<6) || (x>8 && y<6) || (x<6 && y>8) || (x>8 && y>8)) continue;
318 if (x>=6 && x<=8 && y>=6 && y<=8) continue;
319 tft.drawRect(x*CW, y*CW, CW, CW, BLACK);
320 if(x>=1 && x<=5 && y==7) fillCell(x,y,RED);
321 if(x==7 && y>=1 && y<=5) fillCell(x,y,BLUE);
322 if(x>=9 && x<=13 && y==7) fillCell(x,y,YELLOW);
323 if(x==7 && y>=9 && y<=13) fillCell(x,y,GREEN);
324 }
325 }
326 fillCell(1, 6, RED); fillCell(8, 1, BLUE); fillCell(13, 8, YELLOW); fillCell(6, 13, GREEN);
327 uint16_t SAFE = 0xBDF7;
328 fillCell(2,6,SAFE); fillCell(6,2,SAFE); fillCell(8,12,SAFE); fillCell(12,8,SAFE);
329}
330
331void drawBaseSquare(int x, int y, uint16_t c) {
332 tft.fillRect(x, y, 96, 96, c);
333 tft.fillRect(x+15, y+15, 66, 66, WHITE);
334}
335
336void fillCell(int x, int y, uint16_t c) {
337 tft.fillRect(x*CW+1, y*CW+1, CW-2, CW-2, c);
338}
339
340void drawPiece(int p, int idx) {
341 int pos = pPos[p][idx];
342 int px, py;
343 uint16_t c = (p==0)?RED : (p==1)?BLUE : (p==2)?YELLOW : GREEN;
344 if (pos == -1) {
345 int bx = (p==1||p==2)?144:0; int by = (p==2||p==3)?144:0;
346 int ox = (idx%2)*30 + 33; int oy = (idx/2)*30 + 33;
347 px=bx+ox; py=by+oy;
348 } else {
349 int gx, gy;
350 getGridCoord(p, pos, &gx, &gy);
351 px=gx*CW+8; py=gy*CW+8;
352 }
353 tft.fillCircle(px, py, 6, c);
354 tft.drawCircle(px, py, 6, BLACK);
355}
356
357void erasePiece(int p, int idx) {
358 int pos = pPos[p][idx];
359 if (pos == -1) {
360 int bx = (p==1||p==2)?144:0; int by = (p==2||p==3)?144:0;
361 tft.fillRect(bx+15, by+15, 66, 66, WHITE);
362 for(int i=0; i<4; i++) if(i!=idx && pPos[p][i]==-1) drawPiece(p, i);
363 } else {
364 int gx, gy;
365 getGridCoord(p, pos, &gx, &gy);
366 uint16_t bg = WHITE;
367 if(gx>=1 && gx<=5 && gy==7) bg=RED;
368 if(gx==7 && gy>=1 && gy<=5) bg=BLUE;
369 if(gx>=9 && gx<=13 && gy==7) bg=YELLOW;
370 if(gx==7 && gy>=9 && gy<=13) bg=GREEN;
371 if(gx==1 && gy==6) bg=RED; if(gx==8 && gy==1) bg=BLUE;
372 if(gx==13 && gy==8) bg=YELLOW; if(gx==6 && gy==13) bg=GREEN;
373 if((gx==2&&gy==6)||(gx==6&&gy==2)||(gx==8&&gy==12)||(gx==12&&gy==8)) bg=0xBDF7;
374 tft.fillRect(gx*CW+1, gy*CW+1, CW-2, CW-2, bg);
375
376 int myGlobal = -1;
377 if (pos <= 51) myGlobal = getGlobalStep(p, pos);
378
379 for(int pp=0; pp<totalPlayers; pp++) {
380 for(int ii=0; ii<4; ii++) {
381 if(pp==p && ii==idx) continue;
382 int otherPos = pPos[pp][ii];
383 if (otherPos == -1) continue;
384 bool match = false;
385 if (pos > 51 && otherPos > 51 && pp == p && otherPos == pos) match = true;
386 else if (pos <= 51 && otherPos <= 51) {
387 int otherGlobal = getGlobalStep(pp, otherPos);
388 if (myGlobal == otherGlobal) match = true;
389 }
390 if (match) drawPiece(pp, ii);
391 }
392 }
393 }
394}
395
396void drawAllPieces() {
397 for(int p=0; p<totalPlayers; p++) for(int i=0; i<4; i++) drawPiece(p, i);
398}
399
400void getGridCoord(int p, int s, int *gx, int *gy) {
401 if(s > 51) {
402 int w = s - 52;
403 if(p==0) { *gx=1+w; *gy=7; }
404 else if(p==1) { *gx=7; *gy=1+w; }
405 else if(p==2) { *gx=13-w; *gy=7; }
406 else if(p==3) { *gx=7; *gy=13-w; }
407 return;
408 }
409 int g = getGlobalStep(p, s);
410 if(g<=4) { *gx=1+g; *gy=6; }
411 else if(g==5) { *gx=6; *gy=5; }
412 else if(g<=10) { *gx=6; *gy=5-(g-5); }
413 else if(g==11) { *gx=7; *gy=0; }
414 else if(g==12) { *gx=8; *gy=0; }
415 else if(g<=17) { *gx=8; *gy=1+(g-13); }
416 else if(g==18) { *gx=9; *gy=6; }
417 else if(g<=23) { *gx=10+(g-19); *gy=6; }
418 else if(g==24) { *gx=14; *gy=7; }
419 else if(g==25) { *gx=14; *gy=8; }
420 else if(g<=30) { *gx=13-(g-26); *gy=8; }
421 else if(g==31) { *gx=8; *gy=9; }
422 else if(g<=36) { *gx=8; *gy=10+(g-32); }
423 else if(g==37) { *gx=7; *gy=14; }
424 else if(g==38) { *gx=6; *gy=14; }
425 else if(g<=43) { *gx=6; *gy=13-(g-39); }
426 else if(g==44) { *gx=5; *gy=8; }
427 else if(g<=49) { *gx=4-(g-45); *gy=8; }
428 else if(g==50) { *gx=0; *gy=7; }
429 else if(g==51) { *gx=0; *gy=6; }
430}
431
432int countMovablePieces(int p, int d) {
433 int c=0;
434 for(int i=0; i<4; i++) {
435 int pos = pPos[p][i];
436 if((pos==-1 && d==6) || (pos!=-1 && pos+d<=57)) c++;
437 }
438 return c;
439}
440
441int getFirstMovable(int p, int d) {
442 for(int i=0; i<4; i++) {
443 int pos = pPos[p][i];
444 if((pos==-1 && d==6) || (pos!=-1 && pos+d<=57)) return i;
445 }
446 return 0;
447}
448
449int getTouchedPiece(int x, int y) {
450 for(int i=0; i<4; i++) {
451 int pos = pPos[turn][i];
452 int px, py;
453 if(pos==-1) {
454 int bx = (turn==1||turn==2)?144:0; int by = (turn==2||turn==3)?144:0;
455 int ox = (i%2)*30 + 33; int oy = (i/2)*30 + 33;
456 px=bx+ox; py=by+oy;
457 } else {
458 int gx, gy;
459 getGridCoord(turn, pos, &gx, &gy);
460 px=gx*CW+8; py=gy*CW+8;
461 }
462 if(abs(x-px) < 12 && abs(y-py) < 12) return i;
463 }
464 return -1;
465}
466
467int getGlobalStep(int p, int s) {
468 int off = p * 13;
469 return (s + off) % 52;
470}
471
472void drawMenu() {
473 tft.fillScreen(BLACK);
474
475 tft.setTextSize(4);
476 tft.setCursor(65, 30);
477 tft.setTextColor(YELLOW);
478 tft.print("LUDO PRO");
479
480 // BIG BUTTONS
481 tft.fillRect(60, 80, 200, 40, BLUE);
482 tft.drawRect(60, 80, 200, 40, WHITE);
483 tft.setCursor(105, 92);
484 tft.setTextColor(WHITE);
485 tft.setTextSize(2);
486 tft.print("2 PLAYERS");
487
488 tft.fillRect(60, 130, 200, 40, GREEN);
489 tft.drawRect(60, 130, 200, 40, WHITE);
490 tft.setCursor(105, 142);
491 tft.setTextColor(BLACK);
492 tft.print("3 PLAYERS");
493
494 tft.fillRect(60, 180, 200, 40, RED);
495 tft.drawRect(60, 180, 200, 40, WHITE);
496 tft.setCursor(105, 192);
497 tft.setTextColor(WHITE);
498 tft.print("4 PLAYERS");
499}
500
501void drawGameOverScreen() {
502 currentState = GAME_OVER;
503 tft.fillScreen(BLACK);
504 tft.setTextSize(3); tft.setCursor(40, 30); tft.setTextColor(YELLOW); tft.print("GAME OVER");
505 tft.setTextSize(2);
506 int y = 80;
507 for(int i=0; i<totalPlayers; i++) {
508 int pID = winOrder[i];
509 if (pID == -1) continue;
510 uint16_t c = (pID==0)?RED : (pID==1)?BLUE : (pID==2)?YELLOW : GREEN;
511 tft.setCursor(40, y); tft.setTextColor(WHITE);
512 if (i==0) tft.print("1st: "); else if (i==1) tft.print("2nd: ");
513 else if (i==2) tft.print("3rd: "); else tft.print("4th: ");
514 tft.setTextColor(c);
515 if(pID==0) tft.print("RED"); else if(pID==1) tft.print("BLUE");
516 else if(pID==2) tft.print("YELLOW"); else tft.print("GREEN");
517 y += 40;
518 }
519 tft.setCursor(40, 210); tft.setTextColor(WHITE); tft.setTextSize(1); tft.print("TAP TO RESTART");
520}
521
522bool touch(int *x, int *y) {
523 TSPoint p = ts.getPoint();
524 pinMode(XM, OUTPUT); pinMode(YP, OUTPUT);
525 if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
526 int rawX = map(p.x, TS_MINX, TS_MAXX, 320, 0);
527 int rawY = map(p.y, TS_MINY, TS_MAXY, 0, 240);
528 if(rawX < 0) rawX = 0; if(rawX > 320) rawX = 320;
529 if(rawY < 0) rawY = 0; if(rawY > 240) rawY = 240;
530 *x = rawX; *y = rawY;
531 return true;
532 }
533 return false;
534}