// ~ Simple Puzzle-Box ~
// By Dion Atsma

// In this program, the player's goal is to create a dim magenta light. 
// Achieving this will open the box using the Servo.
// The player can mix colours by turning them on using the buttons,
// and change the brightness using the potentiometer.

#include <Servo.h>
#include "pitches.h"

// These control the different LEDS within the RGB-LED.
#define RGBred 3
#define RGBgreen 5
#define RGBblue 6

// These control individual coloured LED's.
#define LEDred 12
#define LEDgreen 2
#define LEDblue 4

// Buzzer and Servo for playing a sound and opening the box.
#define Buzzer 11
Servo servo;

// Three buttons to control the lights.
#define ButtonA 8
#define ButtonB 13
#define ButtonC 7

// A potentiometer to turn up the brightness of the lights.
#define TurnButton A1

// Concerns whether the LED's are on.
bool redOn = false;
bool blueOn = false;
bool greenOn = true;

// Brightness of the RGB-LED.
int brightness = 100;

// Boolean to check whether the player has solved the puzzle.
bool hasWon = false;

// Concerns the button states
bool stateA = false;
bool lastStateA = false;
bool stateB = false;
bool lastStateB = false;
bool stateC = false;
bool lastStateC = false;

// Notes in the melody that is played when the puzzle is solved:
int melody[] = {
  NOTE_G5, NOTE_FS5, NOTE_DS5, NOTE_A4, NOTE_GS4, NOTE_E5, NOTE_GS5, NOTE_C6
};

void setup() {
  Serial.begin(9600);

  pinMode(RGBred, OUTPUT);
  pinMode(RGBgreen, OUTPUT);
  pinMode(RGBblue, OUTPUT);
  pinMode(LEDred, OUTPUT);
  pinMode(LEDgreen, OUTPUT);
  pinMode(LEDblue, OUTPUT);

  pinMode(Buzzer, OUTPUT);
  servo.attach(9);

  pinMode(ButtonA, INPUT_PULLUP);
  pinMode(ButtonB, INPUT_PULLUP);
  pinMode(ButtonC, INPUT_PULLUP);

  pinMode(TurnButton, INPUT);
}

void loop() {
  // Constantly checks if the puzzle has been solved yet.
  checkWin();

  // If the player solves the puzzle, the lights turn off and stay off.
  if (hasWon) {
    turnTheLightsOff();
    Serial.println("Won");
    lightsTrue(redOn, LEDred, RGBred);
    lightsTrue(greenOn, LEDgreen, RGBgreen);
    lightsTrue(blueOn, LEDblue, RGBblue);
    return;
  }

  // Main functions that are constantly active.
  checkBrightness();
  
  buttonStates();
  lightsTrue(redOn, LEDred, RGBred);
  lightsTrue(greenOn, LEDgreen, RGBgreen);
  lightsTrue(blueOn, LEDblue, RGBblue);
}

// This function takes input from the potentiometer and maps it to
// work as a brightness for the LED's.
void checkBrightness() {
  int brightnessUnmapped = analogRead(TurnButton);
  brightness = map(brightnessUnmapped, 0, 1024, 0, 255);
}

// This function reads the current button state and the button state from the
// previous loop, and updates them every frame for each button.
void buttonStates() {
  stateA = digitalRead(ButtonA);
  lastStateA = checkState(stateA, lastStateA, ButtonA);

  stateB = digitalRead(ButtonB);
  lastStateB = checkState(stateB, lastStateB, ButtonB);

  stateC = digitalRead(ButtonC);
  lastStateC = checkState(stateC, lastStateC, ButtonC);
}

// This function compares the current state to the lastState of the buttons
// to see if this has changed, meaning the player has pressed the button.
// When this happens, some lights are turned on and off.
bool checkState(bool state, bool lastState, int pin) {
  if (state != lastState) {
    if (state == HIGH) {
      if (pin == 8) {
        onOffTwoThree();
      }
      else if (pin == 7) {
        onOffOneTwo();
      }
      else {
        onOffAll();
      }
    } else {
      Serial.println("off");
    }

    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastState = state;
  return lastState;
}

// Checks if the puzzle has been solved, meaning:
// - The puzzle is not solved yet;
// - Only the Red and Blue LED are on (RGB-LED now emits Magenta light);
// - The light is dim but not off.
void checkWin() {
  if (brightness > 20 && brightness < 80 
      && redOn && blueOn && !greenOn && !hasWon) {
        hasWon = true;
        win();
      }
}

// When the player wins, a sound is played and the servo moves.
void win() {
  // Play puzzle-solve jingle.
  playMusic();

  // Set Servo to open the box.
  servo.write(180);
}

// Plays the puzzle solve jingle from the Legend of Zelda series.
void playMusic() {
  for (int thisNote = 0; thisNote < 9; thisNote++) {
    int noteDuration = 1000 / 8;
    tone(Buzzer, melody[thisNote], noteDuration);

    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);

    noTone(Buzzer);
  }
}

// Turns off all LED's.
void turnTheLightsOff() {
  redOn = false;
  greenOn = false;
  blueOn = false;
}

// Turns the red and green LED's on or off.
void onOffOneTwo() {
  redOn = !redOn;
  greenOn = !greenOn;
}

// Turns the blue and green LED's on or off.
void onOffTwoThree() {
  blueOn = !blueOn;
  greenOn = !greenOn;
}

// Turns all LED's on or off.
void onOffAll() {
  redOn = !redOn;
  blueOn = !blueOn;
  greenOn = !greenOn;
}

// This function checks for each colour of LED if they should currently
// be on or off (depending on their respective booleans), and turns
// the single LED's on or off, and gives the RGB-LED's the current
// brightness if they are on, or 0 brightness if they are off.
void lightsTrue(bool ledOn, int LED, int RGB) {
  if (ledOn) { 
    digitalWrite(LED, HIGH);
    analogWrite(RGB, brightness);
  } else { 
    digitalWrite(LED, LOW); 
    analogWrite(RGB, 0); 
  }
}
