X și 0

Introducere

Cunoscut încă din vremea Egiptului Antic, jocul de X și 0 este cu siguranță unul dintre cele mai populare din toate timpurile. Totuși, mijloacele prin care acesta poate fi jucat sunt foarte variate, așa că, în cadrul acestui proiect, mi-am propus să implementez un dispozitiv care să poată fi utilizat pentru X și 0.

Descriere generală

Grid-ul este reprezentat cu ajutorul unei matrice de LED-uri RGB, fiecare jucător având o culoare (în loc de un simbol - “X” sau “0”) pe care o alege la începutul jocului dintre culorile predefinite (roșu, verde, albastru, galben, magenta și cyan). Jucătorii pot alege căsuța în care introduc culoarea lor prin intermediul butoanelor (”←”, ”→” și “ok”). Există, de asemenea, și un display LCD pe care sunt afișate scorul și diverse indicații în fiecare etapă a jocului. În timpul jocului, marginea grid-ului pulsează în culoarea jucătorului al cărui rând este.

Schema bloc

Hardware Design

Componenta Cantitatea Observații
plăcuță Arduino 1
matrice cu LED-uri RGB 1
buton 3
display LCD 1
potențiometru 1 10kΩ
rezistență 1 220Ω
fire
breadboard 1

Schema electrică

Mai multe poze

Mai multe poze

Software Design

Diagrame de activitate

Verificarea input-ului de la butoane

Verificarea input-ului de la butoane

Actualizarea stării jocului

Actualizarea stării jocului

Check for winner

Check for winner


Justificarea selecției culorilor predefinite
Motivul pentru care am ales ca cele 6 culori predefinite să fie roșu, verde, albastru, galben, magenta și cyan este faptul că, în compoziția acestora, raportul dintre valorile nenule ale componentelor R, G și B este mereu 1. Astfel, pentru a face marginea grid-ului să pulseze în culoarea unuia dintre jucători, este suficient să fie modificat un factor de intensitate care să fie apoi înmulțit cu valoarea fiecărei componente - 1 dacă aceasta face parte din compoziția culorii, 0 altfel.

De exemplu, pentru cyan avem R = 0, G = 1, B = 1, iar culoarea marginii în fiecare moment va fi
R = 0 * FIC, G = 1 * FIC, B = 1 * FIC, adică
R = 0, G = FIC, B = FIC, unde FIC = factorul de intensitate curent.

Mecanismul de cooldown al butoanelor
Introducerea cooldown-ului a fost făcută pentru a face debouncing butoanelor și a evita astfel înregistrarea mai multor apăsări în locul uneia.
Pentru a implementa acest mecanism se folosește o variabilă auxiliară (button_cooldown). În momentul înregistrării unei apăsări se verifică valoarea variabilei de cooldown și, dacă aceasta este pozitivă, apăsarea este ignorată. Dacă apăsarea este considerată validă, atunci valoarea button_cooldown este resetată la maxim (200). Decrementarea variabilei se face la fiecare pas (la finalul funcției loop). Cu alte cuvinte, între două apăsări valide este necesar să treacă 200 ms.

Biblioteci folosite

Cod sursă

Cod sursă

src.c
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal.h>
 
#define LED_PIN    6
#define LED_COUNT 64
 
#define LEDS_ON_LINE 8
#define LEDS_ON_COL 8
 
#define LINES 3
#define COLS 3
 
#define CRT_PLAYER_FACT_MAX 30
#define CRT_PLAYER_FACT_MIN 0
 
#define BUTTON_LEFT 9
#define BUTTON_OK 8
#define BUTTON_RIGHT 7
#define BUTTON_COOLDOWN 200
 
int button_cooldown;
 
#define P1 0
#define P2 1
#define EMPTY 2
 
int crt_player;
int crt_player_fact;
int crt_player_dir;
int crt_line;
int crt_col;
int col_players[2];
int grid[LINES][COLS] = {EMPTY, EMPTY, EMPTY,
                         EMPTY, EMPTY, EMPTY,
                         EMPTY, EMPTY, EMPTY};
int score[2];
 
char col[6][8] = {"red    ", "green  ", "blue   ", "yellow ", "magenta", "cyan   "};
int rgb[6][3] = { 1, 0, 0, // red
                  0, 1, 0, // green
                  0, 0, 1, // blue
                  1, 1, 0, // yellow
                  1, 0, 1, // magenta
                  0, 1, 1}; // cyan
 
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
 
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
 
void game_start();
void restart();
void pick_color();
 
