Arduino Electronic Board Game


from Makezine

In 2015, my son had to make a board game for a middle school science fair, and the theme was Ancient Egypt. I just figured it out was an opportunity to introduce him to electronics. 
My initial suggestion was to replace the conventional dice with an electronic dice, but my kid and his classmates are very creative, so when their ideas began to emerge, I didn’t want to limit their ideas. Therefore, the things became a little more complex.



They did the project concept, game and enclosure design and I helped them with my electronics expertise.



The brain of this project is an Arduino Nano, that controls some other modules and circuits (display, MP3 player, buttons, and LEDs).

Just like any schoolwork, they had to do a lot of research about ancient Egyptian history, because they needed to formulate a lot of questions for the quiz that was part of the game. They also had a lot of work to drawing the map of the board, especially considering they used MS-Word to do it. It was printed on vinyl adhesive.​


For the enclosure design, they were inspired by pinball machines. They wanted to make it with a large area for the map and an inclined display, which also hides the pharaoh’s sarcophagus, and when a player reaches the goal, some LEDs are lit, revealing the "secret treasure".




The enclosure was made of plastic (polyethylene) and acrylic sheets, due to its ease of cutting and folding.




The circuit design was created with Fritzing software. I do not like to use jumpers, so usually I make double-sided PCBs. But, as the time was short, I chose to simplify making single-side PCB, using toner transfer method.



This project has 24 LEDs, so I had to use a multiplexer IC. The MAX7219 solved the LEDs problem, but caused a loud noise to come from the speakers. To fix this issue the MAX7219 was replaced by its “brother” MAX7221, which has protection against electro magnetic interference (EMI).


 

The questions of the quiz are shown in a 20×4 LCD display as well as played through the speakers with the kids’ voices, which we recorded and distorted to sound scary like a mummy.

The first programming challenge was to display portuguese accented characters. Despite the fact that the LCD display allowed 8 customizable characters, it wasn’t enough. So to work around this problem I had to dynamically load the custom characters as required.

Another programming challenge I had to overcome was the Arduino 2KB RAM limitation, since I needed lot of memory to hold the quiz strings, which can neither be solved by progmem tricky. I had to store these strings into an external 32 KB EEPROM (Microchip 24LC256). I wrote some Arduino sketches that was executed once, to store these strings into EEPROM, and after it was retrieved by the main program, using their memory addresses.

After all, I think that Arduino Nano was pushed to its limits. More than that, only using an Arduino Mega.

From conception to finish, the project took 30 days.

All information about this project (source-code, PCI design, pictures, etc) are available in GitHub:
https://github.com/marcelomaximiano/BoardGame

Playing game video: https://www.youtube.com/watch?v=Zrz66H8dYvI
Testing session video: https://www.youtube.com/watch?v=dJfBCsZCKEY

Source Code:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "Bounce2.h"

// ***** LCD
#define BACKLIGHT_PIN 5
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address
#include "LedControlMS.h"
#define DataIn_Pin 4
#define Clock_Pin  7
#define Load_Pin   8

// ***** Leds
LedControl lc = LedControl(DataIn_Pin, Clock_Pin, Load_Pin, 1);
#define ledButtonA    17
#define ledButtonB    18
#define ledButtonC    19
#define ledWhite1     20
#define ledWhite2     21
#define ledSnakeRed   22
#define ledSnakeGreen 23

// ***** Buttons
#define buttonRot  A0
#define buttonA    A1
#define buttonB    A2
#define buttonC    A3
#define snakeWire  9
#define snakeStart 10
#define snakeEnd   11
Bounce debouncerRot = Bounce();
Bounce debouncerA   = Bounce();
Bounce debouncerB   = Bounce();
Bounce debouncerC   = Bounce();
Bounce debouncerSW  = Bounce();
Bounce debouncerS1  = Bounce();
Bounce debouncerS2  = Bounce();

// buttons events
#define evtBtnRot    0
#define evtBtnA      1
#define evtBtnB      2
#define evtBtnC      3
#define evtSW        4
#define evtS1Press   5
#define evtS1Release 6
#define evtS2Press   7
#define evtS2Release 8

// ***** Rotary Encoder
int Rot_Pin1 = 12;
int Rot_Pin2 = 2;
int encoderValue = 1;
int lastEncoded = LOW;

// ***** Sound Volume
int soundVolume = 20;  // max 31

// ***** Sound Track
#define soundTrackStart 52
#define soundTrackEnd   58
int soundTrack = soundTrackStart;


// ***** Acentos para mensagens gerais
char strAccents[11] = "          ";

void setup()
{
  Serial.begin(9600);
  Wire.begin();

  uint32_t seed = millis();
  randomSeed(seed);

  lc.shutdown(0, false);
  /* Set the brightness to a medium values */
  lc.setIntensity(0, 12);
  /* and clear the display */
  lc.clearDisplay(0);

  // initialize Rotary Encoder
  pinMode (Rot_Pin1, INPUT);
  pinMode (Rot_Pin2, INPUT);

  // inicializa os botoes
  initButton(buttonRot);
  initButton(buttonA);
  initButton(buttonB);
  initButton(buttonC);
  initButton(snakeWire);
  initButton(snakeStart);
  initButton(snakeEnd);

  // toca o som de introduçao
  getSoundVolume();
  soundPlay(48);
  
  // Switch on the backlight
  pinMode (BACKLIGHT_PIN, OUTPUT);
  digitalWrite(BACKLIGHT_PIN, HIGH);
  lcd.begin(20, 4);              // initialize the lcd
  lcd.clear();
  lcd.setCursor(0, 1);
  displayRomStr(10020);     // O Segredo
  loadChars(strAccents);
  lcd.setCursor(0, 2);
  displayRomStr(10040);     // da Piramide
  delay(3000);
}

