Suche

Simon Says Memory

Bevor wir mit dem Zusammenbau des Simon Says Memorys beginnen, programmieren wir erst den Mikrocontroller. In dieser Anleitung verwenden wir einen Arduino NANO.

Abschnitt 1 - Definitionen

Als erstes werden die Frequenzen der Noten für die Buzzer festgelegt

#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978

Jetzt werden die Knöpfe, Buzzer und LEDs konfiguriert

#define CHOICE_OFF      0 //Wird benutzt um die LEDs aus zu schalten
#define CHOICE_NONE     0 //Wird benutzt um die Knöpfe zu kontrollieren
#define CHOICE_RED  (1 << 0)
#define CHOICE_GREEN    (1 << 1)
#define CHOICE_BLUE (1 << 2)
#define CHOICE_YELLOW   (1 << 3)

//Pin Belegung für die LEDs
#define LED_RED     10
#define LED_GREEN   3
#define LED_BLUE    13
#define LED_YELLOW  5

// Pin Belegung für die Knöpfe
#define BUTTON_RED    9
#define BUTTON_GREEN  2
#define BUTTON_BLUE   12
#define BUTTON_YELLOW 6

// Pin Belegung für die Buzzer
#define BUZZER1  4
#define BUZZER2  7

Anschließend werden die Spiel Parameter festgelegt und zusätzliche Variablen

// Definire Spiel Parameter
#define ROUNDS_TO_WIN      13 //Anzahl an Runden die gewonnen werden müssen um zu gewinnen. 13 sollte machbar sein
#define ENTRY_TIME_LIMIT   3000 //Zeitspanne bevor das Spiel stoppt. 3000ms = 3 Sekunden

#define MODE_MEMORY  0
#define MODE_BATTLE  1
#define MODE_BEEGEES 2

// Spiel Status Variablen
byte gameMode = MODE_MEMORY; //Standardmäßig lasst uns das Memory-Spiel spielen
byte gameBoard[32]; //Enthält die Kombination von Tasten, während wir fortschreiten
byte gameRound = 0; //Zählt die Anzahl der Runden, die der Spieler geschafft hat

Abschnitt 2 - Setup

Nachdem wir jetzt die Pins, Parameter und alles ähnliche festgelegt haben, fängt nun das Setup an. Dieser Teil ist in jedem Sketch unverzichtbar.

void setup()
{
  //Richte die Hardware-Eingänge/-Ausgänge ein. Diese Pins sind in der Header-Datei hardware_versions definiert.

  //Aktiviere Pull-up-Widerstände an den Eingängen
  pinMode(BUTTON_RED, INPUT_PULLUP);
  pinMode(BUTTON_GREEN, INPUT_PULLUP);
  pinMode(BUTTON_BLUE, INPUT_PULLUP);
  pinMode(BUTTON_YELLOW, INPUT_PULLUP);

  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);
  pinMode(LED_YELLOW, OUTPUT);

  pinMode(BUZZER1, OUTPUT);
  pinMode(BUZZER2, OUTPUT);

  //Modus wird gecheckt
  gameMode = MODE_MEMORY; // By default, we're going to play the memory game

  // Check to see if the lower right button is pressed
  if (checkButton() == CHOICE_YELLOW) play_beegees();

  // Checke ob der obere rechte Knopf gedrückt wird
  if (checkButton() == CHOICE_GREEN)
  {
    gameMode = MODE_BATTLE; //Setze den Battle Modus als Spielmodus fest
    //Turn on the upper right (green) LED
    setLEDs(CHOICE_GREEN);
    toner(CHOICE_GREEN, 150);

    setLEDs(CHOICE_RED | CHOICE_BLUE | CHOICE_YELLOW); // Schalte die anderen LEDs ein, bis du den Knopf loslässt

    while(checkButton() != CHOICE_NONE) ; // Warte bis der Spieler aufhört den Knopf zu drücken

    //Jetzt passiert nichts. Der Battle Modus  wird jetzt die Haupt-routine sein.
  }

  play_winner(); // Nachdem das Setup vorbei ist, spiele "play_winner" ab
}

