Trying to make a whack-a-mole like game, gets out of sync after a while

So I'm in the middle of making a whack-a-mole game, where the heads popping up are LEDs turning on and instead of a hammer there is a button next to each LED. I am testing it through the serial monitor for easy development rignt now and it seems to work fine, but after a while it does some strange things. It stops randomly, the delay it is doing doesn't correspond with the delay that was chosen and printed,...

this is my code:

int leds[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

int button12 = A1;
int button12State;
int prevButton12State;

int randLed; int randDuration; 
int randInterval; 
int currentTargetLed;

String income;
bool pressed;

unsigned long startMillis;
unsigned long ledStartMillis;

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

  for(int i = 0; i < 10; i++) {
    pinMode(leds[i], OUTPUT);
  }

  Serial.println("RESET");
  randomSeed(analogRead(A0));
  startMillis = millis();
}

void loop() {
  // button12State = analogRead(button12);
  // if(button12State > 0) {
  //   if(button12State < 10) {
  //     Serial.println("button 2 is pressed");
  //   }
  //   else if(20 < button12State < 30) {
  //     Serial.println("button 1 is pressed");
  //   }
  // }

  manageLeds();
}

void manageLeds() {
  randInterval = random(1000, 5000);
  Serial.println("Waiting " + String(randInterval) + " ms");

  startMillis = millis();
  while((millis() - startMillis) != randInterval) {
    // wait for rand interval, don't do anything in the meantime (BUT YOU COULD!)
  }
  raiseLed();
}

void raiseLed() {
  randLed = random(10);
  randDuration = random(1000, 5000);
  // turn on randomly selected led
  digitalWrite(leds[randLed], 1);
  Serial.println("turned on led " + String(randLed));
  currentTargetLed = randLed;
  pressed = false;

  // while the les is on for the random duration, check if the button next to it is pressed
  ledStartMillis = millis();
  while((millis() - ledStartMillis) != randDuration) {
    // test without buttons
    if(Serial.available() > 0) {
      income = Serial.readString();
      if(income == String(currentTargetLed)) {
        Serial.println("WIN");
        digitalWrite(leds[randLed], 0);
        Serial.println("turned off led " + String(randLed));
        pressed = true;
        // don't continue with the set duration when right button pressed
        break;
      }
    }

    // test with buttons


  }

  // print lose message when failed
  if(!pressed) {
    Serial.println("LOSE");
    digitalWrite(leds[randLed], 0);
    Serial.println("turned off led " + String(randLed) + " after " + String(randDuration) + " ms");
  }
}

some of it isn't used at the moment due to testing through the Serial monitor.

Anyone see what could be causing my problems?

A classic case of what can go wrong if you use a String variable.
As there is no garbage collection.
Why use a String here anyway?

I copied this from a previous project, as it doesn't matter what that is because i will remove serial input as soon as i get the logic done.

I tried to play your game, looks like your code isn't yet quite ready.

This I saw whilst seeing how close to playable it is:

