Need another set of eyes to solve a couple of puzzle box problems

Hello,

I'm having three issues with a puzzle box I'm making and could really use another set of eyes to help with my stupidity.

My skill level:

  • muddling my way around the code, with likely a lot of inefficiency. I don't know much about manipulating arrays, etc, so rely a lot on if statements. I've tried switch cases, but in a very limited manner.

Background:
This is a reverse geocache puzzle box with a few more things added on. Clues are presented on an OLED screen, along with the distance and clue number. The person is given a card (by someone else) when within 150m of the clue. This card serves two purposes. First, it contains a secret question that cna be revealed by different means. For example, peeling off a sticker on the card, or shining a UV light to reveal the question. The card can also be tapped on the box when you're within 150m of the clue location. This brings up a hangman style game, and the secret question on the card is a hint to solving the word.

The clue is considered solved once the card is tapped, and the hangman game is successfully completed. This state is written to EEPROM, so the clue remains permanently solved (until the EEPROM address is reset to 0). There are 8 clues, 8 locations, 8 cards, and 8 word games. I haven't written this part yet, but the box will open after all the clues are solved... either automatically, or with the help of a magnet and sensor.

Acknolwedgements:
I've adapted a bit of code from an RFID tutorial on sparkfun, and also an old hangman game written for a 16x2 LCD controlled by a POT. Links to these are here:
https://learn.sparkfun.com/tutorials/sparkfun-rfid-starter-kit-hookup-guide

Physical hardware:

  • Adafruit 128x32 I2C OLED
  • two tact switches (with resistors)
  • Sparkfun RFID reader, with header
  • Arduino Nano
  • Grove SIM28 GPS

Problem 1:

I have a function that handles displaying a geo clue as per below. This code is modified to use distance1, which is a manual global variable I set for troubleshooting. The actual clues use "distance", which is calculated by a different function and returned correctly. What I want this bit of code to do is to display the clue, along with the distance, and the clue number. The position of the distance value varies on screen depending on the number of digits. Finally, another function is called when the distance is less than 150 meters, that displays a message that the destination has been reached, and allows the person to tap the card to start the hangman game.

All this works fine. The distance computes, it displays the right text when within 150 meters, allows the RFID card to be tapped, and starts the hangman game. However, there is likely something wrong with my if statements that causes the "you have arrived" function text to flicker and alternate between the clue text and the arrived text. It's displaying both sets of text rapidly switching between the two.

The related functions are as follows:

This is the clue function (note the fake distance used to troubleshoot is a variable called distance1, and set to 0.13):

void current_clue_one() {

  if (current_clue == 1 && clue_one_complete == 0 ) {
    // display first clue if current_clue is set to 1, and the clue hasn't yet been completed

    gps_clue1(); // call the GPS function to measure the distance left to reach the clue location

    
    if (distance >= 0.15) {

      oled.setCursor(0, 0);
      oled.print(F("Where no shrimp drown  "));
      oled.setCursor(0, 1);
      oled.print(F("in the hot sea...      "));
      oled.setCursor(0, 2);
      oled.print(F("                       "));

      if (distance1 > 0.15 < 10) {
        oled.setCursor(0, 3);
        oled.print(F(" "));
        oled.setCursor(5, 3);
        oled.println(distance1);
        oled.setCursor(32, 3);
        oled.print(F("km"));
        oled.setCursor(111, 3);
        oled.print(F("1/8"));

      }
      else if (distance1 >= 10) {
        oled.setCursor(0, 3);
        oled.println(distance1);
        oled.setCursor(32, 3);
        oled.print(F("km"));
        oled.setCursor(111, 3);
        oled.print(F("1/8"));
      }
    }

        if (distance1 <0.15)
   {
        you_have_arrived();
        rfid_Clue1();
   }
  }


  if (current_clue == 1 && clue_one_complete == 1 ) {
    // Displays the completed screen when the clue has been completed and the RFID card has been successfully scanned.

    clue_has_been_completed();
  }
}

This is the "you_have_arrived" function:

void you_have_arrived() {
  oled.setCursor(0, 0);
  oled.print(F("You have arrived!     "));
  oled.setCursor(0, 1);
  oled.print(F("Tap your card...      "));
  oled.setCursor(0, 2);
  oled.println(F("                    "));
  oled.setCursor(0, 3);
  oled.print(F("                      "));
  // delay(2000);
}

The entire code is too big to fit inline, so is available as an attachment.

Problem 2:
I'd like the GPS function to parse the serial output into lat/lon every 30 seconds. Clearly my attempt at doing this below does not work, but why? The parsing is continuous. Does this even matter? Does it slow things down, when compared to taking a reading and parsing every 30 seconds?

Here is my stupid attempt at it:

