Changing behaviour of code made for a Geocache

Greetings everyone!

I am very new to the forum - pleased to meet you!
So please excuse my (probably) very stupid question.

I am also new to using Ardsuino's and sketches.

Anyway, here comes the question:
I have been building a Geocache that incorporates a Simon Says game that you need to overcome in order to open the compartment where the log is held in.

I downloaded the sketch I needed from a website that tells you how to build one of these caches.
His cache was made to work with a 9v battery that a cacher needs to bring.
However I decided to install an adapter that provides continues power to the Arduino/cache.

This presents me with a problem as the game will present the player with 4 tones and some flashing lights behind the buttons to initiate play.
Since my version of the cache is connected to continuous power the cache keeps playing those 4 sounds over and over.

This will drive me and the neighbours crazy since the cache is hanging on the side of my garage.

I could install a refridgerator light switch to provide the cache with power as soon as the player opens the case but I was wondering if the issue could be tackled in the code?

Perhaps someone has a nice solution as to how I could solve the issue of the flashing lights and the beeping of the speaker untill the player starts to play.
Maybe just remove that aspect of the game, but I am afraid I am not knowledgeable enough to know what exactly to remove.

Any help would be greatly appreciated - thank you very much for taking the time to read my question and possibly help me solve this issue.

To give you an understanding of the workings of the cache, a Youtube movie about it can be found here: https://www.youtube.com/watch?v=fp8XLRejD4g

The website where the idea was taken from can be found here:

Here's the actual code:

/**
 * GC9GNKE - "Tens boa memoria ?? 
 * Algum codigo foi tirado (exemplos) do tutorial da Sparkfun:
 * 
 */


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


//trocar para 0 para desativar debugging
//trocar para 1 para ativar debugging
#define DEBUG 0

#define BUZZER  12

#define SERVO 3
Servo myservo;

#define soundButtonTop  262
#define soundButtonLeft 330
#define soundButtonBottom 392 
#define soundButtonRight 494

int soundsArray[4] = {soundButtonTop,soundButtonLeft, soundButtonBottom,soundButtonRight};
/*
 * Definitions for buttons and leds
 */
#define ledTop 11
#define ledLeft 10
#define ledBottom 9
#define ledRight 8


int ledsArray[4] = {ledTop, ledLeft, ledBottom, ledRight};

//buttons definitions
#define buttonTop 7
#define buttonLeft 6
#define buttonBottom 5
#define buttonRight 4


#define NO_BUTTON 0

int buttonsArray[4] = {buttonTop, buttonLeft, buttonBottom, buttonRight};

/**
 * Program variables
 */

// Numero de nivels que o utilizador tem que fazer
// para ganhar
// vamos apenas usar 5 dos 6 que temos definidos
#define NUM_LEVELS 5

//tempo (milisegundos - 3 segundos) que o geocacher tem para pressionar o botao
#define BUTTON_TIMEOUT 3000

#define ledReady 13

//current level
int curLevel = 0;

/**
 * definicao de tempos para os LEDs acenderem e desligaram
 * Nivel mais altos, menos tempo para mostrar
 * 2s, 1,5s, 900ms, 700ms, 600ms, 500ms -  
 */
//Para 6 niveis
//int timeLevels[NUM_LEVELS] = {1000, 900, 750, 650, 600, 500};

//Para 5 niveis
int timeLevels[NUM_LEVELS] = {1000, 900, 750, 650, 600};

/**
 * Tamanho das sequencias para memorizar e repetir
 * Mais alto o nivel, maior a sequencia a memorizar
 * Primeiro nivel acende 4 botoes
 * Segundo nivel, acende 5 botoes e assim sucessivamente. 
 * 4, 5, 6, 7, 8, 10
 */
//Para 6 niveis
//int sequences[NUM_LEVELS] = {4, 5, 5, 6, 7, 8};

// Para 5 niveis
int sequences[NUM_LEVELS] = {4, 5, 5, 6, 7};

long debounceDelay = 200;    // tempo para "deixar de saltar". aumentar se botao regista mais que uma vez