Abschnitt 3 - Loop

Jetzt wird der Loop Teil programmiert. Dieser Teil ist in jedem Sketch unverzichtbar.

void loop()
{
  attractMode(); // Blinkende LEDs während wir auf eine Eingabe des Spielers warten

  //zeigen den Spielstart an
  setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE | CHOICE_YELLOW); // Schalte die LEDs an
  delay(1000);
  setLEDs(CHOICE_OFF); // Schalte die LEDs aus
  delay(250);

  if (gameMode == MODE_MEMORY)
  {
    // Spiele das Memory und verarbeite die Resultate
    if (play_memory() == true) 
      play_winner(); // Spieler hat gewonnen, spiele die gewonnen-melodie ab
    else 
      play_loser(); // Spieler hat verloren, spiele die verloren-melodie ab
  }

  if (gameMode == MODE_BATTLE)
  {
    play_battle(); // Spiele so lange bis jemand verliert

    play_loser(); // Spieler hat verloren, spiele verloren-melodie ab
  }
}

Spielfunktionen

Die folgenden Funktionen haben nur auf das Spiel einfluss

Funktion: Spiele normales Memory


// Gibt '0' zurück, wenn der Spieler verliert oder eine '1' wenn der Spieler gewinnt
boolean play_memory(void)
{
  randomSeed(millis()); // Setze einen Seed für den Zufallsgenerator mit einer Zufallszahl n Millisekunden()

  gameRound = 0; // Setze das Spiel zurück

  while (gameRound < ROUNDS_TO_WIN) 
  {
    add_to_moves(); // Füge einen Button zu der bisherigen Reihenfolge...

    playMoves(); // ... und spiele diese dann ab

    // Jetzt muss der Spieler die Reihenfolge wiederholen
    for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
    {
      byte choice = wait_for_button(); // warte darauf welchen Knopf der Spieler drückt

      if (choice == 0) return false; // Falls nichts gedrückt wurde, verliert der Spieler

      if (choice != gameBoard[currentMove]) return false; // Falls ein falscher Knopf gedrückt wurde, verliert der Spieler
    }

    delay(1000); // Der Spieler hat alles richtig gemacht, warte kurz bevor die Reihenfolge abgespielt wird
  }

  return true; // Der Spieler hat es durch alle Runden geschafft und gewonnen
}

Funktion: Spiele den 2-Spieler Battle-Modus

Ein Spieler beginnt mit einem Knopfdruck und gibt es dann dem anderen Spieler. Dieser drückt dann erneut diesen Knopf und fügt einen Knopfdruck hinzu. Dann wird es wieder zurück gegeben usw. Diese Funktion endet, wenn eine Spieler verliert.

boolean play_battle(void)
{
  gameRound = 0; // Setze das Spiel zurück

  while (1) // Loop bis jemand einen Fehler macht
  {
    byte newButton = wait_for_button(); // Warte bis der Spieler einen weiteren move macht
    gameBoard[gameRound++] = newButton; // Füge diesen Knopfdruck der Reihenfolge hinzu
    // Jetzt muss der Spieler die Reihenfolge wiederholen
    for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++)
    {
      byte choice = wait_for_button();

      if (choice == 0) return false; // Wenn der Spieler zu lange braucht, verliert er

      if (choice != gameBoard[currentMove]) return false; // Wenn der Spieler was falsches drückt, verliert er
    }

    delay(100); //  gebe dem Spieler extra 100ms um dem anderen Spieler die Box zu geben
  }

  return true; // Hier sollten wir im Idealfall nie hinkommen
}

Funktion: Spiele die momentane Reihenfolge ab

void playMoves(void)
{
  for (byte currentMove = 0 ; currentMove < gameRound ; currentMove++) 
  {
    toner(gameBoard[currentMove], 150);

    // Warte ein bisschen bis der nächste Knopf aufleuchtet
    // Verkürze diesen Zeitraum um das Spiel schwerer zu machen
    delay(150); // 150 geht gut, 75 wird schnell
  }
}

