Spiel "Senso"

Das Spiel "Senso" (auch "Simon" genannt) ist ein bekanntes, elektronisches Geschicklichkeitsspiel, welches ich hier mit einigen elektronischen Bauteilen und einem Arduino als Steuereinheit nachgebaut habe.

Video: Live-Demonstration des Senso-Nachbaus als Prototyp

Verwendete Bauteile

Aufbau

Aufbau der Schaltung
Abb.: Aufbau der Schaltung
Prototyp auf dem Breadboard
Abb.: Aufbau des Prototypen auf dem Breadboard

Sketch

#define PIN_LED_0 4
#define PIN_LED_1 5
#define PIN_LED_2 6
#define PIN_LED_3 7

#define PIN_BUTTON_0 8
#define PIN_BUTTON_1 9
#define PIN_BUTTON_2 10
#define PIN_BUTTON_3 11

#define PIN_PIEZO 12

#define GAME_STATUS_END     1
#define GAME_STATUS_RUNNING 2
#define BUTTON_MAX_INTERVAL 4000 // in milliseconds
#define DELAY_INIT          1000 // in milliseconds
#define DELAY_MIN            100 // in milliseconds
#define DELAY_STEP            50 // in milliseconds

#define BUTTON_DEBOUNCE_INTERVAL 50 // in milliseconds
#define NO_PIN 255

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

bool isWaitingForPlayer, isColorPlayed;
byte ledPins[4] = {PIN_LED_0, PIN_LED_1, PIN_LED_2, PIN_LED_3};
byte buttonPins[4] = {PIN_BUTTON_0, PIN_BUTTON_1, PIN_BUTTON_2, PIN_BUTTON_3};
byte score, gameStatus, sequenceIndex, pressedButton, sequence[100];
int gameDelay, frequencies[4] = {440, 580, 650, 720};
unsigned long lastPlayerAction, lastButtonPressed;
Adafruit_SSD1306 oleDisplay(4);


void setup()
{
  // initialize oled with 0x3C (for 128x32)
  oleDisplay.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oleDisplay.clearDisplay();
  oleDisplay.display();
  oleDisplay.setTextSize(1);
  oleDisplay.setTextColor(WHITE);

  pinMode(PIN_PIEZO, OUTPUT);
  for(byte i=0; i<sizeof(buttonPins); i++) {
    pinMode(buttonPins[i], INPUT);
    pinMode(ledPins[i], OUTPUT);
  }

  initGame();
}


void loop()
{
  checkButtonPressed();
  switch(gameStatus)
  {
    case GAME_STATUS_RUNNING:
      if (isWaitingForPlayer == true) {
        isPlayerAction();
      } else {
        playSequence();
      }
      break;
    case GAME_STATUS_END:
      if(pressedButton < NO_PIN) {
        initGame();
      }
      break;
  }
}