void setup() {
  // put your setup code here, to run once:
  //LED PINS
  for (int i = 0; i < 4; i++)
    pinMode (ledsArray[i], OUTPUT);

  //led Ready
  pinMode (ledReady, OUTPUT);

  //Button PINS
  for (int i = 0; i < 4; i++)
    pinMode (buttonsArray[i], INPUT_PULLUP);

  // Servo
  myservo.attach(SERVO);

  //Serial port
  Serial.begin(115200);
  initializeLeds();
  delay(3000);
}

/*
 * Just show off a animation for startup
 */
void initializeLeds() {
  int delay_timer = 100;
  for (int i = 0; i < 4; i++) {
    digitalWrite (ledsArray[i], HIGH);
    delay(delay_timer);
    digitalWrite (ledsArray[i], LOW);
  }
}


void loop() {

  //play something a do something
  //until user presses a key to start the game. 
  flashLevelWaiting();
  delay(2000); //esperar 2s ate comecar o jogo
  
  //when ready, if return 0 is not ok
  //return 1, win and play sound
  if (playMemoryGame()) {
    //play vitory
    if (DEBUG) Serial.println("A tocar vitoria...");
    playVencedor();
    delay(1000);
    //open door
    if (DEBUG) Serial.println ("Opening logbook door");
    myservo.write(170);
    //indicar que ja podem abrir a porta
    showOpenLogBook();
    //wait 5s
    delay(5000);
    //put in normal position
    if (DEBUG) Serial.println ("Closing logbook door");
    myservo.write(90);
    //wait until a button is pressed
    do {} while (!getButton());
    delay(100);
  }
  else {
    //play gameover
    if (DEBUG) Serial.println("Perdeu....");
    playGameOver();
    delay (1000);
  } 
  //reset game levels
  curLevel = 0;
}


boolean playMemoryGame () {
  //lets randomize 
   for (int z = 0; z < NUM_LEVELS; z++) {
    Serial.println("Current Level: " + String(curLevel));
    
    /*
     * create array with current level sequences
     * Just create an array -
     * Sequences is the number of buttons to press from memory
     * curLevel is current level - current level will
     * be the index in the sequences array - the more high in the level
     * the more button presses will have to remember
     */
    int flashArray[sequences[curLevel]];

    /*
    //fill the array with random Leds
    for (int y = 0; y < sequences[curLevel]; y++)
      flashArray[y] = ledsArray[random(0,3)];
    */
  
    randomizeGame (flashArray, sequences[curLevel]);

    //call the function that will light up the leds
    showLevel (flashArray);
    readyLevel(); //mostrar ao jogador que pode pressionar botoes
    if (DEBUG) Serial.println ("Waiting for user");
    
    //now, let's match the pressed buttons with the game
    if (!parseUserLevel(flashArray)) {
      //User missed something, ending game
      if (DEBUG) Serial.println(" Gameover");
      return 0;
    }
    //Estamos aqui, o utilizador acertou todos os botoes
    //esperar 1s ate ao proximo nivel
    delay(1000);
    curLevel += 1;
    if (DEBUG) Serial.println("Next Level: " + String(curLevel));
   }

  //if we got here, winner
   return 1;
}

void randomizeGame (int flashArray[], int arraySize) {
  //make sure this is really random
  randomSeed(analogRead(A0));
  //fill the array with random Leds
  for (int y = 0; y < arraySize; y++)
    flashArray[y] = ledsArray[random(0,4)];
}

/**
 * Function show the random LEDs according with the Arrays
 * playLevel - show what buttons user must press
 * 
 */
void showLevel (int flashArray[]) {
  //lets light up some leds
  for (int x = 0; x < sequences[curLevel]; x ++) {
    Serial.print(String(flashArray[x]) + " ");
    //digitalWrite (flashArray[x], HIGH);
    tonner (flashArray[x]);
    delay(timeLevels[curLevel]); 
    //digitalWrite (flashArray[x], LOW);
    delay(timeLevels[curLevel]);
  }
}

/*
 * Function to get what button was pressed
 */
int getButton () {
  long startTime = millis();
  //get what button was pressed and return it
  // we return the pin number - it's the same on the array
  while ( (millis() - startTime) < BUTTON_TIMEOUT) {
    if (digitalRead(buttonTop) == 0) return (ledTop);
    else if (digitalRead(buttonLeft) == 0) return (ledLeft);
    else if (digitalRead(buttonRight) == 0) return (ledRight);
    else if (digitalRead(buttonBottom) == 0) return (ledBottom);
  }
  return 0;
}