Funktion: füge einen weiteren Knopf hinzu, indem ein Ausschnitt vom Timer genommen wird.

void add_to_moves(void)
{
  byte newButton = random(0, 4); //minimum (eingeschlossen), maximum (ausgeschlossen)

  // Wir müssen die Nummer, 0 bis 3, in Auswahlmöglichkeiten (CHOICEs) umwandeln
  if(newButton == 0) newButton = CHOICE_RED;
  else if(newButton == 1) newButton = CHOICE_GREEN;
  else if(newButton == 2) newButton = CHOICE_BLUE;
  else if(newButton == 3) newButton = CHOICE_YELLOW;

  gameBoard[gameRound++] = newButton; // Füge diesen neuen Knopf zur Reihenfolge hinzu
}

Hardwarefunktionen

Die folgenden Funktionen kontrollieren die Hardware.

Funktion: Erleuchtet die entsprechenden LEDs

// Fügt einen Byte ein der aus CHOICE_RED, CHOICE_YELLOW, etc. besteht
void setLEDs(byte leds)
{
  if ((leds & CHOICE_RED) != 0)
    digitalWrite(LED_RED, HIGH);
  else
    digitalWrite(LED_RED, LOW);

  if ((leds & CHOICE_GREEN) != 0)
    digitalWrite(LED_GREEN, HIGH);
  else
    digitalWrite(LED_GREEN, LOW);

  if ((leds & CHOICE_BLUE) != 0)
    digitalWrite(LED_BLUE, HIGH);
  else
    digitalWrite(LED_BLUE, LOW);

  if ((leds & CHOICE_YELLOW) != 0)
    digitalWrite(LED_YELLOW, HIGH);
  else
    digitalWrite(LED_YELLOW, LOW);
}

Funktion: Warte bis ein Knopf gedrückt wird.

// Gibt eine der LED Farben wieder (LED_RED, etc.) wenn erfolgrein, 0 falls nichts gedrückt wurde
byte wait_for_button(void)
{
  long startTime = millis(); // Erinnere dich wann dieser Loop gestartet wurde

  while ( (millis() - startTime) < ENTRY_TIME_LIMIT) // Loope bis zu viel Zeit vergangen ist
  {
    byte button = checkButton();

    if (button != CHOICE_NONE)
    { 
      toner(button, 150); // Spiele den Knopf ab, den der Spieler grad gedrückt hat

      while(checkButton() != CHOICE_NONE) ;  // Warte jetzt bis der Spieler den Knopf loslässt

      delay(10); // Das hilft gegen "debouncing" und ausversehen doppeltes drücken

      return button;
    }

  }

  return CHOICE_NONE; // Wenn wir hier ankommen, hat der Spieler nichts gedrückt und verloren
}

Funktion: gibt eine 1 als Bit zurück in der entsprechenden Position von CHOICE_RED, CHOICE_GREEN, etc.

byte checkButton(void)
{
  if (digitalRead(BUTTON_RED) == 0) return(CHOICE_RED); 
  else if (digitalRead(BUTTON_GREEN) == 0) return(CHOICE_GREEN); 
  else if (digitalRead(BUTTON_BLUE) == 0) return(CHOICE_BLUE); 
  else if (digitalRead(BUTTON_YELLOW) == 0) return(CHOICE_YELLOW);

  return(CHOICE_NONE); // Wenn kein Knopf gedrückt wurde, gebe CHOICE_NONE zurück
}

Funktion: erleuchte eine LED und spiele Ton

Rot, oben-links: 440Hz – 2.272ms – 1.136ms Puls
Grün, oben-rechts: 880Hz – 1.136ms – 0.568ms Puls
Blau, unten-links: 587.33Hz – 1.276ms – 0.638ms Puls
Gelb, unten-rechts: 784Hz – 1.276ms – 0.638ms Puls

