/* Simon Says Memory nach der Vorlage von SparkFun Inventor's Kit Simon Says ist ein Gedächtnisspiel. Das Spiel beginnt, indem man einen der vier Tasten drückt. Wenn eine Taste aufleuchtet, drücke die Taste und wiederhole die Sequenz. Die Sequenz wird immer länger und länger. Das Spiel ist nach 13 Runden gewonnen. Generiert eine zufällige Sequenz, spielt Musik und zeigt Tastenlichter an. Simon-Töne von Wikipedia: - A (rot, oben links) - 440 Hz - 2,272 ms - 1,136 ms Impuls - a (grün, oben rechts, eine Oktave höher als A) - 880 Hz - 1,136 ms, 0,568 ms Impuls - D (blau, unten links, eine perfekte Quarte höher als oben links) - 587,33 Hz - 1,702 ms - 0,851 ms Impuls - G (gelb, unten rechts, eine perfekte Quarte höher als unten links) - 784 Hz - 1,276 ms - 0,638 ms Impuls Simon Says Spiel ursprünglich in C für den PIC16F88 geschrieben. Portiert für den ATmega168, dann ATmega328, dann Arduino 1.0. Fehlerbehebungen und Aufräumarbeiten von Joshua Neal Übersetzt und angepasst von Funduino */ //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 //Nachdem wir jetzt die Pins, Parameter und alles ähnliche festgelegt haben, fängt nun das Setup an. 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 } //Jetzt wird der Loop Teil programmiert. 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 } } //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //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 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 } //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //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 }