Wiki do André

Partilha de conhecimento

Pequeno jogo de reflexos para 2 jogadores

Desta vez apresento-vos um pequeno jogo em que a ideia é ver qual dos dois jogadores tem melhores reflexos. Consiste num LED que acende de forma aleatória e, quando acesso, permite aos jogadores carregarem no botão e ver quem foi o mais rápido. Uma luz acende-se a indicar qual o jogador mais rápido e os pontos vão sendo acumulados (não são usados neste esquema, mas podem ser apresentados se juntarem um display de 7 segmentos com 2 ou 4 dígitos, por exemplo).

Material

  • 3 LEDs de cores à escolha (de preferência com cores distintas) + 3 resistências de 330 Ω (laranja, laranja, castanho)
  • Buzzer + 1 resistência de 330 Ω (laranja, laranja, castanho)
  • 2 botões (push-button) + 2 resistências de 1 kΩ (castanho, preto, vermelho)

Esquema de ligação

Imagem do esquema de ligação do jogo

Montagem

  1. Coloquem os LEDs como apresentado na figura. Para cada LED, liguem uma resistência de 330 Ω, unindo a perna negativa do LED e o negativo da breadboard
  2. Coloquem agora os botões. Liguem numa das pernas um fio (branco na figura) ao positivo (5V). Na perna diagonalmente oposta, liguem a resistência de 1 KΩ (em cada botão).
  3. Liguem o buzzer, e coloquem uma resistência de 330 Ω, unindo o negativo do buzzer e o ground (0V/negativo da breadboard).
  4. Liguem o fio do primeiro LED (jogador A) à porta 2 do Arduino
  5. Liguem o fio do LED central (blinker) à porta 3 do Arduino
  6. Liguem o fio do terceiro LED (jogador B) à porta 4 do Arduino
  7. Liguem um fio da porta 6 ao positivo do buzzer
  8. Liguem um fio na linha de cada resistência dos botões às portas 8 e 9 (jogador A e B, respetivamente).
  9. Por fim, liguem um fio do GND (ground/0V) do Arduino à trilha da breadboard que escolheram como negativo)
  10. Façam o mesmo para o fio de alimentação de 5V (se ligaram os fios dos botões no lado oposto da breadboard, não se esqueçam de colocar um fio adicional para a outra trilha do positivo (isto foi feito no esquema em cima)).

Terminadas as ligações, façam upload deste código:

// Configuração de constantes
byte const PLRA_LED = 2;  // led do jogador A
byte const PLRA_BTN = 8;  // botão do jogador A
byte const PLRB_LED = 4;  // led do jogador B
byte const PLRB_BTN = 9;  // botão do jogador B
byte const BLINKER = 3;  // porta do LED indicador
byte const BELL = 6;  // porta do buzzer
int const MIN_TIME = 3000;  // tempo mínimo de intervalo entre indicador
int const MAX_TIME = 7000;  // tempo máximo de intervalo entre indicador
int const BELL_CHEAT = 600;

// variáveis a usar no jogo
long last_start = 0;
long score_a = 0;
long score_b = 0;
int multiple = 1;
byte touch = 0;
byte block_A = 0;
byte block_B = 0;

// preparação das portas
void setup()
{
  pinMode(PLRA_LED, OUTPUT);
  pinMode(PLRB_LED, OUTPUT);
  pinMode(PLRA_BTN, INPUT);
  pinMode(PLRB_BTN, INPUT);
  pinMode(BLINKER, OUTPUT);
  pinMode(BELL, OUTPUT);
  intro();
  last_start = nextBlink();
}