void toner(byte which, int buzz_length_ms)
{
  setLEDs(which); //Schalte die entsprechende LED an

  //Spiele den entsprechenden Ton der LED
  switch(which) 
  {
  case CHOICE_RED:
    buzz_sound(buzz_length_ms, 1136); 
    break;
  case CHOICE_GREEN:
    buzz_sound(buzz_length_ms, 568); 
    break;
  case CHOICE_BLUE:
    buzz_sound(buzz_length_ms, 851); 
    break;
  case CHOICE_YELLOW:
    buzz_sound(buzz_length_ms, 638); 
    break;
  }

  setLEDs(CHOICE_OFF); // Schalte alle LEDs aus
}

Funktion: Schalte den Buzzer an/aus mit einer länge von buzz_length_ms und einem delay dazwischen von buzz_delay_us.

 

void buzz_sound(int buzz_length_ms, int buzz_delay_us)
{
  // Konvertiere die komplette Spielzeit von Millisekunden in Mikrosekunden
  long buzz_length_us = buzz_length_ms * (long)1000;

  // Loope bis die rest-spielzeit unter buzz_delay_us ist
  while (buzz_length_us > (buzz_delay_us * 2))
  {
    buzz_length_us -= buzz_delay_us * 2; //Verkürze die verbleibende Spielzeit

    // Schalte den Buzzer in verschiedenen Geschwindigkeiten
    digitalWrite(BUZZER1, LOW);
    digitalWrite(BUZZER2, HIGH);
    delayMicroseconds(buzz_delay_us);

    digitalWrite(BUZZER1, HIGH);
    digitalWrite(BUZZER2, LOW);
    delayMicroseconds(buzz_delay_us);
  }
}

Funktion: Spiele den Gewinner Sound und Lichter

void play_winner(void)
{
  setLEDs(CHOICE_GREEN | CHOICE_BLUE);
  winner_sound();
  setLEDs(CHOICE_RED | CHOICE_YELLOW);
  winner_sound();
  setLEDs(CHOICE_GREEN | CHOICE_BLUE);
  winner_sound();
  setLEDs(CHOICE_RED | CHOICE_YELLOW);
  winner_sound();
}

Funktion: Spiele den Gewinner Sound ab

Dies ist der einzigartige (und auf Dauer nervige) Sound auf den wir gekommen sind.

void winner_sound(void)
{
  // Schalte den Buzzer in unterschiedlichen Intervallen an und aus
  for (byte x = 250 ; x > 70 ; x--)
  {
    for (byte y = 0 ; y < 3 ; y++)
    {
      digitalWrite(BUZZER2, HIGH);
      digitalWrite(BUZZER1, LOW);
      delayMicroseconds(x);

      digitalWrite(BUZZER2, LOW);
      digitalWrite(BUZZER1, HIGH);
      delayMicroseconds(x);
    }
  }
}

Funktion: Spiele die den Verlierer Sound und LED Animation.

void play_loser(void)
{
  setLEDs(CHOICE_RED | CHOICE_GREEN);
  buzz_sound(255, 1500);

  setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
  buzz_sound(255, 1500);

  setLEDs(CHOICE_RED | CHOICE_GREEN);
  buzz_sound(255, 1500);

  setLEDs(CHOICE_BLUE | CHOICE_YELLOW);
  buzz_sound(255, 1500);
 }

Funktion: Zeige ein „attract mode“ (anziehungs-modus) während  darauf gewartet wird, dass der Spieler einen Knopf drückt.

void attractMode(void)
{
  while(1) 
  {
    setLEDs(CHOICE_RED);
    delay(100);
    if (checkButton() != CHOICE_NONE) return;

    setLEDs(CHOICE_BLUE);
    delay(100);
    if (checkButton() != CHOICE_NONE) return;

    setLEDs(CHOICE_GREEN);
    delay(100);
    if (checkButton() != CHOICE_NONE) return;

    setLEDs(CHOICE_YELLOW);
    delay(100);
    if (checkButton() != CHOICE_NONE) return;
  }
}

Easteregg: Beegees

Die folgenden Funktionen sind nur für das Beegees Easteregg da