/*
 * parse user input for current level
 * Every button in presses, we will check if is the same on the array
 * If it fails, return 0
 */
int parseUserLevel (int flashArray[]) {
  int numPressButtons = 0;
  
  while (numPressButtons < sequences[curLevel]) {
    long startTime = millis();
    int button = getButton();
    if (DEBUG) Serial.println("Button pressed: " + String(button));
    if (button != NO_BUTTON) {
      //play music
      tonner (button);
      if (DEBUG) Serial.println("Button pressed inside: " + String(button));
      delay (debounceDelay); //debounce to prevent multiple presses
      //check if button pressed is the same as in the array
      if (button == flashArray[numPressButtons]) {
        if (DEBUG) Serial.println ("corret button");
        //next button
        numPressButtons++;
        if (DEBUG) Serial.println("numPress: " + String(numPressButtons));
        setLedsPressed (NO_BUTTON);
        continue; //advance next
      }
      if (DEBUG) Serial.println("Error button");
      return 0;
    } //end if button
    if (DEBUG) Serial.println("TIMEOUT");
    return 0;
  }
  //we got here, user got them right
  return 1;
}

//light up the LED of the button pressed
void setLedsPressed(int buttonPressed) {
  if (buttonPressed ==  ledTop)
    digitalWrite (ledTop, HIGH);
  else 
    digitalWrite (ledTop, LOW);
  
  if (buttonPressed ==  ledLeft)
    digitalWrite (ledLeft, HIGH);
  else 
    digitalWrite (ledLeft, LOW);

  if (buttonPressed ==  ledBottom)
    digitalWrite (ledBottom, HIGH);
  else 
    digitalWrite (ledBottom, LOW);

  if (buttonPressed ==  ledRight)
    digitalWrite (ledRight, HIGH);
  else 
    digitalWrite (ledRight, LOW);
}

void play (long note, int duration = 150) {
  tone (BUZZER,note,duration);
  delay (1+duration);
}


void tonner (int buttonPressed) {
  //light leds
  setLedsPressed (buttonPressed);
  
  switch (buttonPressed) {
    case ledTop:
      play (soundButtonTop);
      break;
    case ledLeft:
      play (soundButtonLeft);
      break;
    case ledBottom:
      play (soundButtonBottom);
      break;
    case ledRight:
      play (soundButtonRight);
      break;    
  }
  setLedsPressed(0);
}

// Mostra um modo descontraido ate que estejam prontos a jogar.
void modoDistrair(void) {
  while(1) {
    setLedsPressed(ledTop);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledLeft);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledBottom);
    delay(10);
    if (getButton() != NO_BUTTON) break;
    setLedsPressed(ledRight);
    delay(10);
    if (getButton() != NO_BUTTON) break;
  }
  setLedsPressed(0); //desliga os LEDs
}

//GameOver
void playGameOver() {
  int pressedButton;
  //turn all the LEDs on
  for (int j = 0; j <= 3; j++) {
    digitalWrite(ledsArray[j], HIGH);
  }
  tone(BUZZER, 130, 250);   //E6
  delay(275);
  tone(BUZZER, 73, 250);   //G6
  delay(275);
  tone(BUZZER, 65, 150);   //E7
  delay(175);
  tone(BUZZER, 98, 500);   //C7
  delay(500);

  delay(200);
  setLedsPressed(0);
}

//
void playVencedor() {

  //turn all the LEDs on
  for (int j = 0; j <= 3; j++) {
    digitalWrite(ledsArray[j], HIGH);
  }

  for (int d = 0; d < 3; d++) {
    //play the 1Up noise
    tone(BUZZER, 1318, 150);   //E6
    delay(175);
    tone(BUZZER, 1567, 150);   //G6
    delay(175);
    tone(BUZZER, 2637, 150);   //E7
    delay(175);
    tone(BUZZER, 2093, 150);   //C7
    delay(175);
    tone(BUZZER, 2349, 150);   //D7
    delay(175);
    tone(BUZZER, 3135, 500);   //G7
    delay(500);
  }

  delay(100);
}