// Timers
unsigned long timer1 = 0;
unsigned long timer1_interval = 1000;
unsigned long timer2 = 0;
unsigned long timer2_interval = 300;
unsigned long timer3 = 0;
unsigned long timer3_interval = 20000;
unsigned long timer4 = 0;
unsigned long timer4_interval = 30000;

// etapas do jogo
#define HowManyPlayers 1
#define Begin          2
#define Roulette       3
#define GameOver       4

bool gameOver = false;

int NumberPlayers = 2;
int buttonPressed = -1;

char* players[]   = { "vermelho", "azul", "marrom", "amarelo", "branco" };
int rouletteVal[] = { 5, 4, 0, 3, 2, 1, 3, 6, 1, 3, 2, 4, -1, 0, 6, 1 };
int playersPos[]  = { 0, 0, 0, 0, 0 };
int playersPenalty[]  = { 0, 0, 0, 0, 0 };

int currentPlayer = 0;
int roulettePos = 1;

// niveis de dificuldade das perguntas
#define questionLevel01 1
#define questionLevel02 1
#define questionLevel03 1

void loop()
{
  unsigned long currentMillis = millis();

  // toca o som Numero de Jogadores
  soundPlay(49);
  
  // numero de jogadores
  ligaLed(ledButtonA, true);
  ligaLed(ledButtonB, true);
  ligaLed(ledButtonC, true);

  digitalWrite ( BACKLIGHT_PIN, HIGH );
  loadChars(strAccents);
  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10100);     // Numero de jogadores
  lcd.setCursor(0, 1);
  char str[10];
  sprintf(str, "%1d", NumberPlayers);
  lcd.write(str);
  lcd.setCursor(9, 1);
  lcd.write("A) +");
  lcd.setCursor(9, 2);
  lcd.write("B) -");
  lcd.setCursor(9, 3);
  lcd.write("C) confirma");
  buttonPressed = -1;

  while (true)
  {
    buttonPressed = getButton();

    if (buttonPressed == evtBtnA) {
      NumberPlayers++;
      if (NumberPlayers > 5) {
        NumberPlayers = 5;
      }
    }
    if (buttonPressed == evtBtnB) {
      NumberPlayers--;
      if (NumberPlayers < 2) {
        NumberPlayers = 2;
      }
    }

    if (buttonPressed != -1) {
      lcd.setCursor(0, 1);
      char str[10];
      sprintf(str, "%1d", NumberPlayers);
      lcd.write(str);
    }

    if (buttonPressed == evtBtnC) {
      uint32_t seed = millis();
      randomSeed(seed);
      lcd.clear();
      ligaLed(ledButtonA, false);
      ligaLed(ledButtonB, false);
      ligaLed(ledButtonC, false);
      displayPlayers();
      waitConfirm(3000);
      displayPressToBegin();
      game();
      break;
    }
    buttonPressed = -1;
  }
}

void displayPressToBegin() {
  digitalWrite(BACKLIGHT_PIN, HIGH);

  ligaLed(ledButtonA, false);
  ligaLed(ledButtonB, false);
  ligaLed(ledButtonC, false);

  // toca o som Pressione C para iniciar
  soundPlay(50);

  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10060);     // Pressione C para
  lcd.setCursor(0, 1);
  displayRomStr(10080);     // iniciar a partida

  waitConfirm(0);
}

