/**
* Secret Bookshelf
* Copyright (c) 2022 Playful Technology
*
* One or more microswitches are mounted on the shelves of a bookcase, such that they
* are pressed when a book is placed on top of them and released when the book is pivoted forward.
* Players must pull the correct pattern of books forward (and *only* those books) in order
* to activate a relay, releasing a maglock and pushing the bookcase to one side, revealing a secret passage.
* An accompanying sound effect is also triggered on a serial MP3 player.
*/
// INCLUDES
// For debouncing noisy input from the mechanical switches
#include <Bounce2.h>
// CONSTANTS
// Total number of switches that test which books have been pulled
const byte numInputs = 4;
// GPIO pins to which NC terminals of switches are connected (C terminal from each switch goes to GND)
const byte inputPins[numInputs] = {A0, A1, A2, A3};
// Define which combination of books need to be pulled/not pulled to solve the puzzle
// I'm using normally open switches, with pull-up resistors. That means
// HIGH is when the switch is not being pressed (i.e,. the book has been pulled)
// LOW means the book is pressing down on the switch
const bool solutionPattern[numInputs] = {HIGH, LOW, HIGH, HIGH};
// This pin will be driven LOW to release a maglock when puzzle is solved
const byte lockPin = 4;
// GLOBALS
// Assume the default state of each switch is LOW. That is to say that the book is in place,
// and the button is being pressed.
bool lastInputState[numInputs] = {LOW, LOW, LOW, LOW};
// Create an array of Bounce objects
Bounce bookSwitches[numInputs] = {Bounce(), Bounce(), Bounce(), Bounce()};
// Keep track of current state of the controller
enum Mode {Initialising, Locked, Unlocked};
Mode mode = Mode::Initialising;
// This function runs once when the program first starts
void setup() {
// Initialise a serial connection, used only for debugging
Serial.begin(9600);
Serial.println(__FILE__ __DATE__);
// Attach a debouncer to every book switch
for(int i=0; i<numInputs; i++){
bookSwitches[i].attach(inputPins[i], INPUT_PULLUP);
}
// Set the lock pin as output and secure the lock
pinMode(lockPin, OUTPUT);
digitalWrite(lockPin, HIGH);
// Update the state of the controller
mode = Mode::Locked;
}
// This function runs over and over
void loop() {
// Set a flag to test whether all switches are in the correct state.
// We'll start off by assuming this is true
bool puzzleSolved = true;
// Loop through all the inputs
for(int i=0; i<numInputs; i++){
// Update the state of this switch
bookSwitches[i].update();
// And retrieve its current value
int currentInputState = bookSwitches[i].read();
// If the input has changed, log a little debug info
if(currentInputState != lastInputState[i]) {
Serial.print(F("Input #"));
Serial.print(i);
Serial.print(F(" changed to "));
Serial.println(currentInputState);
// Update the stored value
lastInputState[i] = currentInputState;
}
// If any input is not in the same state as defined in the solution pattern..
if(currentInputState != solutionPattern[i]) {
// ... then update the flag to show the puzzle has not been solved
puzzleSolved = false;
}
}
// If the puzzle has just been solved
if(mode == Mode::Locked && puzzleSolved) {
// Send debug info to serial monitor
Serial.println(F("Puzzle Solved"));
// Release the lock
digitalWrite(lockPin, LOW);
// Update the controller status
mode = Mode::Unlocked;
}
// If the puzzle has just been "unsolved"
else if(mode == Mode::Unlocked && !puzzleSolved) {
// Send debug info to serial monitor
Serial.println(F("Puzzle Reset"));
// Secure the lock
digitalWrite(lockPin, HIGH);
// Update the controller status
mode = Mode::Locked;
}
}