Jogo de tabuleiro feito com Arduino


from Makezine

No ano passado meu filho teve que fazer um jogo de tabuleiro para a feira de ciências da escola, e o tema foi o Egito Antigo. Eu achei que era uma boa oportunidade de introduzi-lo no mundo da eletrônica. 

A minha sugestão inicial foi substituir o dado convencional por um eletrônico, mas meu filho e seus colegas são muito criativos, então quando as ideias começaram a surgir, eu não quis podá-las. Assim, as coisas começaram a ficar um pouco mais complexas.


As crianças fizeram a concepção do projeto, o game design e o desenho da caixa, e eu ajudei-os com meus conhecimentos em eletrônica.



O cerébro do projeto é um Arduino Nano, que coordena alguns outros módulos e circuitos (display, MP3 player, botões e leds).

Como todo trabalho escolar, as crianças tiveram que fazer muitas pesquisas sobre a história do Egito Antigo, pois eles tinha que formular uma série de questões para a parte de perguntas e respostas do jogo.

Eles também tiveram muito trabalho para desenhar o mapa do tabuleiro, que foi impresso em um adesivo de vinil.

Eles se inspiraram em máquinas de pinball para desenhar a caixa do jogo. Eles queriam uma área grande para o mapa e um display inclinado, que também escondia o sarcófago do faraó. O sarcófago do faraó só aparece quando um jogador vence o jogo, chegando ao final da trilha.


A caixa foi feita com plástico (polietileno) e folhas de acrílico, por conta da facilidade de cortá-los e dobrá-los.



O circuito eletrônico foi projetado com o software Fritzing. Eu não gosto de usar jumpers, costumo fazer placas de circuito impresso com dupla-face. Mas o tempo era curto, então preferi simplificar, fazendo uma placa simples, usando o método de transferência de toner.


Este projeto tem 24 leds, então tive que usar um CI multiplexador. O MAX7219 resolveu o problema dos leds, mas causava um ruído forte nos alto-falantes. Para resolver esse problema, o MAX7219 foi trocado por seu "irmão" o MAX7221, que tem uma proteção contra interferência eletromagnética (EMI).

 

As perguntas e respostas são exibidas em um display 20x4, e também tocadas nos alto-falantes com as vozes das crianças, que foram gravadas e distorcidas para parecer como a voz de uma múmia.


O primeiro desafio de programação foi exibir os caracteres acentuados, já que os displays de LCD não têm suporte nativo para acentuação. Apesar do display permitir customizar 8 caracteres, não era o suficiente. Então para contornar esse problema eu tive que carregar dinamicamente os caracteres customizados.

Outro desafio de programação que tive que lidar foi o limite de 2KB de RAM do Arduino, já que eu precisava de muita memória para armazenar as strings das perguntas e respostas, e o truque de usar progmem não era suficiente para resolver o problema. Eu tive que armazenar estas strings em uma memória EEPROM externa (Microchip 24LC256). Eu fiz alguns programas que foram executados uma única vez, para armazenar essas strings na EEPROM, e depois elas eram recuperadas pelo programa principal, usando os endereços de memória onde elas estavam armazenadas.

Depois de tudo isso, acho que o Arduino Nano foi exigido ao máximo. Mais do que isso só usando um Arduino Mega.

Da concepção ao final, o projeto levou 30 dias para ficar pronto.

No GitHub estão disponíveis todas as informações sobre o projeto (código fonte, desenhos da PCI, fotos, etc):
https://github.com/marcelomaximiano/BoardGame

Vídeo do jogo: https://www.youtube.com/watch?v=Zrz66H8dYvI
Vídeo dos testes: https://www.youtube.com/watch?v=dJfBCsZCKEY

Código fonte:
#include 
#include 
#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);
}


Um comentário :