/*
 * Esta funcao vai acender todos os leds
 * para indicar que esta a espera
 */
void flashLevelWaiting () {
  while (1) {
  //flash all of the LEDs when the game starts
    for (int i = 0; i <= 3; i++) {
      tone(BUZZER, soundsArray[i], 200); //play one of the 4 tones
      //turn all of the leds on
      digitalWrite(ledsArray[0], HIGH);
      digitalWrite(ledsArray[1], HIGH);
      digitalWrite(ledsArray[2], HIGH);
      digitalWrite(ledsArray[3], HIGH);
      delay(100);         //wait for a moment
      //turn all of the leds off
      digitalWrite(ledsArray[0], LOW);
      digitalWrite(ledsArray[1], LOW);
      digitalWrite(ledsArray[2], LOW);
      digitalWrite(ledsArray[3], LOW);
      delay(100);   //wait for a moment
    }//this will repeat 4 times
  if (getButton() != NO_BUTTON) return;
  }
}

/*
 * Funcao para mostrar ao utilizador
 * que estamos prontos para aceitar os inputs
 */
void readyLevel () {
  digitalWrite (ledReady, HIGH);
  tone (BUZZER, NOTE_CS6, 150);
  delay(100);
  digitalWrite (ledReady, LOW);
}

/*
 * Funcao que mostra que podemos abrir o logbook
 */
void showOpenLogBook() {
  for (int z=0; z < 10; z++) {
    digitalWrite (ledReady, HIGH);
    tone (BUZZER, NOTE_DS5, 100);
    delay(50);
    digitalWrite (ledReady, LOW);
    delay(50);
  }
}

Off the top of my head, add a bit of code to setup to wait for any of the buttons to be pressed. Once the game is finished or won, enable the watchdog and go into a tight loop to cause a reset. Don't forget to add a bit more code to the very start of setup to disable the watchdog again.

When the cacher finds the cache and presses a button, the game starts up. Once they've won or lost, the watchdog reboots everything to begin again.

Thank you van der Decken - That sounds like agood solution!

I am sorry to have to ask you this - but could you add that to the code? I have no idea where to even start.

Thanks!

There might be an easier solution

After startup, the code is stuck into this function which has an infinite loop

void flashLevelWaiting () {
  while (1) {
  //flash all of the LEDs when the game starts
    for (int i = 0; i <= 3; i++) {
      tone(BUZZER, soundsArray[i], 200); //play one of the 4 tones
      //turn all of the leds on
      digitalWrite(ledsArray[0], HIGH);
      digitalWrite(ledsArray[1], HIGH);
      digitalWrite(ledsArray[2], HIGH);
      digitalWrite(ledsArray[3], HIGH);
      delay(100);         //wait for a moment
      //turn all of the leds off
      digitalWrite(ledsArray[0], LOW);
      digitalWrite(ledsArray[1], LOW);
      digitalWrite(ledsArray[2], LOW);
      digitalWrite(ledsArray[3], LOW);
      delay(100);   //wait for a moment
    }//this will repeat 4 times
  if (getButton() != NO_BUTTON) return;
  }
}

