Genius comentado

Tempos atrás eu fiz um Genius (game dos anos 80, vc pode conferir o bicho funcionando aqui embaixo):


A ideia é repetir uma sequência de sons e cores criadas aleatoriamente pelo jogo. Como ele tem muitas coisas interessantes em termos de software, vou fazer um post mais detalhado aqui
Esse é o circuito montado. Usei um tweeter velho desses de carro para fazer o som, ficou legal, bem alto. Um LED de cada cor, um botão correspondente a cada cor e som.

Aqui mais de perto:

E o esquema no Fritzing:












Fazer games tem os seus desafios: tem que levar em conta a ergonomia, quer dizer, montar o circuito de maneira que o jogador consiga ter acesso aos comandos. Depois, o programa tem que ser rápido e consistente, de maneira a fazer o jogador ter uma experiência adequada. Acho que ficou legal.

Vc pode ver no vídeo o "meu" Genius funcionando:



Abaixo, como prometido, o código comentado:

// biblioteca que define as frequências das notas musicais
#include <Piches.h>

// pino onde está conectado o buzzer (ou auto-falante)
#define buzzer 3

// o programa foi feito como uma máquina de estados, estes são os estados possíveis
#define waitingStart   0 // esperando o jogo começar
#define playingSeq     1 // tocando a sequencia pro usuário repetir
#define userTyping     3 // lendo a sequencia que o cara tá teclando
#define validatingSeq  4 // validando

// pinos onde estáo ligadas as teclas. Devem ser em sequencia, a menor o vermelho.
#define kred    11 
#define kyellow 10
#define kgreen  9
#define kwhite  8

//pinos onde estão ligados os LEDs. Devem ser em sequencia, o menor o vermelho.
#define lred    7
#define lyellow 6
#define lgreen  5
#define lwhite  4 

// define modo debug (=1)
#define debugging 0

// duração da nota
int interval = 500;
// intervalo entre as notas
int noSound = 200;
// guarda a sequência atual
int seq[50];
// indexador da sequência tocada
int posSeq = 0;
// indexador da sequência digitada
int posType = 0;

// estado corrente
int state = waitingStart; 

// tecla pressionada
int key = 0; 

void setup() 
{                
// seta modo dos pinos
  pinMode(lred, OUTPUT);     
  pinMode(lyellow, OUTPUT);     
  pinMode(lgreen, OUTPUT);     
  pinMode(lwhite, OUTPUT);   
  pinMode(kred, INPUT);  
  pinMode(kyellow, INPUT);  
  pinMode(kgreen, INPUT);  
  pinMode(kwhite, INPUT);  
  noSound = 0;
// inicializa gerador de números aleatórios do Arduino:
// eu uso o valor lido de uma porta analógica desconectada,
// que tem sempre algum valor devido à alta impedância dessas
// portas
  randomSeed(analogRead(0));
// faz uma "gracinha"
  demo();

  noSound = 50;
  Serial.begin(9600); 
// cria a prmeira sequência
  generateSeq();
}

void loop() {
// máquina de estados: processa os diferentes estados do programa
  switch(state)
  {
    case waitingStart:   
// reseta o intervalo
      interval = 500;
      if (debugging)
        Serial.println("waitingStart");  
// espera o usuário digitar algo        
      key = readKey(false);
// quando isso ocorre, muda o estado para o próximo
      state = playingSeq;
      return;  

    case playingSeq:
      if (debugging)
        Serial.println("playingSeq");  
// toca a sequência e seta o próximo estado
      playSeq();
      posType = 0;
      state = userTyping;
      return;
    case userTyping:
      if (debugging)
        Serial.println("userTyping");  
      key = 1;
      int ok = true;
// enquanto o usuário estiver digitando certo, fica nesse estado
      while(ok)
      {
          key = readKey(true);
// se o usuário não digitou nada, dançou          
          if (key == 0)
            break;
// toca a nota digitada pelo usuário
          play(key-(lred-kred));
// valida a tecla digitada
          ok = validateSeq(key);
          if (debugging)
          {
            Serial.print("ok=");
            Serial.println(ok);
          }
 // se ok...
          if (ok)
          {
// se o usuário terminou a sequência...
            if (posType == posSeq)
            {
// dá um intervalo de um segundo...
              delay(1000);
// aumenta o tamanho da próxima sequência 
              posSeq++;
// agora o intervalo é 5% menor
              interval = interval * 0.95;
// volta o estado para playingSeq
              state = playingSeq;
              return;
            }  
            else
            {
// usuário ainda não terminou.
              posType++;
            }            
          }
      } 
// se chegou aqui, o usuário teclou errado.
      playWrong();
      Serial.print("Voce fez ");
      Serial.println(posSeq);
// gera nova sequência
      generateSeq();
// volta o estado para o inicial
      state = waitingStart;
      return;      
  }
}