// loop do jogo
void loop()
{
  long ms = millis();

  // jogo gerado; aceitar input dos jogadores
  if( ms >= last_start && touch == 0 )
  {
    digitalWrite(BLINKER, HIGH);

    byte read_A = digitalRead(PLRA_BTN);
    byte read_B = digitalRead(PLRB_BTN);

    // leitura do jogador A
    if( read_A == 1 && block_A == 0 )
    {
      score_a += (1 * multiple);
      digitalWrite(PLRA_LED, HIGH);
      touch = 1;
    }

    // leitura do jogador B
    if( read_B == 1 && block_B == 0)
    {
      score_b += (1 * multiple);
      digitalWrite(PLRB_LED, HIGH);
      touch = 1;
    }
  }
  else if( ms <= last_start )
  {
    // sistema contra batota
    byte read_A = digitalRead(PLRA_BTN);
    byte read_B = digitalRead(PLRB_BTN);

    if( read_A == 1 )
    {
      digitalWrite(PLRA_LED, HIGH);
      tone(BELL, BELL_CHEAT);
      delay(700);
      noTone(BELL);
      digitalWrite(PLRA_LED, LOW);
      block_A = 1;
    }
    else
    {
      block_A = 0;
    }

    if( read_B == 1 )
    {
      digitalWrite(PLRB_LED, HIGH);
      tone(BELL, BELL_CHEAT);
      delay(700);
      noTone(BELL);
      digitalWrite(PLRB_LED, LOW);
      block_B = 1;
    }
    else
    {
      block_B = 0;
    }
  }

  // gerar novo jogo
  if( ms >= last_start && touch == 1 )
  {
    sound();
    delay(1000);
    digitalWrite(PLRA_LED, LOW);
    digitalWrite(PLRB_LED, LOW);
    digitalWrite(BLINKER, LOW);
    last_start = nextBlink();
    touch = 0;
  }
}

// função para controlar a próxima execução do blinker
long nextBlink()
{
  return millis() + random(MIN_TIME, MAX_TIME);
}

// tocar um som com um buzzer
void sound()
{
  tone(BELL, 1600);
  delay(100);
  noTone(BELL);
  tone(BELL, 2000);
  delay(100);
  noTone(BELL);
  tone(BELL, 2400);
  delay(500);
  noTone(BELL);
}

// tocar o mesmo som, de forma inversa
void r_sound()
{
  tone(BELL, 2400);
  delay(100);
  noTone(BELL);
  tone(BELL, 2000);
  delay(100);
  noTone(BELL);
  tone(BELL, 1600);
  delay(500);
  noTone(BELL);
}

// Animação do jogo (luzes + som)
// as luzes piscam da esquerda para a direita e voltam (ver array seq)
// um som é gerado no inicio da animação e no final é gerado novo som
void intro()
{
  sound();

  int seq[] = { PLRA_LED, BLINKER, PLRB_LED, BLINKER, PLRA_LED };
  int sz = 5;

  for( byte i = 0; i < sz; i++ )
  {
    digitalWrite(seq[i], HIGH);
    delay(200);
    digitalWrite(seq[i], LOW);
  }

  r_sound();
}

Notas

Este jogo implementa um pequeno sistema anti-cheat, que consiste em verificar se um jogador está a carregar no botão antes do LED central estar aceso. Quando isso acontece, é emitido um som de erro no buzzer, e a luz do jogador acende. No código duas variáveis block_A e block_B controlam se os jogadores pressionaram ou não antes do tempo. Se um deles pressionar, essa jogada não é válida enquanto o botão não for largado. Isto permite bloquear um jogador que mantenha o botão pressionado até ao momento de acender o botão central, evitando essa batota.

Outra mudança interessante é o facto de se usar o tipo de dados long em vez de int para guardar o tempo. Como um int pode armazenar 2 bytes de dados (valores decimais entre -32768 a 32767), ao chegar aproximadamente aos 33 segundos de jogo (32768 milissegundos, para ser exato), acontecia o chamado overflow, e o valor retornaria à escala de negativos. A partir daí, o LED central nunca mais acenderia, porque o valor retornado pela função millis() seria sempre superior a 32767, valor que nunca poderia ser assumido por uma variável do tipo inteiro. Por isso, é usado o tipo de dados long para registar o próximo instante em que o LED central é aceso. Com èste tipo de dados podemos registar valores até 2147483647 (2 mil milhões ou 2 bilhões), que dá algo como 23 dias de jogo. Por mais que gostem deste jogo, creio que 23 dias é mais que suficiente para não levantar problemas :)

Download

Código, esquema de ligação do Fritzing e algumas imagens