Spiel "Reaction Time"

Dies ist ein sehr einfaches Reaktions-Spiel für 2 Personen, welches ich mir ausgedacht und dann mit einem Arduino und ein paar LEDs nachgebaut habe.

Video: Live-Demonstration des Spiels

Verwendete Bauteile

Spielverlauf

Ziel des Spiels ist schneller richtig zu reagieren als der gegnerische Spieler.
Sobald einer der beiden Spieler seinen Knopf drückt, beginnt das Spiel. Es wird in jeder Spielrunde nach einer kurzen Zeit eine von zwei Spiel-LEDs aufleuchten. Leuchtet die blau LED, so muss der Spieler versuchen, vor dem Gegner seinen Knopf zu drücken. Gelingt dies, so erhält er einen Punkt. Leuchtet jedoch die rote LED darf kein Spieler drücken. Tut er dies dennoch, so verliert er einen Punkt. Der Spieler, der zuerst 10 Punkte erreicht hat, gewinnt das Spiel.

Aufbau

Aufbau der Schaltung
Abb.: Aufbau der Schaltung

Sketch

/**
 * How to play:
 * Goal of the game is to be faster than the other player.
 * Each player has a button and a colored status LED. After one button is pressed the game begins.
 * During the game there will be lit one of two LEDs: red or blue. If the blue LED is lit each player
 * has to hit his button as fast as possible to get 1 point. If the red LED is lit then players must
 * not press their buttons otherwise they lose 1 point. After one player has gathered 10 points he
 * wins the game.
 */

#define PIN_LED_GOOD 8
#define PIN_LED_BAD  9

#define DEBOUNCE_TIME 300   // in ms

#define GAME_STATE_RUNNING 1
#define GAME_STATE_END     2

#define PLAYER_1 0
#define PLAYER_2 1

#define PLAYER_AMOUNT 2

#define WINNING_SCORE 10

#define MIN_CHANGE_TIME 1200  // in ms
#define MAX_CHANGE_TIME 2500  // in ms

const byte pinButtons[PLAYER_AMOUNT] = {2, 3};
const byte pinLeds[PLAYER_AMOUNT] = {10, 11};

byte gameState;
byte ledState;
byte score[PLAYER_AMOUNT];
unsigned long lastButtonClick[PLAYER_AMOUNT];
unsigned long lastLedChange;
unsigned long nextLedStateChange;

void setup()
{
  randomSeed(analogRead(0));

  pinMode(PIN_LED_GOOD, OUTPUT);
  pinMode(PIN_LED_BAD,  OUTPUT);

  for (byte i=0; i<PLAYER_AMOUNT; i++) {
    pinMode(pinLeds[i], OUTPUT);
    pinMode(pinButtons[i], INPUT);
    lastButtonClick[i] = millis();
  }
  gameState = GAME_STATE_END;
}

void loop()
{
  switch(gameState)
  {
    case GAME_STATE_END:
      if (isButtonPressed(PLAYER_1) || isButtonPressed(PLAYER_2)) {
        initGame();
      }
      break;
    case GAME_STATE_RUNNING:
      toggleLedState();
      if (isButtonPressed(PLAYER_1)) {
        checkGameState(PLAYER_1);
      }
      if (isButtonPressed(PLAYER_2)) {
        checkGameState(PLAYER_2);
      }
      break;
  }
}

void initGame()
{
  gameState = GAME_STATE_RUNNING;
  setNextLedChange();
  ledState = 0;

  digitalWrite(PIN_LED_GOOD, LOW);
  digitalWrite(PIN_LED_BAD, LOW);
  for (byte i=0; i<PLAYER_AMOUNT; i++) {
    digitalWrite(pinLeds[i], LOW);
    lastButtonClick[i] = millis();
    score[i] = 0;
  }
}

void setNextLedChange()
{
  lastLedChange = millis();
  nextLedStateChange = random(MIN_CHANGE_TIME, MAX_CHANGE_TIME);
}

bool isButtonPressed(byte playerId)
{
  if (millis() - lastButtonClick[playerId] < DEBOUNCE_TIME) {
    return false;
  }
  if (digitalRead(pinButtons[playerId]) == HIGH) {
    lastButtonClick[playerId] = millis();
    return true;
  }
  return false;
}

void toggleLedState()
{
  if (ledState == PIN_LED_GOOD) {
    return;
  }
  if ((millis() - lastLedChange) < nextLedStateChange) {
    return;
  }
  if (ledState == PIN_LED_BAD) {
    digitalWrite(PIN_LED_BAD, LOW);
    setNextLedChange();
  }

  if (random(0, 100) > 50) {
    ledState = PIN_LED_GOOD;
    digitalWrite(PIN_LED_GOOD, HIGH);
    digitalWrite(PIN_LED_BAD, LOW);
  } else {
    ledState = PIN_LED_BAD;
    digitalWrite(PIN_LED_GOOD, LOW);
    digitalWrite(PIN_LED_BAD, HIGH);
    setNextLedChange();
  }
}

void checkGameState(byte playerId)
{
  switch(ledState) {
    case PIN_LED_GOOD:
      digitalWrite(PIN_LED_GOOD, LOW);
      score[playerId]++;
      if (score[playerId] >= WINNING_SCORE) {
        gameState = GAME_STATE_END;
        for(byte i=0; i<10; i++) {
          digitalWrite(pinLeds[playerId], HIGH);
          delay(250);
          digitalWrite(pinLeds[playerId], LOW);
          delay(250);
        }
        return;
      }
      break;
    case PIN_LED_BAD:
      digitalWrite(PIN_LED_BAD, LOW);
      if (score[playerId] > 0) {
        score[playerId]--;
      }
      break;
  }

  ledState = 0;
  setNextLedChange();
}

Gefertigte Platine

Gerber-Dateien für PCB-Produktion [68 kB]

Errata: Die Polungen der LEDs sind auf dem obigen Schaltplan und auch der fertigen Platine leider alle vertauscht, dies bitte beim Nachbau beachten!

Hinweise zur Bestückung des PCBs:

Eigenes Arduino-Shield als PCB

Zum Ausprobieren habe ich die Schaltung dieses Spiels als Platinen-Layout erstellt (siehe oben) und dann bei JLCPCB, einem in China ansässigen Platinenhersteller, als 10er-Pack PCBs fertigen lassen. Nach ca. 14 Tagen hatte ich ein Paket mit den fertigen Platinen in der Post und das Resultat meiner ersten selbst gefertigten Platine ist zufrieden stellend:

Die Platinen von JLCPCB
Abb. #1: Die Platinen von JLCPCB
Vorderseite/Oberseite der Platine mit Beschriftungen
Abb. #2: Vorderseite/Oberseite der Platine mit Beschriftungen. Vorsicht, die Polungen der LEDs sind vertauscht!
Rückseite/Unterseite der Platine
Abb. #3: Rückseite/Unterseite der Platine
Fertig bestückte Platine (Oberseite)
Abb. #4: Fertig bestückte Platine (Oberseite) Ich habe hier statt 1kΩ einen 820Ω und statt der 10kΩ einen 43kΩ Widerstand verwendet. Außerdem hatten die beiden Mikrotaster 2 statt vier Pins, daher musste ich eine kleine Drahtbrücke auf der Unterseite installieren.
Fertig bestückte Platine (Unterseite)
Abb. #5: Fertig bestückte Platine (Unterseite)
Fertiges Shield auf dem Arduino montiert
Abb. #6: Fertiges "Reaction Time"-Shield auf dem Arduino montiert

Weitere Entwicklung & Ideen

zurück