void initGame()
{
  score = 0;
  sequenceIndex = 0;
  lastPlayerAction = 0;
  memset(sequence, NO_PIN, sizeof(sequence));
  isWaitingForPlayer = false;
  isColorPlayed = true;
  pressedButton = NO_PIN;
  gameDelay = DELAY_INIT;
  gameStatus = GAME_STATUS_RUNNING;

  randomSeed(analogRead(0));
  showScore();

  // intro sequence
  playColor(0, frequencies[0], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(3, frequencies[3], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(0, frequencies[0], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(3, frequencies[3], 300, 30);
  playColor(1, frequencies[1], 300, 30);
  playColor(1, frequencies[1], 300, 30);
  delay(1000);
}


void playColor(byte ledPinIndex, int toneFrequency, int delayTime, int pauseTime)
{
  digitalWrite(ledPins[ledPinIndex], HIGH);
  tone(PIN_PIEZO, toneFrequency);
  delay(delayTime);
  digitalWrite(ledPins[ledPinIndex], LOW);
  noTone(PIN_PIEZO);
  delay(pauseTime);
}


void setGameOver()
{
  gameStatus = GAME_STATUS_END;

  oleDisplay.setCursor(20,20);
  oleDisplay.print("Game Over!");
  oleDisplay.display();

  tone(PIN_PIEZO, 100);
  delay(2000);
  noTone(PIN_PIEZO);
}


void playSuccess()
{
  for(byte i=0; i<sizeof(ledPins); i++) {
      playColor(i, frequencies[i]*2, 40, 10);
  }
  delay(1000);
}


void showScore()
{
  oleDisplay.clearDisplay();
  oleDisplay.setCursor(0,0);
  oleDisplay.print("Score: " + String(score));
  oleDisplay.display();
}


void playSequence()
{
  bool doBreak = false;

  for(byte i=0; i<sizeof(sequence); i++) {
    if (sequence[i] == NO_PIN) {
      sequence[i] = random(0, 4);
      doBreak = true;
    }

    playColor(sequence[i], frequencies[sequence[i]], gameDelay, 200);
    if (doBreak == true) {
      break;
    }
  }

  sequenceIndex = 0;
  isWaitingForPlayer = true;
  lastPlayerAction = millis();
}


void isPlayerAction()
{
  // player did the whole sequence
  if (sequence[sequenceIndex] == NO_PIN) {
    isWaitingForPlayer = false;
    score++;
    showScore();
    if (gameDelay > DELAY_MIN && gameDelay > DELAY_STEP) {
      gameDelay -= DELAY_STEP;
    }
    pressedButton = NO_PIN;
    playSuccess();
    return;
  }

  // no button pressed wait until timeout
  if (pressedButton == NO_PIN) {
    if ((millis() - lastPlayerAction) > BUTTON_MAX_INTERVAL) {
      setGameOver();
    }
    return;
  }

  // repeat color the player pressed
  if (!isColorPlayed) {
	  playColor(pressedButton, frequencies[pressedButton], 300, 50);
	  isColorPlayed = true;
  }

  // check current color
  if (sequence[sequenceIndex] == pressedButton) {
    sequenceIndex++;
  } else {
    setGameOver();
  }
  pressedButton = NO_PIN;
}


void checkButtonPressed()
{
  if ((millis() - lastButtonPressed) < BUTTON_DEBOUNCE_INTERVAL) {
    return;
  }
  if (digitalRead(buttonPins[pressedButton]) == HIGH) {
    // current button is released now
    pressedButton = NO_PIN;
  }
  for(byte i=0; i<sizeof(buttonPins); i++) {
    if(digitalRead(buttonPins[i]) == LOW) {
      if (pressedButton == i) {
        // same button is still pressed
        break;
      }
      pressedButton = i;
      isColorPlayed = false;
      lastPlayerAction = millis();
    }
  }

  lastButtonPressed = millis();
}

Alternative mit Arduino Nano

Aufbau

Vereinfachter Prototyp mit Arduino Nano
Abb.: Vereinfachter Prototyp ohne OLED und mit Arduino Nano

Sketch

#define PIN_LED_0 4
#define PIN_LED_1 5
#define PIN_LED_2 6
#define PIN_LED_3 7

#define PIN_BUTTON_0 8
#define PIN_BUTTON_1 9
#define PIN_BUTTON_2 10
#define PIN_BUTTON_3 11

#define PIN_PIEZO 12

#define GAME_STATUS_END     1
#define GAME_STATUS_RUNNING 2
#define BUTTON_MAX_INTERVAL 4000 // in milliseconds
#define DELAY_INIT          1000 // in milliseconds
#define DELAY_MIN            100 // in milliseconds
#define DELAY_STEP            50 // in milliseconds

#define BUTTON_DEBOUNCE_INTERVAL 50 // in milliseconds
#define NO_PIN 255

bool isWaitingForPlayer, isColorPlayed;
byte ledPins[4] = {PIN_LED_0, PIN_LED_1, PIN_LED_2, PIN_LED_3};
byte buttonPins[4] = {PIN_BUTTON_0, PIN_BUTTON_1, PIN_BUTTON_2, PIN_BUTTON_3};
byte gameStatus, sequenceIndex, pressedButton, sequence[100];
int gameDelay, frequencies[4] = {440, 580, 650, 720};
unsigned long lastPlayerAction, lastButtonPressed;

void setup()
{
  pinMode(PIN_PIEZO, OUTPUT);
  for(byte i=0; i<sizeof(buttonPins); i++) {
    pinMode(buttonPins[i], INPUT);
    pinMode(ledPins[i], OUTPUT);
  }

  initGame();
}


void loop()
{
  checkButtonPressed();
  switch(gameStatus)
  {
    case GAME_STATUS_RUNNING:
      if (isWaitingForPlayer == true) {
        isPlayerAction();
      } else {
        playSequence();
      }
      break;
    case GAME_STATUS_END:
      if(pressedButton < NO_PIN) {
        initGame();
      }
      break;
  }
}


void initGame()
{
  sequenceIndex = 0;
  lastPlayerAction = 0;
  memset(sequence, NO_PIN, sizeof(sequence));
  isWaitingForPlayer = false;
  isColorPlayed = true;
  pressedButton = NO_PIN;
  gameDelay = DELAY_INIT;
  gameStatus = GAME_STATUS_RUNNING;

  randomSeed(analogRead(0));

  // intro sequence
  playColor(0, frequencies[0], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(3, frequencies[3], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(0, frequencies[0], 150, 30);
  playColor(1, frequencies[1], 150, 30);
  playColor(3, frequencies[3], 300, 30);
  playColor(1, frequencies[1], 300, 30);
  playColor(1, frequencies[1], 300, 30);
  delay(1000);
}


void playColor(byte ledPinIndex, int toneFrequency, int delayTime, int pauseTime)
{
  digitalWrite(ledPins[ledPinIndex], HIGH);
  tone(PIN_PIEZO, toneFrequency);
  delay(delayTime);
  digitalWrite(ledPins[ledPinIndex], LOW);
  noTone(PIN_PIEZO);
  delay(pauseTime);
}


void setGameOver()
{
  gameStatus = GAME_STATUS_END;

  tone(PIN_PIEZO, 100);
  delay(2000);
  noTone(PIN_PIEZO);
}


void playSuccess()
{
  for(byte i=0; i<sizeof(ledPins); i++) {
      playColor(i, frequencies[i]*2, 40, 10);
  }
  delay(1000);
}


void playSequence()
{
  bool doBreak = false;

  for(byte i=0; i<sizeof(sequence); i++) {
    if (sequence[i] == NO_PIN) {
      sequence[i] = random(0, 4);
      doBreak = true;
    }

    playColor(sequence[i], frequencies[sequence[i]], gameDelay, 200);
    if (doBreak == true) {
      break;
    }
  }

  sequenceIndex = 0;
  isWaitingForPlayer = true;
  lastPlayerAction = millis();
}


void isPlayerAction()
{
  // player did the whole sequence
  if (sequence[sequenceIndex] == NO_PIN) {
    isWaitingForPlayer = false;
    if (gameDelay > DELAY_MIN && gameDelay > DELAY_STEP) {
      gameDelay -= DELAY_STEP;
    }
    pressedButton = NO_PIN;
    playSuccess();
    return;
  }

  // no button pressed wait until timeout
  if (pressedButton == NO_PIN) {
    if ((millis() - lastPlayerAction) > BUTTON_MAX_INTERVAL) {
      setGameOver();
    }
    return;
  }

  // repeat color the player pressed
  if (!isColorPlayed) {
	  playColor(pressedButton, frequencies[pressedButton], 300, 50);
	  isColorPlayed = true;
  }

  // check current color
  if (sequence[sequenceIndex] == pressedButton) {
    sequenceIndex++;
  } else {
    setGameOver();
  }
  pressedButton = NO_PIN;
}


void checkButtonPressed()
{
  if ((millis() - lastButtonPressed) < BUTTON_DEBOUNCE_INTERVAL) {
    return;
  }
  if (digitalRead(buttonPins[pressedButton]) == HIGH) {
    // current button is released now
    pressedButton = NO_PIN;
  }
  for(byte i=0; i<sizeof(buttonPins); i++) {
    if(digitalRead(buttonPins[i]) == LOW) {
      if (pressedButton == i) {
        // same button is still pressed
        break;
      }
      pressedButton = i;
      isColorPlayed = false;
      lastPlayerAction = millis();
    }
  }

  lastButtonPressed = millis();
}
zurück