void gps_clue1()
// This function reads NMEA sentences from the serial instance defined earlier
{

  start = millis();

  if (start - finish >= 1000) {

    while (gpscom.available())     // While there is data on the RX pin...
    {
      int c = gpscom.read();    // load the data into a variable...
      // Serial.write(c);
      if (gps.encode(c))     // if there is a new valid sentence...
      {
        float latitude, longitude;
        gps.f_get_position(&latitude, &longitude);

        //Enter GPS lat/lon values for each clue here
        float f_lat, f_lon;
        f_lat = 1.306282;
        f_lon = 103.832275;
        delay(500);
        distance = DistanceBetween2Points(latitude, longitude, f_lat, f_lon, KILOMETERS_PER_METER ); // call the distance function to figure out how far to the clue
        // Serial.print("Distance: "); Serial.println(distance); // debug in serial monitor if needed

        finish = start;
      }
    }
  }
}

Problem 3:
The hangman game works fine, and when won, allows the clue to complete. However, when the person playing loses, it lets them try again... but remembers the letters picked successfully the last time around, instead of starting a new game with the same word. Need to flush out the past attempt somehow. I haven't looked at this problem in depth yet.

Thank you for reading and for your help. I've tried to follow the forum rules on posting as much as possible, so apologies if I've missed something.

N.

clue_box_with_hangman_experiment_2.ino (45.3 KB)

For my little brain there is far too much stuff in your Original Post. Perhaps you can start with ONE simple question and when that is answered move on to another one?

...R

Robin,

Sure, based on the description of problem 1, why does the function seem to rapidly and alternately display the clue, as well as the "you have arrived" text? I know it's looping through the if statements and probably meets two conditions that are true, and so alternately displays each in quick order.

However, where is this happening? I'm a bit clueless!

N.