void game() {
  int rotV = lastEncoded;

  // reinicializa a posição dos jogadores
  for (int i = 0; i <= 4; i++) {
    playersPos[i] = 0;
    playersPenalty[i]  = 0;
  }
  currentPlayer = 0;
  gameOver = false;

  // toca a musica de abertura
  soundTrack = soundTrackStart;
  soundPlay(soundTrack);

  while (true)
  {
    turnOffAllLeds();
    ligaLed(ledButtonA, false);
    ligaLed(ledButtonB, false);
    ligaLed(ledButtonC, true);

    digitalWrite(BACKLIGHT_PIN, HIGH);
    lcd.clear();
    displayPlayer();
    lcd.setCursor(0, 1);
    lcd.write("Gire a roleta!");
    ligaLed(roulettePos, true);

    while (true)
    {
      // **********  Rotary Encoder
      readRotaryEncoder();
      if (rotV != lastEncoded) {
        ringSeq(encoderValue);
      }

      buttonPressed = getButton();

      if ((buttonPressed == evtBtnRot) || (buttonPressed == evtBtnC)) {
        loadChars(strAccents);
        lcd.clear();
        ligaLed(ledButtonA, false);
        ligaLed(ledButtonB, false);
        ligaLed(ledButtonC, false);
        spinRoulette();

        lcd.clear();
        lcd.setCursor(0, 0);
        displayPlayer();

        if (roulettePos < 1) roulettePos = 1;
        if (roulettePos > 16) roulettePos = 16;
        
        int rv = rouletteVal[roulettePos - 1];

        if (rv == -1) {
          playersPos[currentPlayer] = 0;
          lcd.setCursor(0, 1);
          displayRomStr(10180);     // Volte ao inicio!
        }
        else if (rv == 0) {
          lcd.setCursor(0, 1);
          displayRomStr(10200);     // Fique na mesma casa!
        }
        else {
          playersPos[currentPlayer] = playersPos[currentPlayer] + rv;
          if (playersPos[currentPlayer] > 54) {
            playersPos[currentPlayer] = 55;
          }

          lcd.setCursor(0, 1);
          displayRomStr(10220);     // Avance X casa
          char str[10];
          sprintf(str, "%1d", rv);
          lcd.setCursor(7, 1);
          lcd.write(str);
          if (rv > 1) {
            lcd.setCursor(13, 1);
            lcd.write("s");
          }

          waitConfirm(2000);

          switch (playersPos[currentPlayer]) {
            case 5: case 16: case 22: case 31: case 42: case 47: case 52:
              displayGetCard();
              break;
            case 8: case 14:
              snake(50);
              break;
            case 27: case 37:
              snake(40);
              break;
            case 44: case 50:
              snake(30);
              break;
            case 20:
              delay(2000);
              playersPos[currentPlayer] = 12;
              lcd.setCursor(0, 1);
              displayRomStr(10240);     // Volte para a casa 12
              break;
            case 25:
              delay(2000);
              playersPos[currentPlayer] = 33;
              lcd.setCursor(0, 1);
              displayRomStr(10260);     // Avance p/ a casa 33
              break;
          }

          if (playersPos[currentPlayer] == 55) {
            delay(2000);
            // toca a musica final
            soundPlay(64);
            lcd.clear();
            lcd.setCursor(0, 0);
            displayRomStr(10280);     // Parabens!!!
            lcd.setCursor(0, 1);
            displayPlayer();
            lcd.setCursor(0, 2);
            displayRomStr(10300);     // Venceu o jogo !!!
            ligaLed(ledWhite1, true);
            ligaLed(ledWhite2, true);
            gameOver = true;
            delay(5000);
            displayGameOver();
            break;
          }
        }
        waitConfirm(3000);
        displayPos();
        waitConfirm(3000);

        currentPlayer++;
        if (currentPlayer > NumberPlayers - 1) {
          currentPlayer = 0;
        }
        break;
      }
    }
    if (gameOver) {
      break;
    }
  }
  buttonPressed = -1;
}

void snake(int time) {
  loadChars(strAccents);

  // toca o som de introduçao
  soundPlay(60);
  
  turnOffAllLeds();
  ligaLed(ledSnakeGreen, true);

  lcd.clear();
  lcd.setCursor(0, 1);
  displayRomStr(10320);     // Desafio da Cobra
  delay(2000);

  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10660);     // O objetivo é levar
  lcd.setCursor(0, 1);
  displayRomStr(10680);     // a argola até a base
  lcd.setCursor(0, 2);
  displayRomStr(10700);     // vermelha, sem encostar
  lcd.setCursor(0, 3);
  displayRomStr(10720);     // na cobra metálica.
  waitConfirm(5000);

  lcd.clear();
  displayPlayer();
  lcd.write(",");
  lcd.setCursor(0, 1);
  displayRomStr(10340);     // voce tem XX segundos
  lcd.setCursor(9, 1);
  lcd.print(time, DEC);
  lcd.setCursor(0, 2);
  displayRomStr(10360);     // para atravessar o
  lcd.setCursor(0, 3);
  displayRomStr(10380);     // Vale das Cobras !!!
  waitConfirm(5000);

  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10400);     // Para começar
  lcd.setCursor(0, 1);
  displayRomStr(10420);     // encoste a argola na
  lcd.setCursor(0, 2);
  displayRomStr(10440);     // parte metalica da
  lcd.setCursor(0, 3);
  displayRomStr(10460);     // base preta.
  buttonPressed = -1;

  while (true) {
    buttonPressed = getButton();
    if (buttonPressed == evtS1Press) {
      break;
    }
  }

  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10740);     // Pode começar !!!
  buttonPressed = -1;

  while (true) {
    buttonPressed = getButton();
    if (buttonPressed == evtS1Release) {
      break;
    }
  }

  // toca a trilha da cobra
  soundPlay(61);

  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10760);     // Não encoste
  lcd.setCursor(0, 1);
  displayRomStr(10780);     // na cobra!

  unsigned long currentMillis = millis();
  timer2 = currentMillis;
  timer2_interval = 1000;
  buttonPressed = -1;

  while (true) {
    currentMillis = millis();
    buttonPressed = getButton();

    if (buttonPressed == evtS2Press) {
      // toca a trilha da cobra exito
      soundPlay(62);
      playersPos[currentPlayer] = playersPos[currentPlayer] + 1;
      lcd.clear();
      lcd.setCursor(0, 0);
      displayRomStr(10280);     // Parabéns !!!
      lcd.setCursor(0, 1);
      displayRomStr(10800);     // Você conseguiu !!!
      lcd.setCursor(0, 3);
      displayRomStr(10600);     // Avance uma casa
      lcd.write("Avance 1 casa.");
      break;
    }

    if ((buttonPressed == evtSW) || (time < 0)) {
      // toca o som da mordida da cobra
      soundPlay(63);
      playersPos[currentPlayer] = playersPos[currentPlayer] - 1;
      ligaLed(ledSnakeRed, true);
      ligaLed(ledSnakeGreen, false);
      lcd.clear();
      lcd.setCursor(0, 0);
      displayRomStr(10820);     // A cobra mordeu você!
      lcd.setCursor(0, 2);
      displayRomStr(10640);     // Retorne uma casa
      break;
    }

    if (currentMillis - timer2 > timer2_interval) {
      lcd.setCursor(0, 3);
      lcd.write("Tempo : ");
      lcd.print(time, DEC);
      lcd.write(" ");
      timer2 = currentMillis;
      time--;
    }
  }

  waitConfirm(3000);
  turnOffAllLeds();

  // toca a proxima musica
  soundTrack++;
  if (soundTrack > soundTrackEnd) {
    soundTrack = soundTrackStart;
  }
  soundPlay(soundTrack);
}

