/**************************************************
 *  Author: Danilo Gallo                          *
 *  Datum: 12.06.2022                             *
 *  Klasse: INS2A                                 *
 *  Titel: Simon Says - Senso                     *
 *************************************************/

/**************************************************
   Diese Datei definiert Konstanten mit der Frequenz
   der verschiedenen Musiknoten.
   Quelle: https://www.arduino.cc/en/Tutorial/BuiltInExamples/toneMelody
 *************************************************/
#include "pitches.h" 

/**************************************************
   Konstants: 
   Pin-Nummern für LEDs, Tasten und Lautsprecher 
   sowie Spieltöne werden definiert: 
 *************************************************/
const byte ledPins[] = {10, 11, 12, 13}; //Pin 10-13 für LEDs
const byte buttonPins[] = {4, 5, 6, 7}; //Pin 2-5 für Buttons
#define SPEAKER_PIN 2

#define MAX_GAME_LENGTH 50 //Kombination max. 50 Elemente

const int gameTones[] = { NOTE_G3, NOTE_C4, NOTE_E4, NOTE_G5}; //Definition der Töne für jede Farbe

/**************************************************
   Globale Variablen 
 *************************************************/
byte gameSequence[MAX_GAME_LENGTH] = {0}; //Enthält die Kombination der Tasten
byte gameIndex = 0; //Enthält die erreichte Stufe/Niveau

int delaytime; //Variable für Delay

bool LEDs; // true oder false Variable zum Einschalten der LEDs
bool music; // true oder false Variable zum Einschalten der Musik
bool picked; // true oder false Variable zur Überprüfung, ob man sich im Menü befindet

int RandomLED; // Zufallsvariable zur Auswahl eines Led

/**************************************************
  Variablen zum Speichern der LEDS-Zustände für 
  zufälliges Blinken und Abspielen
 *************************************************/ 
bool led1; 
bool led2;
bool led3;
bool led4;

/**************************************************
   Einrichten des Arduino-Boards und Initialisierung 
   der seriellen Kommunikation
 *************************************************/
void setup() {
  Serial.begin(9600);
  for (byte i = 0; i < 4; i++) {
    pinMode(ledPins[i], OUTPUT);
    pinMode(buttonPins[i], INPUT_PULLUP);
  }

/**************************************************
    Display the main menu 
 *************************************************/
  Serial.println("1 (rot) button - Start normal game."); 
  Serial.println("2 (gruen) button - Start game ohne Ton.");
  Serial.println("3 (blau) button - Start game ohne LEDs.");
  Serial.println();
  Serial.println("Bitte wählen Sie den gewünschten Game-Modus :-)");
  pinMode(SPEAKER_PIN, OUTPUT);
  // Die folgende Zeile setzt den Zufallszahlengenerator in Gang.
  // Sie geht davon aus, dass Pin A0 potentialfrei ist (nicht angeschlossen):
  randomSeed(analogRead(A0));
  //Erklärung: Der Startpunkt für die Erzeugung von Zufallszahlen wird als Seed bezeichnet.
  //In diesem Fall liest der Code den Wert eines nicht angeschlossenen Analogeingangs und verwendet ihn als Startwert. 
  //Dies ist eher zufällig, denn die nicht angeschlossenen Stifte verhalten sich im Grunde wie Antennen 
  //und nehmen elektromagnetische Felder aus der Umgebung auf.
}

/**************************************************
   Leuchtet die LED und spielt einen passenden Ton
 *************************************************/
void lightLedAndPlayTone(byte ledIndex) {
  if (LEDs) digitalWrite(ledPins[ledIndex], HIGH);
  if (music) tone(SPEAKER_PIN, gameTones[ledIndex]);

  delaytime = map(gameIndex, 0, 50, 500, 10); // Legt den Delay nach Stufe fest, von Stufe 0 bis 50, Delay 500ms bis 10ms
  Serial.print("Delay in ms is: "); // Anzeige der aktuellen Dely im seriellen Monitor
  Serial.println(delaytime);
  delay(delaytime);

  if (LEDs) digitalWrite(ledPins[ledIndex], LOW);
  if (music) noTone(SPEAKER_PIN);
}

/**************************************************
   Spielt die Notenfolge ab, die der Benutzer 
   wiederholen muss
 *************************************************/
void playSequence() {
  for (int i = 0; i < gameIndex; i++) {
    byte currentLed = gameSequence[i];
    lightLedAndPlayTone(currentLed);
    delay(50);
  }
}

/**************************************************
    Wartet, bis der Benutzer eine der Tasten gedrückt hat,
    und gibt den Index dieser Taste zurück
 *************************************************/
byte readButtons() {
  while (true) {
    for (byte i = 0; i < 4; i++) {
      byte buttonPin = buttonPins[i];
      if (digitalRead(buttonPin) == LOW) {
        return i;
      }
    }
    delay(1);
  }
}

/**************************************************
   "Game over sequence" abspielen und game score 
   auf dem Serial Monitor zeigen
 *************************************************/