Thanks - that does help a bit. But it would take me too long (I'm lazy) to figure out all that code.

What would happen if this line

if (distance1 <0.15)

was changed to else if

And I suspect you could reduce that program to 25% of its present length if you modified your functions to take parameters so that (for example) a single

void lets_play_hangman(in hangValue) {

function that is called like

void loop() {
    lets_play_hangman(play_hangman);


}

and similar changes for other repetitive bits.

IMHO it would also make the program easier to develop and debug.

...R

Robin,

Thank you for replying. I did try adding an else if there last night and it basically skipped past, doing nothing. I'll try it again tonight to confirm.

As for the length, absolutely agree with you. Repeating functions each time for each game, each GPS lookup, each clue is extremely inefficient. I'm still learning my way through all this, and it has only been a couple of months since I first started.

Right now I'm just muddling around trying to absorb whatever I can.

N.

Just tried with

else if (distance < 0.15)... and it ignores the function within. No flickering present either.

I remove the else, leaving

if (distance <0.15)... and it goes back to flickering, but the RFID function is successfully called, and I can tap the right RFID card and it progresses to the hangman game.

N.

nm1213:
Just tried with ...

I don't understand your comment.

It sounds like you don't want it to ignore "the function within", whereas it seems to me that is the whole purpose of the clause?

Perhaps I am not clear about what is intended to happen?

On the question of organizing your program one idea would be to strip the program back to a single version of each function and get that working fully and then grow the program from that using functions that take parameters. Stripping back might make the cause of the current problem easier to see.

...R

Hi Robin,

The function logic has three tasks.

  1. If the distance is more than or equal to 10km, display the distance at the right cursor point on the oled.
  2. If the distance is less than 10km, the shift the display of the distance on screen slightly to accommodate for the lack of two digits before the decimal.
  3. If the distance is less than 150 meters, then consider that the person has reached the clue point and allow for the rfid card to be scanned and display "you have arrived" on screen.

The test case assumes distance is 0.13km as that's manually set for troubleshooting. This means the "you have arrived" screen should trigger and rfid scanning should be enabled.

With a simple if (distance < 0.15), this does happen. However, it also flickers the screen while rapidly alternating "you have arrived" and the clue text, in this case "where no shrimp drown...".

With an else if (distance< 0.15), the flickering does not happen, however nor does the rfid scan work. It's like this condition is not happening, and the else if part skips even though it shouldn't.

N.

It's almost like as the function is written, and the distance is set to 0.13, two of the if conditions are true and start competing with each other for displaying their respective text on screen.

nm1213:
It's almost like as the function is written, and the distance is set to 0.13, two of the if conditions are true and start competing with each other for displaying their respective text on screen.

It's almost as if you've forgotten that we can't see your code.

I've attached the file in the first post... did I do so incorrectly? I also used the code tags to highlight the failing portion.

I meant the corrected code, with all the suggestions made so far incorporated.

(And no, I can't see your code at all, because I'm using my phone)

There were two suggestions made so far. The first was to change a single if statement to an else if statement. Specifically "if (distance1 <0.15)" to "else if (distance1 <0.15)".

The second was to rewrite the code to avoid repetitive functions. Everything currently works, except for the three problems listed in the original post. I consider these minor, and not necessitating an urgent rewrite. Nor can I possibly rewrite everything in the span of a few hours.

However, I did try Robin's suggestion about using an else if instead of an if. The resulting code is listed below. It has unfortunately not fixed the problem.

void current_clue_one() {

  if (current_clue == 1 && clue_one_complete == 0 ) {
    // display first clue if current_clue is set to 1, and the clue hasn't yet been completed

    gps_clue1(); // call the GPS function to measure the distance left to reach the clue location

    
    if (distance >= 0.15) {

      oled.setCursor(0, 0);
      oled.print(F("Where no shrimp drown  "));
      oled.setCursor(0, 1);
      oled.print(F("in the hot sea...      "));
      oled.setCursor(0, 2);
      oled.print(F("                       "));

      if (distance1 > 0.15 < 10) {
        oled.setCursor(0, 3);
        oled.print(F(" "));
        oled.setCursor(5, 3);
        oled.println(distance1);
        oled.setCursor(32, 3);
        oled.print(F("km"));
        oled.setCursor(111, 3);
        oled.print(F("1/8"));

      }
      else if (distance1 >= 10) {
        oled.setCursor(0, 3);
        oled.println(distance1);
        oled.setCursor(32, 3);
        oled.print(F("km"));
        oled.setCursor(111, 3);
        oled.print(F("1/8"));
      }
    }

       else if (distance1 <0.15)
   {
        you_have_arrived();
        rfid_Clue1();
   }
  }


  if (current_clue == 1 && clue_one_complete == 1 ) {
    // Displays the completed screen when the clue has been completed and the RFID card has been successfully scanned.

    clue_has_been_completed();
  }
}
if (distance1 > 0.15 < 10)

What's that?

This is where the distance is greater than 0.15, but less than 10.

I've also tried with if (distance1 > 0.15 && distance1 < 10), but it results in the same behavior.

Thanks again for your help.

N.

Ok, let's explainif (distance1 > 0.15 < 10)
distance > 0.15 will evaluate to one (true) or zero (false).
Both are less than ten.

Thanks again for your patience with me.

There are four conditions I'd like to have...

  1. If the distance is greater or equal to 150 meters, then print the clue, the distance, and "km" after the distance value.

  2. If the distance is greater than 150 meters, but less than 10km, then display the distance, but offset it slightly on screen. Also still display the clue.

  3. If the distance is greater or equal to 10, then again display the distance, with a different offset. Still display the clue.

  4. If the distance is less than 150m, then consider the destination reached, move to another function that displays "you have arrived", and allow the RFID card to be scanned using yet another function. No longer show the clue.

N.

So, assuming the distance is set to 0.13:

  1. if (distance >=0.15) is false
  2. if (distance1 > 0.15 < 10) and if (distance1 > 0.15 && distance1 < 10) should also give a value of false
  3. if (distance1 >= 10) should be false.
  4. if (distance1 < 0.15) should return as TRUE.

Therefore only one of the conditions (4), should run. However, the behaviour observed is the opposite, where two take turns running alternately.

N.

Maybe the problem that is causing the flicker is the absence of a variable to tell it not to. Think about it this way.

Your distance is less than 0.15 so both of these are called

       you_have_arrived();
        rfid_Clue1();

but after they are done I suspect the distance is still less than 0.15 so they get called again.

Because your code is long and repetitive I cannot easily follow through to check that.

If that does not help to solve the problem please write a short "essay" that talks us through everything that happens from the moment loop() is called - maybe something earlier in the process is causing a problem.

...R

Robin,

Thanks again. Will go through this again step by step and try and figure out what is going on. I also thought I'd stop being lazy and use your suggestion to isolate a single part of code to debug. I've done this many times in the past to much effect, however am at fault for being lazy this time around.

I've managed to solve Problem 2, where I wanted to parse an NMEA GPS string every 20 seconds, then pause, take a break and do it again. I had the finish = start; logic in the wrong place. You can see this as I've left it in the code below, while commenting it out.

The right location is uncommented. Either way, thanks for sticking with this and prompting me to stop being lazy.

void gps_clue1()
// This function reads NMEA sentences from the serial instance defined earlier
{

  start = millis();

  if (start - finish >= 20000) {
finish = start

    while (gpscom.available())     // While there is data on the RX pin...
    {
      int c = gpscom.read();    // load the data into a variable...
      // Serial.write(c);
      if (gps.encode(c))     // if there is a new valid sentence...
      {
        float latitude, longitude;
        gps.f_get_position(&latitude, &longitude);

        //Enter GPS lat/lon values for each clue here
        float f_lat, f_lon;
        f_lat = 1.306282;
        f_lon = 103.832275;
        delay(500);
        distance = DistanceBetween2Points(latitude, longitude, f_lat, f_lon, KILOMETERS_PER_METER ); // call the distance function to figure out how far to the clue
        Serial.print("Distance: "); 
        Serial.println(distance); // debug in serial monitor if needed

       finish = start;
      }
    }
  }
}