void ligaLed(int i, bool flag) {
  int dig = 0;
  int ld = 0;

  if ((i >= 1) && (i <= 8)) {
    dig = 0;
    if (i == 6) {
      i = 7;
    }
    else if (i == 7) {
      i = 6;
    }
    if (i == 8) {
      ld = 0;
    }
    else {
      ld = i;
    }
  }
  else if ((i >= 9) && (i <= 16)) {
    dig = 1;
    i = i - 8;
    if (i == 8) {
      ld = 0;
    }
    else {
      ld = i;
    }
  }
  else if ((i >= 17) && (i <= 24)) {
    dig = 3;
    i = i - 16;
    if (i == 8) {
      ld = 0;
    }
    else {
      ld = i;
    }
  }

  lc.setLed(0, dig, ld, flag);
}

void clearRing() {
  for (int r = 4; r <= 7; r++) {
    lc.setRow(0, r, B00000000);
  }
}

void ringSeq(int n) {
  clearRing();
  for (int c = 1; c <= n; c++) {
    ring(c);
  }
}

void ring(int n) {
  int dig = 0;
  int ld = 0;

  switch (n) {
    case 0:
      dig = 7;
      ld = 3;
      break;
    case 1 ... 4:
      dig = 4;
      ld = n - 1;
      break;
    case 5 ... 8:
      dig = 5;
      ld = n - 5;
      break;
    case 9 ... 12:
      dig = 6;
      ld = n - 9;
      break;
    case 13 ... 15:
      dig = 7;
      ld = n - 13;
      break;
    default:
      break;
  }
  lc.setLed(0, dig, ld, true);
}

void readRotaryEncoder() {
  int n = LOW;
  n = digitalRead(Rot_Pin1);
  if ((lastEncoded == LOW) && (n == HIGH)) {
    if (digitalRead(Rot_Pin2) == LOW) {
      encoderValue--;
    }
    else {
      encoderValue++;
    }
    if (encoderValue > 15) encoderValue = 15;
    if (encoderValue < 1) encoderValue = 1;
  }
  lastEncoded = n;
}

void initButton(int pinButton) {
  pinMode(pinButton, INPUT);
  digitalWrite(pinButton, HIGH);
  pinMode(pinButton, INPUT_PULLUP);

  switch (pinButton) {
    case buttonRot:
      debouncerRot.attach(pinButton);
      debouncerRot.interval(5); // interval in ms
      break;
    case buttonA:
      debouncerA.attach(pinButton);
      debouncerA.interval(5); // interval in ms
      break;
    case buttonB:
      debouncerB.attach(pinButton);
      debouncerB.interval(5); // interval in ms
      break;
    case buttonC:
      debouncerC.attach(pinButton);
      debouncerC.interval(5); // interval in ms
      break;
    case snakeWire:
      debouncerSW.attach(pinButton);
      debouncerSW.interval(5); // interval in ms
      break;
    case snakeStart:
      debouncerS1.attach(pinButton);
      debouncerS1.interval(5); // interval in ms
      break;
    case snakeEnd:
      debouncerS2.attach(pinButton);
      debouncerS2.interval(5); // interval in ms
      break;
    default:
      break;
  }
}