void gameOver() {
  Serial.print("Game over! your score: ");
  Serial.println(gameIndex - 1);
  gameIndex = 0;
  delay(200);

  //  Wah-Wah-Wah-Wah sound abspielen
  tone(SPEAKER_PIN, NOTE_DS5);
  delay(300);
  tone(SPEAKER_PIN, NOTE_D5);
  delay(300);
  tone(SPEAKER_PIN, NOTE_CS5);
  delay(300);
  for (byte i = 0; i < 10; i++) {
    for (int pitch = -10; pitch <= 10; pitch++) {
      tone(SPEAKER_PIN, NOTE_C5 + pitch);
      delay(5);
    }
  }
  noTone(SPEAKER_PIN);
  delay(500);
  
  // Am Ende des Spiels wird das Menü aktiviert und auf dem seriellen Monitor angezeigt.
  Serial.println("1 (rot) button - Start normal game."); 
  Serial.println("2 (gruen) button - Start game ohne Ton.");
  Serial.println("3 (blau) button - Start game ohne LEDs.");
  Serial.println();
  Serial.println("Bitte wählen Sie den gewünschten Game-Modus :-)");
  picked = 0;
}

/**************************************************
   Abfrage der Benutzereingaben und Vergleich 
   mit der erwarteten Reihenfolge.
 *************************************************/
bool checkUserSequence() {
  for (int i = 0; i < gameIndex; i++) {
    byte expectedButton = gameSequence[i];
    byte actualButton = readButtons();
    lightLedAndPlayTone(actualButton);
    if (expectedButton != actualButton) {
      return false;
    }
  }

  return true;
}

/**************************************************
   Spielt einen Hurra!-Sound, wenn der Benutzer 
   ein Level beendet
 *************************************************/
void playLevelUpSound() {
  tone(SPEAKER_PIN, NOTE_E4);
  delay(150);
  tone(SPEAKER_PIN, NOTE_G4);
  delay(150);
  tone(SPEAKER_PIN, NOTE_E5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_C5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_D5);
  delay(150);
  tone(SPEAKER_PIN, NOTE_G5);
  delay(150);
  noTone(SPEAKER_PIN);
}

/**************************************************
   The main game loop (Attract Modus)
 *************************************************/
void loop() {
  if (!picked) { // Wenn man sich im Menü befindet
    RandomLED = random(0, 4); // Wähle eine zufällige Zahl für den Betrieb mit LED

    if (RandomLED == 0) led1 = !led1; // Zustand der ausgewählten LED ändern
    if (RandomLED == 1) led2 = !led2;
    if (RandomLED == 2) led3 = !led3;
    if (RandomLED == 3) led4 = !led4;

    delay(150);  

    RandomLED = random(0, 4); // Zufallszahl wird generiert, um den Ton abzuspielen

    if (RandomLED == 0) { // Ton abspiele und shuffle die LEDs.
      led1 = !led1;
      tone(SPEAKER_PIN, NOTE_G3);
      delay(100);
      noTone(SPEAKER_PIN);
    }
    if (RandomLED == 1) {
      led2 = !led2;
      tone(SPEAKER_PIN, NOTE_C4);
      delay(100);
      noTone(SPEAKER_PIN);
    }
    if (RandomLED == 2) {
      led3 = !led3;
      tone(SPEAKER_PIN, NOTE_E4);
      delay(100);
      noTone(SPEAKER_PIN);
    }
    if (RandomLED == 3) {
      led4 = !led4;
      tone(SPEAKER_PIN, NOTE_G5);
      delay(100);
      noTone(SPEAKER_PIN);
    }

    digitalWrite(ledPins[0], led1);  //  Die aktuellen Zustände werden in die LEDs geschrieben
    digitalWrite(ledPins[1], led2);
    digitalWrite(ledPins[2], led3);
    digitalWrite(ledPins[3], led4);

    if (digitalRead(buttonPins[3]) == LOW) { // Wenn einer Taste gerückt wird
      picked = HIGH; // Menu abschalten, Piezo und LED-Variablen ein- oder ausschalten und die LEDs ausschalten.
      LEDs = true;
      music = true;
      digitalWrite(ledPins[0], LOW);
      digitalWrite(ledPins[1], LOW);
      digitalWrite(ledPins[2], LOW);
      digitalWrite(ledPins[3], LOW);
      delay(1000);
    }
    if (digitalRead(buttonPins[2]) == LOW) {
      picked = HIGH;
      LEDs = true;
      music = false;
      digitalWrite(ledPins[0], LOW);
      digitalWrite(ledPins[1], LOW);
      digitalWrite(ledPins[2], LOW);
      digitalWrite(ledPins[3], LOW);
      delay(1000);
    }
    if (digitalRead(buttonPins[1]) == LOW) {
      picked = HIGH;
      LEDs = false;
      music = true;
      digitalWrite(ledPins[0], LOW);
      digitalWrite(ledPins[1], LOW);
      digitalWrite(ledPins[2], LOW);
      digitalWrite(ledPins[3], LOW);
      delay(1000);
    }

  }
  else { //Wenn man spielt (also wenn man sich nicht im Menu befindet)
    // einer zufälligen Farbe am Ende der Sequenz hinzufügen
    gameSequence[gameIndex] = random(0, 4);
    gameIndex++;
    if (gameIndex >= MAX_GAME_LENGTH) {
      gameIndex = MAX_GAME_LENGTH - 1;
    }

    playSequence(); //wenn die beiden Sequenzen nicht stimment
    if (!checkUserSequence()) {
      gameOver(); //wird die Funktion gameOver aufgerufen
    }

    delay(300);

    //gameIndex wird in Fall von Gameover auf 0 gesetzt.
    //Also wird nach jedem korrekten Spielrunde di funktion fürs Spielen
    //des Hurra!Sound aufgerufen
    if (gameIndex > 0) {
      playLevelUpSound();
      delay(500);
    }
  }
}