You break out of the infinite loop through the press of a button (poorly coded in my opinion as it's sequential - you get stuck in cycles of 800ms of sound / light and then 3 seconds waiting for a press in the getButton() function )

so you could modify that function so that you actually don't enter the animation / sound until a key is pressed by moving the button checking into an infinite loop at the top.

You could try to change the function into

void flashLevelWaiting () {
  // wait for a button press
  while (getButton() == NO_BUTTON) yield();

  //flash all of the LEDs when the game starts
  for (int i = 0; i <= 3; i++) {
    tone(BUZZER, soundsArray[i], 200); //play one of the 4 tones
    //turn all of the leds on
    digitalWrite(ledsArray[0], HIGH);
    digitalWrite(ledsArray[1], HIGH);
    digitalWrite(ledsArray[2], HIGH);
    digitalWrite(ledsArray[3], HIGH);
    delay(100);         //wait for a moment
    //turn all of the leds off
    digitalWrite(ledsArray[0], LOW);
    digitalWrite(ledsArray[1], LOW);
    digitalWrite(ledsArray[2], LOW);
    digitalWrite(ledsArray[3], LOW);
    delay(100);   //wait for a moment
  }//this will repeat 4 times
}

Thanks Jackson!

So if I change that bit to your code the cache wille not give me that start music/lights flashing until someone presses a button first?

That would be a very nice solution!

If someone puts in a wrong sequence the game will also revert to that starting state where it flashes the lights and plays the sound - will this still work?

Thanks for your help so far, I appreciate it!

best way is to try as I did not read the whole code :slight_smile:

As we make this the first step in the loop, my guess is that what will happen is that after the game resets you'll have to press again a key to restart

Hi Jackson,

Thanks, let me load the code into the cache and I will get back to you!

Thanks again for the help :slight_smile:

I think it's fine to have a key to press anyway to restart otherwise if we add something to remember the game started then after one try you will end up again with the infinite sound and light show that bother you and your neighbours (with more code we could add a timeout — if no one touched a button for x seconds then the game ends and goes back to waiting for an press - but that's may be over-engineering it).

Hi Jackson,

That would be the preferred method though since player have to push a button to get the game out of the infinite light/sound show.

They will now have to push 2 buttons to get it to work, which feels less intuitive to be honest.
So if we could add the time-out feature that would be amazing.

Thanks! :slight_smile:

They would have to push a button again only if they fail - that is ait brings them back to exactly the same point as when they found the box - so there is some consistency.

Can you confirm though that it does work with this modification?

I’m typing out of my iPhone and adding the timeout would require to have a look at all the code - can’t do that right now as I’m away from home. May be another helper can have a look.

You would need to define how long to wait before triggering this timeout. Having an extra led to show the system status would probably be nice too.

Hello M0rlarty,

J-M-L's name isn't Jackson (as far as I know).

Jackson is a badge that J-M-L has obtained for making over 25000 posts.
You can learn about all the forum badges here.

Oops, please excuse the fumble up!

J-M-L - Thanks for your help!!! :slight_smile:

Ok I uploaded the sketch and it works really well!!

Only 1 thing that we need to solve now - after winning the game all the lights in the buttons stay on. I think we need to revert to the start of the flasLevelWaiting part of the code to get it all working like it should?

Is that something you could code in for me J-M-L?
I think this bit of code should be in the playVencedor bit of the code (playVictory) but I am not sure though!

Thank you so much for your help so far!

In the setup() function you have this call

Setup is called only once so that’s why LEDs are not reset

Remove it from there and add it at the start of the flashLevelWaiting() function before the while loop.

void flashLevelWaiting () {
  // prepare game 
  initializeLeds();

  // wait for a button press
  while (getButton() == NO_BUTTON) yield();

…

That might do what you want

Other options would be indeed to add the call after the play victory (but also probably when you loose so probably better to have it in a common entry point at the start of the game )

Thanks again J-M-L!

I'll upload the sketch and will let you know if this solves the issue :slight_smile:
I appreciate your help in this so much!


Uploaded the sketch and the LED's in the buttons still stay lit after a win.
So sadly this hasn't solved it yet! :frowning:

I’ll be back to my Mac in a few days - I’ll have a look at the code unless someone gets a chance to dig in before

Try putting initializeLeds() at the end of playGameOver()

//GameOver
void playGameOver() {
  int pressedButton;
  //turn all the LEDs on
  for (int j = 0; j <= 3; j++) {
    digitalWrite(ledsArray[j], HIGH);
  }
  tone(BUZZER, 130, 250);   //E6
  delay(275);
  tone(BUZZER, 73, 250);   //G6
  delay(275);
  tone(BUZZER, 65, 150);   //E7
  delay(175);
  tone(BUZZER, 98, 500);   //C7
  delay(500);

  delay(200);
  setLedsPressed(0);
  initializeLeds(); // <- put "LED" function here
}

Thank you xfpd, I will try that :slight_smile:

I'll upload the sketch later today and see if that has the desired effect!

I tried to put the code there but the lights still remain on after a win.

I made a Youtube video of it which can be watched here:

Post the code.