// verifica qual botão foi acionado
int getButton() {
  // evento do botao Rotary Encoder
  debouncerRot.update();
  static int lastBtnRotVal = -1;
  int BtnRotVal = debouncerRot.read();
  if (BtnRotVal != lastBtnRotVal) {
    lastBtnRotVal = BtnRotVal;
    if ( BtnRotVal == LOW ) {
      // evento Release
    }
    else {
      // evento Press
      return evtBtnRot;
    }
  }
  // evento do botao A
  debouncerA.update();
  static int lastBtnAVal = -1;
  int BtnAVal = debouncerA.read();
  if (BtnAVal != lastBtnAVal) {
    lastBtnAVal = BtnAVal;
    if ( BtnAVal == LOW ) {
      // evento Release
    }
    else {
      // evento Press
      return evtBtnA;
    }
  }

  // evento do botao B
  debouncerB.update();
  static int lastBtnBVal = -1;
  int BtnBVal = debouncerB.read();
  if (BtnBVal != lastBtnBVal) {
    lastBtnBVal = BtnBVal;
    if ( BtnBVal == LOW ) {
      // evento Release
    }
    else {
      // evento Press
      return evtBtnB;
    }
  }

  // evento do botao C
  debouncerC.update();
  static int lastBtnCVal = -1;
  int BtnCVal = debouncerC.read();
  if (BtnCVal != lastBtnCVal) {
    lastBtnCVal = BtnCVal;
    if ( BtnCVal == LOW ) {
      // evento Release
    }
    else {
      // evento Press
      return evtBtnC;
    }
  }

  // evento encostar a argola no arame
  debouncerSW.update();
  static int lastBtnSWVal = -1;
  int BtnSWVal = debouncerSW.read();
  if (BtnSWVal != lastBtnSWVal) {
    lastBtnSWVal = BtnSWVal;
    if ( BtnSWVal == HIGH ) {
      // evento Release
    }
    else {
      // evento Press
      return evtSW;
    }
  }

  // evento encostar a argola no ponto de inicio
  debouncerS1.update();
  static int lastBtnS1Val = -1;
  int BtnS1Val = debouncerS1.read();
  if (BtnS1Val != lastBtnS1Val) {
    lastBtnS1Val = BtnS1Val;
    if ( BtnS1Val == HIGH ) {
      // evento Release
      return evtS1Release;
    }
    else {
      // evento Press
      return evtS1Press;
    }
  }

  // evento encostar a argola no ponto final
  debouncerS2.update();
  static int lastBtnS2Val = -1;
  int BtnS2Val = debouncerS2.read();
  if (BtnS2Val != lastBtnS2Val) {
    lastBtnS2Val = BtnS2Val;
    if ( BtnS2Val == HIGH ) {
      // evento Release
      return evtS2Release;
    }
    else {
      // evento Press
      return evtS2Press;
    }
  }

  getSoundVolume();

  return -1;
}

// mostra os jogados ativos no jogo e a sequencia deles
void displayPlayers() {
  digitalWrite(BACKLIGHT_PIN, HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10120);     // Ordem dos jogadores
  lcd.setCursor(0, 1);
  lcd.write("1-");
  lcd.write(players[0]);
  lcd.setCursor(11, 1);
  lcd.write("2-");
  lcd.write(players[1]);
  if (NumberPlayers > 2) {
    lcd.setCursor(0, 2);
    lcd.write("3-");
    lcd.write(players[2]);
  }
  if (NumberPlayers > 3) {
    lcd.setCursor(11, 2);
    lcd.write("4-");
    lcd.write(players[3]);
  }
  if (NumberPlayers > 4) {
    lcd.setCursor(0, 3);
    lcd.write("5-");
    lcd.write(players[4]);
  }
}

// apaga todos os leds
void turnOffAllLeds() {
  lc.shutdown(0, false);
  lc.clearDisplay(0);
}

// faz os leds da roleta acenderem sequencialmente
// com velocidade indicada pelo valor do rotary encoder.
void spinRoulette() {
  int randNumber = random(20);

  digitalWrite(BACKLIGHT_PIN, LOW);

  int turns = randNumber + (encoderValue * 2);
  int i = 0;
  int speed = 180 - (encoderValue * 10);
  if (speed < 10) speed = 10;

  while (i <= turns) {
    ligaLed(roulettePos, true);
    delay(speed);
    speed = speed + 1;
    ligaLed(roulettePos, false);
    roulettePos++;
    if (roulettePos > 16) {
      roulettePos = 1;
    }
    i++;
  }

  int rv = rouletteVal[roulettePos - 1];

  // evita que o jogador atual sofra a penalidade
  // de voltar ao início mais de uma vez
  if (rv == -1) {
    if (playersPenalty[currentPlayer] > 0) {
      roulettePos++;
      rv = rouletteVal[roulettePos - 1];
    }
    playersPenalty[currentPlayer]++;
  }

  ligaLed(roulettePos, true);
  digitalWrite(BACKLIGHT_PIN, HIGH);
}

