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

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");
  }
}

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.txt · Last modified: 2024/05/27 02:10 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