This is an old revision of the document!
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
<file PM_project>
#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);
}
<\file>