Whack a Mole Game

2019-game-box.pdf (31.4 KB)
Hello. I need help with the code. in this game you always have to press the button that lights up and then you get a point, but i want a point to be deducted if you press the wrong button.



#include <stdio.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
//#include "Parola_Fonts_data.h"
#define HARDWARE_TYPE MD_MAX72XX::DR1CR0RR1_HW     // PAROLA_HW   ICSTATION_HW   GENERIC_HW  FC16_HW
#define MAX_DEVICES 8
#define CLK_PIN   13
#define DATA_PIN  11
#define CS_PIN    10
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);



long randNumber;
int step_counter = 0;
int button_values[] = {913, 429, 267, 179, 110};
int btn_tol = 20;
int analogValue = 0;
int pin_p1 = A0;
int pin_p2 = A6;
int leds_cnt = 5;
int p1_leds[5] = {2,3,4,5,6};
int p2_leds[5] = {A2,A3,A4,A5,A1};
int p1_score = 0;
int p2_score = 0;
int action_speed = 2000;
int action_speed_min = 250;

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

  randomSeed(analogRead(A7));
  
  pinMode(pin_p1, INPUT);
  pinMode(pin_p2, INPUT);

  for (int i = 0; leds_cnt > i; i++) {
    pinMode(p1_leds[i], OUTPUT);
  }
  for (int i = 0; leds_cnt > i; i++) {
    pinMode(p2_leds[i], OUTPUT);
  }  

  P.begin(2);
  P.setZone(0,0,3);
  P.setZone(1,4,7);
  P.displayZoneText(0, "abc", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
  P.setZoneEffect(0, true, PA_FLIP_LR);
  P.displayZoneText(1, "abcd", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
  P.setZoneEffect(1, true, PA_FLIP_UD);
  P.displayAnimate();

}

void loop()
{
  if(p1_score < 100 and p2_score < 100) {
    
    step_counter++;
    bool step_action = false;
    if (step_counter > action_speed) {
      step_counter = 0;
      step_action = true;  
      action_speed = action_speed - round(action_speed/50);
      if (action_speed < action_speed_min) {
        action_speed = action_speed_min;
      }
      Serial.println(action_speed);
    }
  
    if (step_action) {
      int pin_light = random(0,5);
      digitalWrite(p1_leds[pin_light], HIGH);
      digitalWrite(p2_leds[pin_light], HIGH);
      
    }
  
    analogValue = analogRead(pin_p1);
    for (int i = 0; leds_cnt > i; i++) {
      if ( analogValue < button_values[i] + btn_tol and analogValue > button_values[i] - btn_tol ){
        if(digitalRead(p1_leds[i]) == HIGH){
          digitalWrite(p1_leds[i], LOW);
          p1_score++;
        }
      }
    }
  
    analogValue = analogRead(pin_p2);
    for (int i = 0; leds_cnt > i; i++) {
      if ( analogValue < button_values[i] + btn_tol and analogValue > button_values[i] - btn_tol ){
        if(digitalRead(p2_leds[i]) == HIGH){
          digitalWrite(p2_leds[i], LOW);
          p2_score++;
        }
      }
    }
  
    if ( step_counter % 100 == 0){
      char Score1[80];
      sprintf(Score1, "%d", p1_score);
      char *chrdisp[] = {Score1};
  
      char Score2[80];
      sprintf(Score2, "%d", p2_score);
      char *chrdisp2[] = {Score2};
  
      P.displayZoneText(0, chrdisp[0], PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
      P.displayZoneText(1, chrdisp2[0], PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
      P.displayAnimate();
    }
  } else {
    if (p1_score > p2_score) {
      P.displayZoneText(0, "Winner", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
      P.displayZoneText(1, "Looser", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
    } else {
      P.displayZoneText(0, "Looser", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
      P.displayZoneText(1, "Winner", PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
    }
    P.displayAnimate();  
    delay(500);
      char Score1[80];
      sprintf(Score1, "%d", p1_score);
      char *chrdisp[] = {Score1};
  
      char Score2[80];
      sprintf(Score2, "%d", p2_score);
      char *chrdisp2[] = {Score2};
    P.displayZoneText(0, chrdisp[0], PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(1, chrdisp2[0], PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);    
    P.displayAnimate();
    delay(500);        
  }
}

In the code for both player 1 and player 2, that's the part that says

 if the LED is on
   turn it off and add to the player's score

If at that exact point the LED corresponding to the pressed button is not on, it means the player hit a button whose LED was already off.

That would be the place to subtract from the player's score. In both the nearly identical code sections for the two players.

Whatcha gonna do about my sister, who would quickly end up in negative territory for points? :expressionless:

a7

Yes, I also think that this would be the right povition for the code of the point deduction, but how do I do that,
do you have any idea? The player with the fastest 100 points wins. I've already built the game and it works perfectly.
it's just that my kids just push the buttons whether they light up or not and that's why i wanted to deduct a point if they got it wrong.

Only one I cannot test from where I am now.

Try

        if (digitalRead(p1_leds[i]) == HIGH){
          digitalWrite(p1_leds[i], LOW);
          p1_score++;
        }
        else p1_score--;

It occurs to me that you could also (or instead) add to the score of the other player…

a7

1 Like

if i do it with else p1_score--; at the position do the score then goes infinitely into minus when I press the button and if I p2_score++; sometimes the opponent's score is infinitely plus, but only one point should be added or removed for each keystroke

code looks unnecessarily complicated ... shouldn't it just read a button and update the score if the correct button is pressed and then change the LED or timeout

does a button press need to be detected, recognize a change in state and that the button is being pressed to ignore multiple detections?

The way the buttons are done makes it hsrd to run the code; code like this is difficult to analyse just sitting on the beach. Normally we would run the code...

I agree here. A big step forward would be to handle all the buttons once per loop, in a section of code all for just that.

So that denouncing and state change detecting (seeing when a button goes down rather than that it is down) could be applied outside the context of how those button activities were being used by the game logic.

@bberni73 if you could describe what game play looks like that would help dry analysis.

And as a quick and dirty experiment, place a short delay() in the code two places and report - just make you button stabs shorter, and see if the logic at least suports my theory.

        if(digitalRead(p1_leds[i]) == HIGH){
          digitalWrite(p1_leds[i], LOW);
          p1_score++;
        }
        else p1_score--;

        delay(200);   // 0.2 second - keep button presses shorter, use the smallest value you can here for now

Of course it might wreck the feel of the game. We fix that later.

I'm not sure why my idea didn't simply work - at a glance and without developed a headaches, if it increments satisfactorily, how can it decrement rapidly?

I assume you managed to get the change into the correct place. If you would post the new code we can move to looking at that now.

TIA and HTH

a7

this is the original game

looks like the buttons are daisy chained together with resistors and each button corresponds to a specific voltage.

the above code checks if the corresponding led is presumably "On" (HIGH?), turns it Off and increments the score.

the else case could decrements the score

i would have also though pressing a button would also advance to the next LED, but it looks like that is based on action speed for both players

It's whack-a-mole! Watch the video for ten seconds if you've never played.

Two players, two strings of analog voltage divider buttons &c.

Afree. If pressing the button did not work to whack a mole, it must have been a press on a mile hole, so to speak, and would be the logical place to punich the player.

Trying to think how to rig a test I can run. Without spending the entire evening on it.

I'd've put this on something with enough real I/O to do without the analog stuff.

And leaving the analog window comparison to be done in the midst of the game logic isn't help.ing.

a7

mit Verzögerung funktioniert es, aber es blockiert die ganzen Eingaben

with delay it works, but it blocks all inputs

Yes, as I mentioned, the delay will ruin the flow.

There are no calls to millis() and the only ones to delay() do not happen until the game is over.
All timing is therefor dependent on exactly how long the processor is taking to do all the stuff in each loop of loop(). I think.

I don't have printable words for commenting on the quality of this code.

I can't take the time to lash up (or work around) the way the input is done.

Try @gcjr's suggestion to debounce and state-change-detect the buttons. Then adding the decrement where it is should work.

But srsly, great hardware (except the analog buttons). Use it as the designers intended, or start your journey learning how to code and write this yourself. From scratch.

I give up.

a7

That's a bad attitude. How do you ever get anything done?

This simulation


Wokwi_badge Fake Analog Buttons to Digital


shows how to get all the button inputs at once, and stash data that can be used to see what (if any) buttons became pressed since the last trip through the *loop*().

It denounces and detects state changes, and it goes all the way to being over done in an effort to ensure good and fair game play. Try it out in the sim I linked. Try it out with your real analog voltage divider button hack.

// https://wokwi.com/projects/361477000001335297
// https://forum.arduino.cc/t/whack-a-mole-game/1112219

// odd values. nmust be the circuit or something
// const int button_values[] = {913, 429, 267, 179, 110};
// wokwi test values

const int button_values[] = {850, 680, 510, 340, 170,};

// for the analog regions corresponding to buttons
const unsigned char inputPin[2] = {A0, A1};

const int btn_tol = 20;

// stores state and time for debouncing
// and sets pressed for any buttons that get pressed.
unsigned char lastButton[2][5];
unsigned long lastButtonTime[2][5];
unsigned char pressed[2][5];

unsigned long now; // everyone's time for the whole loop

void setup()
{
  Serial.begin(115200);
  xprintf("\nhello world.\n"); 
}

void loop()
{
  now = millis();
  scanButtons();
  reportButtons();
}

// look at all the buttons, load the pressed array
void scanButtons()
{
  unsigned long now = millis();

  for (unsigned char player = 0; player < 2; player++) {
    int analogValue = analogRead(inputPin[player]);
    for (int ii = 0; ii < 5; ii++) {
      pressed[player][ii] = 0;    // false. not pressed - or is it? let us see:

      if (now - lastButtonTime[player][ii] < 15)  // too soon to look at this button again?
        continue;

      unsigned char button = (analogValue < button_values[ii] + btn_tol) && (analogValue > button_values[ii] - btn_tol);

      if (button != lastButton[player][ii]) {
        pressed[player][ii] = button;
        lastButton[player][ii] = button;
        lastButtonTime[player][ii] = now;
      }
    }
  }
}

// show any pressed buttons
void reportButtons()
{
  for (unsigned char player = 0; player < 2; player++) {
    for (int ii = 0; ii < 5; ii++) {
      if (pressed[player][ii]) {
        xprintf("player %d detect hit on button %d\n", player, ii);
      }
    }
  }
}

// programmer misses printf...
void xprintf(const char *format, ...)
{
  char buffer[256];
  va_list args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end(args);
  Serial.print(buffer);
}

This could be a retrofit to you original code. Having spent some time with that code, I can say that it is fragile by not-design, that is to say by tinkering with it you risk changing the feel of the game...

But here

        if (digitalRead(p1_leds[i]) == HIGH){

is exactly where you might instead write

        if  (gotPressed(0, i)){

where 0 is player 1, and i is the LED/button you are checking.

Then the else mod for incrementing the opponent's score or decrementing the player's score will work.

a7

Thank you for taking so much time for my problem. although the game itself works and it would only be an improvement if the point was deducted.

the simulation works and i get a display for each key in the serial monitor. But I think that I can't rewrite the code like this because I have my difficulties with programming :slight_smile: I can do small things, but if it gets too complex...
But I will not give up in any case to get the problem under control.

Did you have to use your resistor values? I assume you mean you were able to run my code on your device.

Did you measure, in an earlier phase, the analog voltage divider to determine the "magic numbers" corresponding to pressing each button?

const int button_values[] = {913, 429, 267, 179, 110};

The circuit offered by the original designer could be modified slightly so the numbers might be more evenly spread out.

Unimportant if you find that the buttons function consistently.

If you build the game in the wokwi simulator using slide faders instead of resistors, I will attempt to insert the improved code into it.

wokwi is free, you don't need to register but thattoo is free and you get a few more benefits.

Offer good until this thread closes. No problem if you don't want to, just don't. If you do, come back and say so, post a link to it and use the @ character and my name like @alto777 which will notify me that you have.

If someone else builds it for us I woukdn't complain. I'm just out of whac-a-mole energy. :expressionless:

a7

yes i have this const int button_values[] = {913, 429, 267, 179, 110}; used because the others didn't work.
Thanks for the link to Wokwi. this is a very interesting site.
I will try to build the hardware there and would
and then I would report back. Thank you for your time
​Details ansehen

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.