// mostra o placar geral, com a posição
// no tabuleiro de cada jogador
void displayPos() {
  digitalWrite(BACKLIGHT_PIN, HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  displayRomStr(10140);     // Placar geral:

  lcd.setCursor(0, 1);
  lcd.write(players[0]);
  char str[10];
  sprintf(str, " %2d", playersPos[0]);
  lcd.setCursor(10, 1);
  lcd.write(str);

  if (NumberPlayers > 1) {
    lcd.setCursor(0, 2);
    lcd.write(players[1]);
    char str[10];
    sprintf(str, " %2d", playersPos[1]);
    lcd.setCursor(10, 2);
    lcd.write(str);
  }
  if (NumberPlayers > 2) {
    lcd.setCursor(0, 3);
    lcd.write(players[2]);
    char str[10];
    sprintf(str, " %2d", playersPos[2]);
    lcd.setCursor(10, 3);
    lcd.write(str);
  }

  if (NumberPlayers > 3) {
    waitConfirm(3000);
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.write(players[3]);
    char str[10];
    sprintf(str, " %2d", playersPos[3]);
    lcd.setCursor(10, 0);
    lcd.write(str);
  }

  if (NumberPlayers > 4) {
    lcd.setCursor(0, 1);
    lcd.write(players[4]);
    char str[10];
    sprintf(str, " %2d", playersPos[4]);
    lcd.setCursor(10, 1);
    lcd.write(str);
  }

  buttonPressed = -1;
  waitConfirm(3000);
}

// mostra a mensagem de fim de jogo
void displayGameOver() {
  digitalWrite(BACKLIGHT_PIN, HIGH);
  lcd.clear();
  lcd.setCursor(0, 1);
  displayRomStr(10160);     // game over
  waitConfirm(0);
}


// exibe uma questao aleatoria, lendo da memoria EEPROM ------------------
#define eepromChip 0x50    //romAddress of 24LC256 eeprom chip
#define recCount 47
#define recordLength 160
#define fieldLength   20
// questions in EEPROM
#define questLevel01_begin  0
#define questLevel01_end   13
#define questLevel02_begin 14
#define questLevel02_end   32
#define questLevel03_begin 33
#define questLevel03_end   46

// exibe a mensagem para o jogador retirar uma carta
// e indicar, através dos botões, a cor dela.
void displayGetCard() {
  loadChars(strAccents);
  digitalWrite(BACKLIGHT_PIN, HIGH);
  lcd.clear();
  displayPlayer();
  lcd.setCursor(0, 1);
  displayRomStr(10480);     // deve pegar uma carta
  lcd.setCursor(0, 2);
  displayRomStr(10500);     // e pressionar o botao
  lcd.setCursor(0, 3);
  displayRomStr(10520);     // da mesma cor.

  // espera a resposta
  ligaLed(ledButtonA, true);
  ligaLed(ledButtonB, true);
  ligaLed(ledButtonC, true);

  buttonPressed = -1;
  int level = 0;

  while (true)
  {
    buttonPressed = getButton();

    if (buttonPressed == 1) {  // botao vermelho = pergunta dificil
      level = 3;
      buttonPressed = -1;
      break;
    }
    if (buttonPressed == 2) {  // botao azul = pergunta media
      level = 2;
      buttonPressed = -1;
      break;
    }
    if (buttonPressed == 3) {  // botao verde = pergunta facil
      level = 1;
      buttonPressed = -1;
      break;
    }
    buttonPressed = -1;
  }

  lcd.clear();
  lcd.write("O ");
  displayPlayer();
  lcd.setCursor(0, 1);
  displayRomStr(10540);     // deve responder a
  lcd.setCursor(0, 2);
  displayRomStr(10560);     // pergunta a seguir:

  displayQuestion(level);
}

void displayPlayer() {
  lcd.write("Jogador ");
  lcd.write(players[currentPlayer]);
}

// exibe uma pergunta aleatória, de acordo com o nível
// de dificuldade indicado na variável level.
// Lê a pergunta da memória EEPROM e exibe no display
// lcd utilizando caracteres acentuados, que são
// carregados dinamicamente de acordo com os acentos
// que serão necessários para a pergunta a ser exibida.
void displayQuestion(int level) {
  static int questionNumber = -1;
  int rnd = -1;
  int tries = 0;
  buttonPressed = -1;


  digitalWrite(BACKLIGHT_PIN, HIGH);

  // Sorteia uma questão dentro do range
  // de dificuldade indicada no parâmetro level.
  // Se coincindir com o número sorteado na
  // rodada anterior, tenta sortear novamente.
  do
  {
    switch (level) {
      case 1:
        rnd = random(questLevel01_begin, questLevel01_end);
        break;
      case 2:
        rnd = random(questLevel02_begin, questLevel02_end);
        break;
      case 3:
        rnd = random(questLevel03_begin, questLevel03_end);
        break;
    }
    if (rnd != questionNumber) {
      break;
    }
    tries++;
  } while (tries < 3);

  questionNumber = rnd;

  unsigned char rdata[21] = "ABCDEFGHIJ1234567890";
  char xdata[21] = "ABCDEFGHIJ1234567890";
  unsigned int romAddr = questionNumber * recordLength;;
  char mp3[5] = "????";
  int mp3Ind = 0;
  char aDelay[3] = "00";
  int  questDelay = 0;
  char accents[11] = "          ";
  char correct = 'X';

  // le o header do registro no rom -------
  readEEPROM(eepromChip, romAddr, rdata, fieldLength);
  strncpy(xdata, (char*)rdata, fieldLength);

  // le o nome do arquivo mp3 da questao
  mp3[0] = xdata[0];
  mp3[1] = xdata[1];
  mp3[2] = xdata[2];
  mp3[3] = xdata[3];
  mp3Ind = atoi(mp3);

  // le o tempo de espera entre a questao e as alternativas
  aDelay[0] = xdata[5];
  aDelay[1] = xdata[6];
  questDelay = atoi(aDelay) * 1000;

  // le os caracteres especiais da pergunta
  for (int i = 0; i < 10; i++) {
    accents[i] = xdata[i + 8];
  }
  loadChars(accents);

  // le da rom as quatro linhas da questao
  lcd.clear();
  romAddr = romAddr + fieldLength;
  for (int i = 0; i < 4; i++) {
    readEEPROM(eepromChip, romAddr, rdata, fieldLength);
    strncpy(xdata, (char*)rdata, fieldLength);
    if (xdata[0] != ' ') {
      lcd.setCursor(0, i);
      lcdDisplay(xdata, accents);
    }
    romAddr = romAddr + fieldLength;
  }

  soundStop();
  delay(1000);
  soundPlay(mp3Ind);
  waitConfirm(questDelay);

  // le as tres alternativas
  lcd.clear();

  for (int i = 0; i < 3; i++) {
    readEEPROM(eepromChip, romAddr, rdata, fieldLength);
    strncpy(xdata, (char*)rdata, fieldLength);
    // procura por * no final da string, para saber se é a alternativa correta
    if (xdata[19] == '*') {
      correct = char(65 + i);
    }
    xdata[18] = 0;   // trunca a string em 18 caracteres
    lcd.setCursor(0, i);
    lcd.write(char(65 + i));
    lcd.write(")");
    lcdDisplay(xdata, accents);
    romAddr = romAddr + fieldLength;
  }

  // espera a resposta
  ligaLed(ledButtonA, true);
  ligaLed(ledButtonB, true);
  ligaLed(ledButtonC, true);

  buttonPressed = -1;
  bool ra = false;

  while (true)
  {
    buttonPressed = getButton();

    if (buttonPressed == 1) {
      if (correct == 'A') ra = true;
      buttonPressed = -1;
      break;
    }
    if (buttonPressed == 2) {
      if (correct == 'B') ra = true;
      buttonPressed = -1;
      break;
    }
    if (buttonPressed == 3) {
      if (correct == 'C') ra = true;
      buttonPressed = -1;
      break;
    }
    buttonPressed = -1;
  }

  lcd.clear();

  if (ra == true) {
    lcd.setCursor(0, 0);
    displayRomStr(10580);     // Resposta correta !!!
    lcd.setCursor(0, 1);
    displayRomStr(10600);     // Avance 1 casa
    playersPos[currentPlayer] = playersPos[currentPlayer] + 1;
    if (playersPos[currentPlayer] > 54) {
      playersPos[currentPlayer] = 55;
    }
  } else {
    lcd.setCursor(0, 0);
    displayRomStr(10620);     // Resposta errada !!!
    lcd.setCursor(0, 1);
    displayRomStr(10640);     // Retorne 1 casa
    playersPos[currentPlayer] = playersPos[currentPlayer] - 1;
  }

  waitConfirm(2000);

  // toca a proxima musica
  soundTrack++;
  if (soundTrack > soundTrackEnd) {
    soundTrack = soundTrackStart;
  }
  soundPlay(soundTrack);  
}

// espera pelo pressionamento do botão C, ou pelo tempo estipulado na variável timeWait,
// se timeWait for igual a zero, espera indefinidamente
void waitConfirm(int timeWait) {
  unsigned long currentMillis = millis();

  ligaLed(ledButtonA, false);
  ligaLed(ledButtonB, false);
  ligaLed(ledButtonC, true);

  timer1 = currentMillis;
  buttonPressed = -1;

  while (true)
  {
    currentMillis = millis();
    if (timeWait > 0) {
      if (currentMillis - timer1 > timeWait) {
        timer1 = currentMillis;
        buttonPressed = -1;
        break;
      }
    }

    buttonPressed = getButton();

    if (buttonPressed == 3) {
      buttonPressed = -1;
      break;
    }
    buttonPressed = -1;
  }
}

// exibe texto acentuado no display
void lcdDisplay(char str[], char accents[]) {
  int p = 0;
  int i = 0;
  char s[21];

  int len1 = 0;
  while (str[len1] != 0) {
    len1++;
  };

  int len2 = 0;
  while ((accents[len2] != 0) && (accents[len2] != 32)) {
    len2++;
  };

  for (i = 0; i < len1; i++) {
    p = -1;
    int cp = 0;
    while (cp < len2) {
      if (str[i] == accents[cp]) {
        p = cp;
        break;
      }
      cp++;
    }
    if (p >= 0) {
      s[i] = char(p + 1);
    } else {
      s[i] = str[i];
    }
  }
  s[i] = 0;

  lcd.write(s);
}

// Caracteres acentuados
const uint8_t charBitmap[][12] = {
  { 0b01110, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111, 0b00000 }, // ã
  { 0b00010, 0b00100, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111, 0b00000 }, // á
  { 0b00100, 0b01010, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111, 0b00000 }, // â
  { 0b00010, 0b00100, 0b01110, 0b10001, 0b11111, 0b10001, 0b10001, 0b00000 }, // Á
  { 0b00010, 0b00100, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110, 0b00000 }, // é
  { 0b00100, 0b01010, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110, 0b00000 }, // ê
  { 0b00010, 0b00100, 0b00000, 0b01100, 0b00100, 0b00100, 0b01110, 0b00000 }, // í
  { 0b00010, 0b00100, 0b01110, 0b00100, 0b00100, 0b00100, 0b01110, 0b00000 }, // Í
  { 0b00010, 0b00100, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000 }, // ó
  { 0b01110, 0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000 }, // õ
  { 0b01110, 0b10001, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000 }, // ô
  { 0b00010, 0b00100, 0b10001, 0b10001, 0b10001, 0b10011, 0b01101, 0b00000 }, // ú
  { 0b00000, 0b00000, 0b01110, 0b10000, 0b10000, 0b10001, 0b01110, 0b11000 }  // ç
};

// carrega os caracteres especiais para a ROM do LCD
void loadChars(char str[]) {
  int len = 0;
  do {
    len++;
  } while ((str[len] != 0) && (str[len] != 32));

  for (int i = 0; i < len; i++) {
    char c = str[i];
    if (c == '@') lcd.createChar (i + 1, (uint8_t *)charBitmap[0]);
    if (c == '#') lcd.createChar (i + 1, (uint8_t *)charBitmap[1]);
    if (c == '$') lcd.createChar (i + 1, (uint8_t *)charBitmap[2]);
    if (c == '&') lcd.createChar (i + 1, (uint8_t *)charBitmap[3]);
    if (c == '*') lcd.createChar (i + 1, (uint8_t *)charBitmap[4]);
    if (c == '/') lcd.createChar (i + 1, (uint8_t *)charBitmap[5]);
    if (c == '<') lcd.createChar (i + 1, (uint8_t *)charBitmap[6]);
    if (c == '>') lcd.createChar (i + 1, (uint8_t *)charBitmap[7]);
    if (c == '_') lcd.createChar (i + 1, (uint8_t *)charBitmap[8]);
    if (c == ']') lcd.createChar (i + 1, (uint8_t *)charBitmap[9]);
    if (c == '[') lcd.createChar (i + 1, (uint8_t *)charBitmap[10]);
    if (c == '{') lcd.createChar (i + 1, (uint8_t *)charBitmap[11]);
    if (c == '}') lcd.createChar (i + 1, (uint8_t *)charBitmap[12]);
  }
}

// lê a memória EEPROM usando comunicação I2c
void readEEPROM(int deviceromAddress, unsigned int eeromAddress, unsigned char* data, unsigned int num_chars)
{
  unsigned char i = 0;
  Wire.beginTransmission(deviceromAddress);
  Wire.write((int)(eeromAddress >> 8));   // MSB
  Wire.write((int)(eeromAddress & 0xFF)); // LSB
  Wire.endTransmission();

  Wire.requestFrom(deviceromAddress, num_chars);

  while (Wire.available()) data[i++] = Wire.read();
}

void soundPlay(int soundIndex) {
  getSoundVolume();
  Serial.print("soundVolume=");
  Serial.println(soundVolume);
  if (soundVolume > 0) {
     soundSetVolume(soundVolume);
     Serial.write(0x7E);
     Serial.write(0x04);
     Serial.write(0xA0); // A0 for SD card
     Serial.write((byte)0x00);
     Serial.write(soundIndex); // track number
     Serial.write(0x7E);
     Serial.print("soundIndex=");
     Serial.println(soundIndex);
  }
}

void soundPause() {
  Serial.write(0x7E);
  Serial.write(0x02);
  Serial.write(0xA3);
  Serial.write(0x7E);
}

void soundStop() {
  Serial.write(0x7E);
  Serial.write(0x02);
  Serial.write(0xA4);
  Serial.write(0x7E);
}

void soundSetVolume(int soundVolume) {
  // 00-mute
  // 31-max volume
  Serial.write(0x7E);
  Serial.write(0x03);
  Serial.write(0xA7);
  Serial.write(soundVolume); //  volume max 31 (1F)
  Serial.write(0x7E);
}

#define volPin A6    // select the input pin for the potentiometer

void getSoundVolume() {
  static unsigned long lastTime = millis();
  if (millis() - lastTime > 1000) {
    lastTime = millis();
    int sensorValue = analogRead(volPin);
    int v = map(sensorValue, 530, 1023, 0, 30);
    if (v < 0) {
      v = 0;
    }
    if (v > 30) {
      v = 30;
    }
    if (v != soundVolume) {
      soundVolume = v;
      soundSetVolume(soundVolume);
    }
  }
}

void displayRomStr(unsigned int eepromAddress) {
  unsigned char rdata[21] = "ABCDEFGHIJ1234567890";
  char xdata[21] = "ABCDEFGHIJ1234567890";
  unsigned int romAddr = 10000;

  // se a lista de acentos estiver vazia
  // carrega da EEPROM
  if (strAccents[0] == 32) {
    readEEPROM(eepromChip, romAddr, rdata, 20);
    strncpy(strAccents, (char*)rdata, 10);
    strAccents[11] = 0;
  }

  readEEPROM(eepromChip, eepromAddress, rdata, 20);
  strncpy(xdata, (char*)rdata, 20);

  lcdDisplay(xdata, strAccents);
}


0 comentários :

Post a Comment