Proiectul consta in implementarea unei variante mai noi a jocului X si O, unde la cateva miscari realizate, una dintre ele este stearsa. Pe langa joc, proiectul va avea si un speaker pe care se va reda muzica.
Am descoperit aceasta varianta a jocului pe internet si mi-am dorit sa o implementez. De asemenea, doresc sa imi imbunatatesc abilitatile de game design si mi s-a parut o idee adecvata pentru acest lucru.
Update: vreau sa fac cadou acest joc nepotului meu
Ca µC am folosit un Arduino UNO R3 si un Arduino NANO. Pentru input-uri (cum ar fi selectarea miscarii, confirmarea miscarii) folosesc butoane, pentru redarea muzicii am un cititor de card microSD(input) si un difuzor(output) & interfata grafica se va realiza pe un ecran LCD. In timpul dezvoltarii fazei software a proiectului am intampinat probleme si astfel am avut nevoie de folosirea a celui de-al doilea µC(arduino NANO) pentru redarea muzicii.
Lista componente
1 x Arduino UNO R3
1 x Arduino NANO
2 x mini breadboard
5 x Butoane
1 x LCD 1.44” SPI si controller ST7735
1 x modul cititor card microSD/SDHC
1 x card SDHC
1 x Speaker
1 x amplificator audio mono XPT8871
? x fire tata-tata si mama-tata
Schema cablaj:
Schema electrica:
Poza cablaj:
Tin sa mentionez ca in schema cablajului nu este acelasi amplificator audio pe care il folosesc pentru ca nu am gasit schema exacta a componentei, dar este unul similar.
pinuri digitale:
SCK - pin 13 pentru microSD card reader si LCD
MOSI - pin 11 pentru microSD card reader si LCD
pin 4 - CS microSD card reader
pin 8 - RES LCD
pin 9 - DC LCD
pin 9 - Speaker (pe arduino nano)
pin 10 - CS LCD
pinuri analog:
pin A0,A1,A2,A3,A4: pinii pentru butoane
Codul pentru joc:
#include <Adafruit_GFX.h> #include <Adafruit_ST7735.h> #include <SPI.h> // Definirea pinilor pentru LCD #define TFT_CS 10 #define TFT_RST 8 #define TFT_DC 9 // Definirea pinilor pentru butoane #define BUTTON_UP A0 #define BUTTON_DOWN A4 #define BUTTON_LEFT A1 #define BUTTON_RIGHT A2 #define BUTTON_SELECT A3 // Crearea unui obiect Adafruit_ST7735 pentru ecranul LCD Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // Dimensiuni grilă #define GRID_SIZE 3 #define CELL_SIZE 30 // Debounce unsigned long lastDebounceTime = 0; unsigned long debounceDelay = 200; // Starea jocului char board[GRID_SIZE][GRID_SIZE]; int currentPlayer = 0; // 0 pentru X, 1 pentru O int currentRow = 0, currentCol = 0; // Structuri pentru a ține evidența mutărilor fiecărui jucător struct Move { int row; int col; }; Move movesX[3]; Move movesO[3]; int moveCountX = 0; int moveCountO = 0; // Calcularea coordonatelor pentru centrare int gridStartX = (tft.width() - (GRID_SIZE * CELL_SIZE)) / 2; int gridStartY = (tft.height() - (GRID_SIZE * CELL_SIZE) - 40) / 2; bool gameWon = false; bool alreadyDisplayed = false; unsigned long winTime; void setup() { Serial.begin(9600); // Inițializarea LCD-ului tft.initR(INITR_BLACKTAB); tft.fillScreen(ST77XX_BLACK); // Curățarea ecranului și umplerea cu negru // Inițializarea pinilor pentru butoane pinMode(BUTTON_UP, INPUT_PULLUP); pinMode(BUTTON_DOWN, INPUT_PULLUP); pinMode(BUTTON_LEFT, INPUT_PULLUP); pinMode(BUTTON_RIGHT, INPUT_PULLUP); pinMode(BUTTON_SELECT, INPUT_PULLUP); // Inițializarea tablei de joc for (int i = 0; i < GRID_SIZE; i++) { for (int j = 0; j < GRID_SIZE; j++) { board[i][j] = ' '; } } // Desenarea grilei drawGrid(); highlightCell(currentRow, currentCol, true); } void loop() { if (gameWon) { if (millis() - winTime > 10000) { // Așteaptă 10 secunde înainte de resetare resetBoard(); gameWon = false; alreadyDisplayed = false; tft.fillScreen(ST77XX_BLACK); drawGrid(); } else { if (!alreadyDisplayed) { char winner; if (currentPlayer == 0) { winner = 'X'; } else { winner = 'O'; } displayWinner(winner); alreadyDisplayed = true; currentPlayer = 0; } } return; } else { readButtons(); // Evidențiere celula curentă highlightCell(currentRow, currentCol, true); } } void readButtons() { if (millis() - lastDebounceTime > debounceDelay) { if (digitalRead(BUTTON_UP) == LOW) { lastDebounceTime = millis(); moveCursor(0, -1); // Muta sus } if (digitalRead(BUTTON_DOWN) == LOW) { lastDebounceTime = millis(); moveCursor(0, 1); // Muta jos } if (digitalRead(BUTTON_LEFT) == LOW) { lastDebounceTime = millis(); moveCursor(-1, 0); // Muta stânga } if (digitalRead(BUTTON_RIGHT) == LOW) { lastDebounceTime = millis(); moveCursor(1, 0); // Muta dreapta } if (digitalRead(BUTTON_SELECT) == LOW) { lastDebounceTime = millis(); placeMark(); // Selecteaza miscare } } } void drawGrid() { for (int i = 1; i < GRID_SIZE; i++) { tft.drawLine(gridStartX + CELL_SIZE * i, gridStartY, gridStartX + CELL_SIZE * i, gridStartY + GRID_SIZE * CELL_SIZE, ST77XX_WHITE); tft.drawLine(gridStartX, gridStartY + CELL_SIZE * i, gridStartX + GRID_SIZE * CELL_SIZE, gridStartY + CELL_SIZE * i, ST77XX_WHITE); } } void highlightCell(int row, int col, bool highlight) { // Curățăm celula anterioară (dacă există) for (int i = 0; i < GRID_SIZE; i++) { for (int j = 0; j < GRID_SIZE; j++) { if (i == row && j == col) continue; int x = gridStartX + j * CELL_SIZE; int y = gridStartY + i * CELL_SIZE; tft.drawRect(x, y, CELL_SIZE, CELL_SIZE, ST77XX_WHITE); if (board[i][j] != ' ') { drawMark(i, j, board[i][j]); } } } // Evidențiem celula curentă int x = gridStartX + col * CELL_SIZE; int y = gridStartY + row * CELL_SIZE; tft.drawRect(x, y, CELL_SIZE, CELL_SIZE, highlight ? ST77XX_BLUE : ST77XX_WHITE); // Redesenăm conținutul celulei după evidențiere pentru a nu o pierde if (board[row][col] != ' ') { drawMark(row, col, board[row][col]); } } void moveCursor(int dx, int dy) { currentCol = (currentCol + dx + GRID_SIZE) % GRID_SIZE; currentRow = (currentRow + dy + GRID_SIZE) % GRID_SIZE; } void placeMark() { if (board[currentRow][currentCol] == ' ') { char mark = currentPlayer == 0 ? 'X' : 'O'; board[currentRow][currentCol] = mark; drawMark(currentRow, currentCol, mark); // Adaugă mutarea la istoricul mutărilor jucătorului curent if (currentPlayer == 0) { if (moveCountX >= 3) { Move oldestMove = movesX[moveCountX % 3]; board[oldestMove.row][oldestMove.col] = ' '; drawMark(oldestMove.row, oldestMove.col, ' '); } movesX[moveCountX % 3] = {currentRow, currentCol}; moveCountX++; } else { if (moveCountO >= 3) { Move oldestMove = movesO[moveCountO % 3]; board[oldestMove.row][oldestMove.col] = ' '; drawMark(oldestMove.row, oldestMove.col, ' '); } movesO[moveCountO % 3] = {currentRow, currentCol}; moveCountO++; } // Verifică dacă jucătorul curent a câștigat if (checkWin(currentRow, currentCol, mark)) { winTime = millis(); // Setează timpul de câștig gameWon = true; // Setează starea de câștig return; } // Schimbă jucătorul currentPlayer = 1 - currentPlayer; displayCurrentPlayer(); } } void drawMark(int row, int col, char mark) { int x = gridStartX + col * CELL_SIZE + CELL_SIZE / 2 - 5; int y = gridStartY + row * CELL_SIZE + CELL_SIZE / 2 - 8; tft.setCursor(x, y); tft.setTextColor(ST77XX_WHITE); tft.setTextSize(2); if (mark == ' ') { // Curățăm marca tft.fillRect(gridStartX + col * CELL_SIZE + 1, gridStartY + row * CELL_SIZE + 1, CELL_SIZE - 2, CELL_SIZE - 2, ST77XX_BLACK); } else { tft.print(mark); } } bool checkWin(int row, int col, char mark) { // Verifică rândul if (board[row][0] == mark && board[row][1] == mark && board[row][2] == mark) return true; // Verifică coloana if (board[0][col] == mark && board[1][col] == mark && board[2][col] == mark) return true; // Verifică diagonalele if (board[0][0] == mark && board[1][1] == mark && board[2][2] == mark) return true; if (board[0][2] == mark && board[1][1] == mark && board[2][0] == mark) return true; return false; } void displayWinner(char winner) { tft.fillScreen(ST77XX_BLACK); // Definim variabilele pentru a stoca limitele textului int16_t x1, y1; uint16_t w, h; // Creăm textul complet care va fi afișat String text = "Player "; text += winner; text += " won!"; // Setăm dimensiunea textului tft.setTextSize(1); // Obținem limitele textului tft.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h); // Calculăm pozițiile cursorului pentru a centra textul int16_t centeredX = (tft.width() - w) / 2; int16_t centeredY = (tft.height() - h) / 2; // Setăm culoarea textului tft.setTextColor(ST77XX_WHITE); // Setăm cursorul la poziția calculată tft.setCursor(centeredX, centeredY); // Afișăm textul tft.print(text); // Coordonatele centrului smiley face-ului int16_t smileyX = tft.width() / 2; int16_t smileyY = centeredY / 2; // Poziționăm smiley face-ul deasupra textului // Dimensiunea smiley face-ului int16_t radius = 20; // Desenam smiley face-ul tft.drawCircle(smileyX, smileyY, radius, ST77XX_WHITE); tft.fillCircle(smileyX - radius / 3, smileyY - radius / 3, 2, ST77XX_WHITE); tft.fillCircle(smileyX + radius / 3, smileyY - radius / 3, 2, ST77XX_WHITE); drawArc(smileyX, smileyY, radius - 5, 0, 180, ST77XX_WHITE); } void drawArc(int16_t x, int16_t y, int16_t radius, int16_t startAngle, int16_t endAngle, uint16_t color) { for (int16_t angle = startAngle; angle <= endAngle; angle++) { float rad = angle * 3.14 / 180; int16_t x1 = x + cos(rad) * radius; int16_t y1 = y + sin(rad) * radius; tft.drawPixel(x1, y1, color); } } void resetBoard() { for (int i = 0; i < GRID_SIZE; i++) { for (int j = 0; j < GRID_SIZE; j++) { board[i][j] = ' '; } } moveCountX = 0; moveCountO = 0; drawGrid(); highlightCell(currentRow, currentCol, true); } void displayCurrentPlayer() { // Șterge zona jucătorului curent tft.fillRect(0, GRID_SIZE * CELL_SIZE + gridStartY, tft.width(), 20, ST77XX_BLACK); // Definim variabilele pentru a stoca limitele textului int16_t x1, y1; uint16_t w, h; // Creăm textul complet care va fi afișat String text = "Player: "; text += (currentPlayer == 0) ? 'X' : 'O'; // Setăm dimensiunea textului tft.setTextSize(1); // Obținem limitele textului tft.getTextBounds(text.c_str(), 0, 0, &x1, &y1, &w, &h); // Calculăm pozițiile cursorului pentru a centra textul int16_t centeredX = (tft.width() - w) / 2; int16_t centeredY = gridStartY + GRID_SIZE * CELL_SIZE + 5; // Setăm cursorul la poziția calculată tft.setCursor(centeredX, centeredY); // Setăm culoarea textului tft.setTextColor(ST77XX_WHITE); // Afișăm textul tft.print(text); }
Codul pentru muzica:
#include <SPI.h> #include <SD.h> #include <TMRpcm.h> #define CS_SD 4 #define Speaker 9 TMRpcm audio; void setup() { Serial.begin(9600); if(!SD.begin(CS_SD)) { Serial.println("A esuat initializarea cardului SD"); while(1); } audio.CSPin = CS_SD; audio.speakerPin = Speaker; audio.setVolume(6); } void loop() { if (!audio.isPlaying()) { audio.play("powerup.wav"); } }