int validateSeq(int key)
{
  return (key-(lred-kred)) == seq[posType]; // (key-3) = key to LED
}

// espera e lê a tecla digitada pelo usuário. Se vallidateMilis = 1,
// retorna 0 após 3 segundos
int readKey(int validateMilis)
{
   unsigned long ms = millis();
   int value = LOW;
   while(value == LOW)
   {
     for (int i = kred; i >= kwhite; i--)
     {
       if (validateMilis)
         if ((millis() - ms) > 3000)
         {
           if (debugging)
             Serial.println("Timeout"); 
           return 0;
         }
       value = digitalRead(i);
       if (value == HIGH)
       {
         return i;
       }
     }
  }
}

// toca uma determinada nota (pitch), com uma determinada duração (duration)
void playPich(int pich, int duration)
{
  tone(buzzer, pich);
  delay(duration);
  noTone(buzzer);
}

// toca a Imperial March (tema do Darth Vader no StarWars)
void playWrong()
{
    int ritym = 500;
    for(int i = 0; i < 2; i++)
    {
      digitalWrite(lred, HIGH);
      digitalWrite(lyellow, HIGH);
      digitalWrite(lgreen, HIGH);
      digitalWrite(lwhite, HIGH);
      playPich(NOTE_A3, ritym);
      playPich(NOTE_A3, ritym);
      playPich(NOTE_A3, ritym);
      playPich(NOTE_F3, ritym * 3 / 4);
      playPich(NOTE_C4, ritym * 1 / 4);
      playPich(NOTE_A3, ritym);
      playPich(NOTE_F3, ritym * 3 / 4);
      playPich(NOTE_C4, ritym * 1 / 4);
      playPich(NOTE_A3, ritym * 2);
      playPich(NOTE_E4, ritym);
      playPich(NOTE_E4, ritym);
      playPich(NOTE_E4, ritym);
      playPich(NOTE_F4, ritym * 3 / 4);
      playPich(NOTE_C4, ritym * 1 / 4);
      playPich(NOTE_GS3, ritym);
      playPich(NOTE_F3, ritym * 3 / 4);
      playPich(NOTE_C4, ritym * 1 / 4);
      playPich(NOTE_A3, ritym * 2);
      digitalWrite(lred, LOW);
      digitalWrite(lyellow, LOW);
      digitalWrite(lgreen, LOW);
      digitalWrite(lwhite, LOW);
      noTone(buzzer);
      delay(500);
    }
}

// toca a demo
void demo()
{
  interval = 500;
  while(interval > 5)
  {
    play(lred);
    play(lyellow);
    play(lgreen);
    play(lwhite);
    interval = interval / 1.5;
  }
}

// toca uma nota, pisca o LED correspondente
void blink(int led, int pich)
{
  tone(buzzer,pich);
  digitalWrite(led, HIGH);
  delay(interval);
  digitalWrite(led, LOW);
  noTone(buzzer);
  delay(noSound);
}

// toca uma nota
void play(int pich)
{
  switch(pich)
  {
    case lred:
      blink(lred,NOTE_C4);
      break;
    case lyellow:
      blink(lyellow,NOTE_E4);
      break;
    case lgreen:
      blink(lgreen,NOTE_G4);
      break;
    case lwhite:
      blink(lwhite,NOTE_AS4);
      break;
  }  
}

// gera a sequência de notas, aleatória
void generateSeq()
{
  posSeq = 0;
  if (debugging)
    Serial.println("generateSeq");
  int note;
  for (int i = 0; i < 50; i++)
  {
    seq[i] = random(4) + 4;
    if (debugging)
      Serial.println(seq[i]);
  }
}

// toca a sequência
void playSeq()
{
  for (int i = 0; i <= posSeq; i++)
  {
    play(seq[i]);
  }
}


Comentários

Postar um comentário

Postagens mais visitadas deste blog

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte III

Dividindo um programa (sketch) do Arduino em mais de um arquivo

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I