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>