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




Excelente projeto. Me deu grandes ideias!
ResponderExcluirMerit Casino – Is This Scam to Avoid? - XN Games
ResponderExcluirMerit Casino is a Scam site that was launched in 2004 and is regulated by the Malta Gaming Authority. This company provides 메리트카지노총판 an easy-to-use gambling