if (20 < button12State < 30) {

which is commented out. But isn't how you write that, so please look at

if (( button12State > 20) &&  (button12State < 30)) {

which is one way to do that kind of testing for range. One way that will actually work.

&& is the "and" operator and is true only when both operands are true.

HTH

Please keep posting to get this working so I can play with it. :wink:

a7

And learn to get along without capital S strings.

    Serial.println("turned off led " + String(randLed) + " after " + String(randDuration) + " ms");

is better if you just take the trouble to

    Serial.print("turned off led ");
    Serial.print(randLed);
    Serial.print( " after ");
    Serial.print(randDuration);
    Serial.println(" ms");

All your use of Strings could easily be eliminated.

a7

Here is the updated code:

int leds[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
int buzzer = 12;
int button12 = A1;
int button12State;
int prevButton12State;

int score = 0;

int randLed; int randDuration; 
int randInterval; 
int currentTargetLed;

int income;
bool pressed;

unsigned long startMillis;
unsigned long ledStartMillis;

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

  for(int i = 0; i < 10; i++) {
    pinMode(leds[i], OUTPUT);
  }

  Serial.println("RESET");
  randomSeed(analogRead(A0));
  startMillis = millis();
}

void loop() {
  // button12State = analogRead(button12);
  // if(button12State > 0) {
  //   if(button12State < 10) {
  //     Serial.println("button 2 is pressed");
  //   }
  //   else if(button12State > 20 && button12State < 30) {
  //     Serial.println("button 1 is pressed");
  //   }
  // }

  game();
}

void game() {
  randInterval = random(1000, 5000);
  Serial.print("Waiting ");
  Serial.print(randInterval);
  Serial.println(" ms");

  startMillis = millis();
  while((millis() - startMillis) != randInterval) {
    // wait for rand interval, don't do anything in the meantime (BUT YOU COULD!)
  }
  raiseLed();
}

void raiseLed() {
  randLed = random(10);
  randDuration = random(1000, 5000);
  // turn on randomly selected led
  digitalWrite(leds[randLed], 1);
  Serial.print("turned on led ");
  Serial.println(randLed);
  currentTargetLed = randLed;
  pressed = false;

  // while the les is on for the random duration, check if the button next to it is pressed
  ledStartMillis = millis();
  while((millis() - ledStartMillis) != randDuration) {
    // test without buttons
    if(Serial.available() > 0) {
      income = Serial.read() - '0';
      if(income == currentTargetLed) {
        Serial.println("WIN");
        digitalWrite(leds[randLed], 0);
        Serial.print("turned off led ");
        Serial.println(randLed);
        pressed = true;
        // don't continue with the set duration when right button pressed
        break;
      }
    }

    // test with buttons
    // checkForButton();

  }

  // print lose message when failed
  if(!pressed) {
    Serial.println("LOSE");
    digitalWrite(leds[randLed], 0);
    Serial.print("turned off led ");
    Serial.print(randLed);
    Serial.print( " after ");
    Serial.print(randDuration);
    Serial.println(" ms");
  }
}

int tempo = 180;
int quarterLength = 1000 / (tempo / 60);
void playWinSound(int buzzer) {
  playHertzforLength(buzzer, 392, quarterLength / 2);
  playHertzforLength(buzzer, 493.88	, quarterLength / 2);
  playHertzforLength(buzzer, 587.33	, quarterLength / 2);
  playHertzforLength(buzzer, 783.99		, quarterLength);
  playHertzforLength(buzzer, 587.33	, quarterLength / 2);
  playHertzforLength(buzzer, 783.99		, quarterLength);
}

float ms; int noteStart;
void playHertzforLength(int buzzer, float hertz, int noteLength) {
  ms = 1000000/hertz;
  noteStart = millis();
  while((millis() - noteStart) < noteLength) {
    digitalWrite(buzzer, 1);
    delayMicroseconds(ms/2);
    digitalWrite(buzzer, 0);
    delayMicroseconds(ms/2);
  }
}

I implemented the suggestions you all gave, and which i'm grateful for. However, the problem still arised.

I eliminated every use of String :slight_smile:

The code regarding the buttons is still commented as i don't have my hardware with me right now and am just testing the logic via Serial.
Also in the meantime i'm working on a sound effect when the game is won, eventually.

millis() sometimes skips a count so "<" would be better than "!=" in this test. If millis() skips the value where it would exactly match, the match will never be true.

If you don't need to do anything else in this wait loop you could replace it all with "delay(randInterval);"

1 Like

This would block any button press.

This fixed the stopping of the program, thank you for that.

The delays getting mixed up is sadly still there, but i thank you all for the help.

Yes, a friend pointed that out, sry I tots missed that when I had time earlier. I could say I really didn't have (or take) the time.

I post only to say that is something (testing logic separate from getting buttons to work) I truly wish more ppl would catch on to.

To fully go with that, one might get the buttons working without the game logic. Probably not terribly important here, just sayin'.

divide et impera!

I eliminated every use of String

That's what I m talking aabout, nice.

a7

I hear it! But.

wokwi has an active buzzer, so I used tone().

void playHertzforLength(int buzzer, float hertz, int noteLength)
{
  int noteStart;

  noteStart = millis();
  tone(buzzer, (int) hertz, noteLength);      // dosen't block!

  while (millis() - noteStart < noteLength);  // so blocok it
}

Are there extant issues or just new features to come?

a7

I know of the tone function, but the teacher doesn't let us use it do i wrote a function that does the same thing.

I'm looking to make some more sound effects when scoring a point or being too late. For anything else i won't have enough pins.

I don't understand that. You only need 1 pin to make as many different noises as you have room for in your Arduino.

Space, time and I/O pins, the box we work within.

I use neopixels. one pin for as many LEDs as you want.

Your 10 switches could be on 7 pins in a 3 x 4 matrix = oh wait! you haven't added 10 buttons yet, how's that gonna be done in your current plan?

And lastly, once you are a degree or two above noob (you are!) there is non shame in using a board with more I/O.

Ask teach about that. :expressionless:

Nice work. I have a little FSM I will place in the wokwi right where you wrote

    // wait for rand interval, don't do anything in the meantime (BUT YOU COULD!)

beacuse when you could, you should. Do something. There is no meantime, only wasted time!

But not ready yet.

Please continue to update your progress, or at least come by to post your final A+ code.

a7

I've had some time due to my beach buddy, she who must not be kept waiting but somehow thinks it's OK for me to, and noticed through comprehensive testing (!) your use of 16 bit integers in places where that type is not appropriate.

I wondered why my playHertzforLength() was acting weird, traced it to something I did not notice

  int noteStart;

when used with millis(), variables should be of type unsigned long.

millis() returns an unsigned long and can time absolute durations up to 49.7 days.

16 bit int type variables can only time 32.768 seconds, or 65.536 seconds if you declared them unsigned int, so it is now wonder that 30 seconds or so into game play some strange things start to happen.

You will hear them if you test before you change the types of the variables that need to be. Changed.

Also, I note that you allow the player to stab away at the buttons until she gets it right. I would take a press of the wrong button and immediately declare a losing move. But I am cruel, maybe.

a7

I'm going to use the analog input pins to detect two buttons using only one pin, by connecting the different buttons through a different resistor, so there will be different voltages detected by the analog pin.

ezgif.com-gif-maker

Here's a GIF of the Serial output where the problem is very clear.

After "turned ff led n after o ms", there should be no delay.
Then, after "waiting n ms", there should be that many ms delay. and finally, th delay after turning on a led before losing is stated in the next "turned off led".

This is not what happens after running the program for a while.
There is delay after "turned off led", and there is way to little delay after turning it on.

Post your new code, or if it is the last code you posted, reread and heed the advice in #16 above, where @alto777 said

it is now wonder that 30 seconds or so into game play some strange things start to happen.

a7

I did that, changed int to unsigned long. For the rest, nothing has changed.