// Noten in der Melodie. Jede Note ist ungefähr eine Achtelnote. "0" eine Pause
int melody[] = {
  NOTE_G4, NOTE_A4, 0, NOTE_C5, 0, 0, NOTE_G4, 0, 0, 0,
  NOTE_E4, 0, NOTE_D4, NOTE_E4, NOTE_G4, 0,
  NOTE_D4, NOTE_E4, 0, NOTE_G4, 0, 0,
  NOTE_D4, 0, NOTE_E4, 0, NOTE_G4, 0, NOTE_A4, 0, NOTE_C5, 0};

int noteDuration = 115; // Dies setzt das Tempo vor. 115 ist in etwa Disco Tempo :)
int LEDnumber = 0; // Behält den Überblick darüber, bei welcher LED wir uns während der Beegees-Schleife befinden

Funktion: Spiele nichts ausßer die Beegees Musik

Diese Funktion wird aktiviert wenn der Spieler den unteren rechten Knopf gedrückt hält beim Start

void play_beegees()
{
  //Schalte die runtere rechte (gelbe) LED an
  setLEDs(CHOICE_YELLOW);
  toner(CHOICE_YELLOW, 150);

  setLEDs(CHOICE_RED | CHOICE_GREEN | CHOICE_BLUE); // Schalte die anderen LEDs an bis der Spieler den Knopf loslässt

  while(checkButton() != CHOICE_NONE) ; // Warte bis der Spieler den Knopf loslässt

  setLEDs(CHOICE_NONE); // Schalte die LEDs aus

  delay(1000); // Warte eine Sekunde bevor die Musik abgespielt wird

  digitalWrite(BUZZER1, LOW); // Richte die "BUZZER1"-Seite des Buzzers so ein, dass sie auf niedrig bleibt, während wir den Ton auf dem anderen Pin abspielen
  while(checkButton() == CHOICE_NONE) //Spiele die Musik bis der Spieler einen Knopf drückt
  {
    // gehe über die Noten der Melodie
    for (int thisNote = 0; thisNote < 32; thisNote++) {
      changeLED();
      tone(BUZZER2, melody[thisNote],noteDuration);
      // Um die Noten unterscheiden zu können, setze eine Minimalzeit zwischen diesen
      // Die Notenzeit + 30% scheint gut zu funktionieren
      int pauseBetweenNotes = noteDuration * 1.30;
      delay(pauseBetweenNotes);
      // stop the tone playing:
      noTone(BUZZER2);
    }
  }
}

Funktion: Wenn diese Funktion aufgerufen wird, wechselt das board zur nächsten LED.

void changeLED(void)
{
  setLEDs(1 << LEDnumber); // Change the LED

  LEDnumber++; // Ändere die LED zur nächsten LED
  if(LEDnumber > 3) LEDnumber = 0; // Drehe den Zähler um falls nötig
}

Abschluss der Programmierung

Wenn du dir alles durchgelesen und verstanden, aber keine Lust zum abschreiben hast, kannst du sonst auch einfach hier den kompletten Sketch runterladen. Das geht schneller und einfacher.

Aufbau

Entweder kann man eine Box wie eine Wurstdose oder Brotbox verwenden oder wenn man einen 3D-Drucker hat kann man sich eine passende Box drucken.

Wenn du die Wurstdose/Brotbox verwendest schneide zuerst 4 Löcher in den Deckel für die Knöpfe, danach 2 kleine für die Buzzer.

Löte zunächst die Kabel an die alle Kontakte der Knöpfe und Buzzer. Setze dann die Knöpfe und Buzzer ein in die Löcher.

Nun werden die Kabel an den Mikrocontroller angeschlossen:

Grün – Pin 3
Rot – Pin 10
Blau –  Pin 13
Gelb – Pin 5

Grün – Pin 2
Rot – Pin 9
Blau – Pin 12
Gelb – Pin 6

Buzzer 1 – Pin 4

Buzzer 2 – Pin 7

Funduino - Dein Onlineshop für Mikroelektronik

  • Dauerhaft bis zu 10% Rabatt für Schüler, Studenten und Lehrkräfte
  • Mehr als 2.000 Artikel sofort verfügbar!
  • Über 8 Jahre Erfahrung mit Arduino, 3D-Druck und co.
SALE