This is an old revision of the document!


Cristea Marius-Cristian : Upgraded X și O

Introducere

Nume: Cristea Marius-Cristian
Grupa: 333 CB
Indrumator: Daniel Dosaru

Descriere

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.

Motivatie

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 :-)

Descriere generală

In cele ce urmeaza voi detalia schema bloc a proiectului

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.

Hardware Design

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.

Conexiuni pini

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

Software Design

  • mediu de dezvoltare: Arduino IDE
  • librarii: Adafruit_GFX; Adafruit_ST7735; TMRpcm

<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>

Rezultate Obţinute

Acesta este rezultatul final al proiectului. Am facut cutia astfel incat sa se poata deschida pentru a se vedea cablajul. LCD-ul si butoanele se pot inlatura sau baga in cutie.

pm/prj2024/ddosaru/marius.cristea1811.1716764948.txt.gz · Last modified: 2024/05/27 02:09 by marius.cristea1811
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0