void setup() {
  // led matrix setup
  // Initialize all pixels to 'off'
  strip.begin();
  strip.show();
 
  // game setup
  crt_player = P1;
  crt_player_fact = 0;
  crt_player_dir = 1;
  crt_line = 0;
  crt_col = 0;
 
  // lcd display setup
  lcd.begin(16, 2);
  lcd.clear();
  lcd.print("Hello!");
 
  // buttons setup
  pinMode(BUTTON_LEFT, INPUT_PULLUP);
  pinMode(BUTTON_OK, INPUT_PULLUP);
  pinMode(BUTTON_RIGHT, INPUT_PULLUP);
  button_cooldown = 0;
 
  pick_color();
  game_start();
}
 
void color_border();
void update_crt_player_factor();
void color_grid();
void blink_crt();
 
void right_push();
void left_push();
void ok_push();
void switch_crt_player();
void celebrate(int winner);
void draw();
void check_for_winner();
 
void loop() {
  if (button_cooldown <= 0 && digitalRead(BUTTON_LEFT) == 0) {
    left_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
  if (button_cooldown <= 0 && digitalRead(BUTTON_OK) == 0) {
    ok_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
  if (button_cooldown <= 0 && digitalRead(BUTTON_RIGHT) == 0) {
    right_push();
    button_cooldown = BUTTON_COOLDOWN;
  }
 
  color_border();
  update_crt_player_factor();
  color_grid();
  blink_crt();
 
  strip.show();
 
  delay(50);
  if (button_cooldown > 0) {
    button_cooldown -= 50;
  }
 
  check_for_winner();
}
 
//////////////////////////////////////////////////////////////////////////////////
 
void game_start() {
  lcd.clear();
  lcd.print("Game starts!");
  lcd.setCursor(0, 1);
  lcd.print("Good luck!");
  delay(3000);
}
 
void restart() {
  // led matrix setup
  // Initialize all pixels to 'off'
  for (int j = 0; j < LED_COUNT; j++)
      strip.setPixelColor(j, 0, 0, 0);
  strip.show();
 
  // game setup
  crt_player = P1;
  crt_player_fact = 0;
  crt_player_dir = 1;
  crt_line = 0;
  crt_col = 0;
 
  for (int i = 0; i < LINES; i++)
    for (int j = 0; j < COLS; j++)
      grid[i][j] = EMPTY;
 
  button_cooldown = 0;
 
  pick_color();
  game_start();
}
 
void pick_color() {
  for (int i = 0; i < 2; i++) {
    col_players[i] = 0;
    lcd.clear();
    lcd.print(String("Player ") + String(i + 1) + String(" color:"));
 
    int crt_col = 0;
    if (i == 1 && crt_col == col_players[0]) {
      crt_col = (crt_col - 1 + 6) % 6;
    }
    while (1) {
      lcd.setCursor(0, 1);
      lcd.print(col[crt_col]);
 
      if (digitalRead(BUTTON_OK) == 0) {
        col_players[i] = crt_col;
        delay(BUTTON_COOLDOWN);
        break;
      }
      if (digitalRead(BUTTON_LEFT) == 0) {
        crt_col = (crt_col - 1 + 6) % 6;
        if (i == 1 && crt_col == col_players[0]) {
          crt_col = (crt_col - 1 + 6) % 6;
        }
      }
      if (digitalRead(BUTTON_RIGHT) == 0) {
        crt_col = (crt_col + 1) % 6;
        if (i == 1 && crt_col == col_players[0]) {
          crt_col = (crt_col + 1) % 6;
        }
      }
      delay(BUTTON_COOLDOWN);
    }
  }
}
 
//////////////////////////////////////////////////////////////////////////////////
 
void color_border() {
  int r, g, b;
 
  r = rgb[col_players[crt_player]][0];
  g = rgb[col_players[crt_player]][1];
  b = rgb[col_players[crt_player]][2];
 
  // upper border
  for (int i = 0; i < LEDS_ON_COL; i++) {
    strip.setPixelColor(i, r * crt_player_fact,
                        g * crt_player_fact, b * crt_player_fact);
  }
 
  // lower border
  for (int i = 0; i < LEDS_ON_COL; i++) {
    strip.setPixelColor((LEDS_ON_COL - 1) * LEDS_ON_LINE + i,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
 
  // left border
  for (int i = 1; i < LEDS_ON_LINE; i++) {
    strip.setPixelColor(i * LEDS_ON_LINE,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
 
  // right border
  for (int i = 1; i < LEDS_ON_LINE; i++) {
    strip.setPixelColor(i * LEDS_ON_LINE + LEDS_ON_LINE - 1,
                        r * crt_player_fact,
                        g * crt_player_fact,
                        b * crt_player_fact);
  }
}
 
void update_crt_player_factor() {
  if (crt_player_fact + crt_player_dir <= CRT_PLAYER_FACT_MAX &&
      crt_player_fact + crt_player_dir >= CRT_PLAYER_FACT_MIN) {
        crt_player_fact += crt_player_dir;
  } else {
    crt_player_dir *= -1;
  }
}
 
void color_grid() {
 int r, g, b;
  for (int line = 0; line < LINES; line++) {
    for (int col = 0; col < COLS; col++) {
      int line_index = line * 2 + 1;
      int col_index = col * 2 + 1;
      int elem1 = line_index * LEDS_ON_LINE + col_index;
      int elem2 = elem1 + 1;
      int elem3 = elem1 + LEDS_ON_LINE;
      int elem4 = elem3 + 1;
 
      if (grid[line][col] == P1) {
        r = 20 * rgb[col_players[P1]][0];
        g = 20 * rgb[col_players[P1]][1];
        b = 20 * rgb[col_players[P1]][2];
      } else if (grid[line][col] == P2){
        r = 20 * rgb[col_players[P2]][0];
        g = 20 * rgb[col_players[P2]][1];
        b = 20 * rgb[col_players[P2]][2];
      } else {
        r = g = b = 0;
      }
      strip.setPixelColor(elem1, r, g, b);
      strip.setPixelColor(elem2, r, g, b);
      strip.setPixelColor(elem3, r, g, b);
      strip.setPixelColor(elem4, r, g, b);
    }
  }
}
 
void blink_crt() {
  int line_index = crt_line * 2 + 1;
  int col_index = crt_col * 2 + 1;
  int elem1 = line_index * LEDS_ON_LINE + col_index;
  int elem2 = elem1 + 1;
  int elem3 = elem1 + LEDS_ON_LINE;
  int elem4 = elem3 + 1;
 
  if (crt_player_fact % 10) {
    strip.setPixelColor(elem1, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem2, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem3, crt_player_fact, crt_player_fact, crt_player_fact);
    strip.setPixelColor(elem4, crt_player_fact, crt_player_fact, crt_player_fact);
  } else {
    strip.setPixelColor(elem1, 0, 0, 0);
    strip.setPixelColor(elem2, 0, 0, 0);
    strip.setPixelColor(elem3, 0, 0, 0);
    strip.setPixelColor(elem4, 0, 0, 0);
  }
}
 
void right_push() {
  crt_col++;
  if (crt_col == COLS) {
    crt_col = 0;
    crt_line++;
  }
  if (crt_line == LINES) {
    crt_line = 0;
  }
}
 
void left_push() {
  crt_col--;
  if (crt_col == -1) {
    crt_col = COLS - 1;
    crt_line--;
  }
  if (crt_line == -1) {
    crt_line = LINES - 1;
  }
}
 
void ok_push() {
  if (grid[crt_line][crt_col] == EMPTY) {
    grid[crt_line][crt_col] = crt_player;
    switch_crt_player();
    crt_line = 0;
    crt_col = 0;
  }
}
 
void switch_crt_player() {
  if (crt_player == P1) {
    crt_player = P2;
  } else {
    crt_player = P1;
  }
}
 
void celebrate(int winner) {
  lcd.clear();
  lcd.print(String("Player ") + String(winner + 1) + String(" wins!"));
 
  for (int i = 0; i < LED_COUNT; i++)
    strip.setPixelColor(i, 0, 0, 0);
 
  int r = rgb[col_players[winner]][0] * 20;
  int g = rgb[col_players[winner]][1] * 20;
  int b = rgb[col_players[winner]][2] * 20;
 
  score[winner]++;
 
  // drawing
  strip.setPixelColor(2, r, g, b);
  strip.setPixelColor(3, r, g, b);
  strip.setPixelColor(4, r, g, b);
  strip.setPixelColor(5, r, g, b);
 
  strip.setPixelColor(9, r, g, b);
  strip.setPixelColor(14, r, g, b);
 
  strip.setPixelColor(16, r, g, b);
  strip.setPixelColor(18, r, g, b);
  strip.setPixelColor(21, r, g, b);
  strip.setPixelColor(23, r, g, b);
 
  strip.setPixelColor(24, r, g, b);
  strip.setPixelColor(31, r, g, b);
 
  strip.setPixelColor(32, r, g, b);
  strip.setPixelColor(34, r, g, b);
  strip.setPixelColor(37, r, g, b);
  strip.setPixelColor(39, r, g, b);
 
  strip.setPixelColor(40, r, g, b);
  strip.setPixelColor(43, r, g, b);
  strip.setPixelColor(44, r, g, b);
  strip.setPixelColor(47, r, g, b);
 
  strip.setPixelColor(49, r, g, b);
  strip.setPixelColor(54, r, g, b);
 
  strip.setPixelColor(58, r, g, b);
  strip.setPixelColor(59, r, g, b);
  strip.setPixelColor(60, r, g, b);
  strip.setPixelColor(61, r, g, b);
 
  strip.show();
  delay(3000);
 
  lcd.clear();
  lcd.print(String("Player 1: ") + String(score[P1]));
  lcd.setCursor(0, 1);
  lcd.print(String("Player 2: ") + String(score[P2]));
 
  delay(10000);
}
 
void draw() {
  lcd.clear();
  lcd.print("Game over!");
  lcd.setCursor(0, 1);
  lcd.print("Draw");
 
  // drawing
  int r1 = rgb[col_players[0]][0] * 30;
  int r2 = rgb[col_players[1]][0] * 30;
  int g1 = rgb[col_players[0]][1] * 30;
  int g2 = rgb[col_players[1]][1] * 30;
  int b1 = rgb[col_players[0]][2] * 30;
  int b2 = rgb[col_players[1]][2] * 30;
 
  for (int i = 0; i < LED_COUNT; i++)
    strip.setPixelColor(i, 0, 0, 0);
 
  // X
  strip.setPixelColor(0, r1, g1, b1);
  strip.setPixelColor(3, r1, g1, b1);
  strip.setPixelColor(9, r1, g1, b1);
  strip.setPixelColor(10, r1, g1, b1);
  strip.setPixelColor(17, r1, g1, b1);
  strip.setPixelColor(18, r1, g1, b1);
  strip.setPixelColor(24, r1, g1, b1);
  strip.setPixelColor(27, r1, g1, b1);
 
  // 0
  strip.setPixelColor(37, r2, g2, b2);
  strip.setPixelColor(38, r2, g2, b2);
  strip.setPixelColor(44, r2, g2, b2);
  strip.setPixelColor(47, r2, g2, b2);
  strip.setPixelColor(52, r2, g2, b2);
  strip.setPixelColor(55, r2, g2, b2);
  strip.setPixelColor(61, r2, g2, b2);
  strip.setPixelColor(62, r2, g2, b2);
 
  // diag
  for (int i = 0; i < LEDS_ON_COL; i++)
    strip.setPixelColor((i + 1) * LEDS_ON_LINE - i - 1, 10, 10, 10);
 
 
  strip.show();
  delay(3000);
 
  lcd.clear();
  lcd.print(String("Player 1: ") + String(score[P1]));
  lcd.setCursor(0, 1);
  lcd.print(String("Player 2: ") + String(score[P2]));
 
  delay(10000);
}
 
void check_for_winner() {
  int found = -1;
  int done = 1;
 
  // check lines
  for (int i = 0; i < LINES && found == -1; i++) {
    if (grid[i][0] != EMPTY && grid[i][0] == grid[i][1] &&
        grid[i][0] == grid[i][2])
      found = grid[i][0];
  }
 
  // check cols
  for (int i = 0; i < COLS && found == -1; i++) {
    if (grid[0][i] != EMPTY && grid[0][i] == grid[1][i] &&
        grid[0][i] == grid[2][i])
      found = grid[0][i];
  }
 
  // check diags
  if (found == -1 && grid[0][0] != EMPTY &&
      grid[0][0] == grid[1][1] && grid[0][0] == grid[2][2])
    found = grid[0][0];
  if (found == -1 && grid[0][2] != EMPTY &&
      grid[0][2] == grid[1][1] && grid[0][2] == grid[2][0])
    found = grid[0][2];
 
  if (found != -1) {
    celebrate(found);
    restart();
  } else {
    for (int i = 0; i < LINES; i++) {
      for (int j = 0; j < COLS; j++) {
        if (grid[i][j] == EMPTY) {
          done = 0;
        }
      }
    }
 
    if (done) {
      draw();
      restart();
    }
  }
}

Rezultate Obţinute

Jocul în așteptarea următoarei mutări a jucătorului care a ales culoarea roșie: marginile pulsează în culoarea acestuia, iar căsuța cu selecția curentă (stânga sus) licărește cu lumină albă.

Desenul afișat în cazul în care jucătorul care a ales culoarea verde câștigă:

Desenul afișat în cazul unei remize (X în culoarea aleasă de primul jucător și 0 în culoarea aleasă de cel de-al doilea):

Video

Download

Arhiva cu codul sursă:

x-si-0.zip

Slide-uri de prezentare:

prezentare.zip

Bibliografie/Resurse

pm/prj2022/abirlica/x-si-0.txt · Last modified: 2022/06/02 02:01 by maria.mosneag
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