Help with code (newby)

I am trying to code (Newbie) a 501 darts game scoreboard all works so far except when i press the reset button it goes to player 1 displays ok then player 2 is missing, everything is ok after that, any help is appreciated.

thanks in advance
Brian

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

// Initialize the LCD (I2C address: 0x27, size: 20x4)
LiquidCrystal_I2C lcd(0x3F, 20, 4);

// Keypad configuration
const byte ROWS = 4;  // Four rows
const byte COLS = 4;  // Four columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {7, 6, 5, 4};  // Connect keypad ROW0, ROW1, ROW2, ROW3 to these pins
byte colPins[COLS] = {3, 2, 1, 0};  // Connect keypad COL0, COL1, COL2, COL3 to these pins
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// Game variables
int numPlayers = 1;           // Number of players
int scores[4] = {501, 501, 501, 501}; // Scores for up to 4 players
int currentPlayer = 0;        // Current player's turn

void setup() {
  lcd.init();        // Initialize the LCD
  lcd.backlight();   // Turn on the backlight
  lcd.clear();
  selectPlayers();
}

void loop() {
  playGame();
}

void selectPlayers() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Select Players:");
  lcd.setCursor(0, 1);
  lcd.print("1-4: Press Key");

  char key;
  while (true) {
    key = keypad.getKey();
    if (key >= '1' && key <= '4') {
      numPlayers = key - '0';  // Convert char to int
      break;
    }
  }

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Players Selected:");
  lcd.setCursor(0, 1);
  lcd.print(numPlayers);
  delay(2000);
  lcd.clear();
}

void playGame() {
  showScores();
  char key;
  lcd.setCursor(0, 0);
  lcd.print("P ");
  lcd.print(currentPlayer + 1);
  lcd.print("'s Turn");
  lcd.setCursor(0, 1);
  lcd.print("Score: ");
 ;

  int score = 0;
  while (true) {
    key = keypad.getKey();
    if (key >= '0' && key <= '9') {
      score = score * 10 + (key - '0');  // Build the score from multiple digits
      lcd.setCursor(7, 1);
      lcd.print(score);
    } else if (key == '#') {  // Confirm the score with '#'
      scores[currentPlayer] -= score;
      break;
    } else if (key == '*') {  // Clear the entered score with '*'
      score = 0;
      lcd.setCursor(7, 1);
      lcd.print("    ");  // Clear display area
      lcd.setCursor(7, 1);
    } else if (key =='D'){
      resetGame();
    }
  }

  // Display updated scores
  showScores();

  // Move to the next player
  currentPlayer = (currentPlayer + 1) % numPlayers;
}

void showScores() {
  lcd.clear();
  lcd.setCursor(0, 0);
  //lcd.print("Scores:");
  for (int i = 0; i < numPlayers; i++) {
    lcd.setCursor(11, i );
    lcd.print("P");
    lcd.print(i + 1);
    lcd.print(": ");
    lcd.print(scores[i]);
  }

}


void resetGame() {
  for (int i = 0; i < 4; i++) {
    scores[i] = 501; // Reset scores
  }
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Game Reset!!");
  delay(2500);
  lcd.clear();
  currentPlayer = 0;
  playGame();
  showScores();  
}

Welcome to the forum

As your topic does not relate directly to the installation or operation of the IDE it has been moved to the Programming Questions category of the forum

Your program is structured in such a way that function 1 calls function 2 that calls function3 that calls function1.
A recursive pattern.
This will not end well. The stack will be loaded until it overflows.
What you need is a state machine.
Sounds difficult, but is not that difficult.
Good tutorials on how to do that are available on this forum.

1 Like

Do not use pins 0 or 1 on the Arduino Uno/Nano. They are reserved for Serial communication.

Comment-out these two lines... see if that helps.

1 Like

moved the pins and commented out the lines but just the same thanks anyway

Is "reset" the button on the MCU module or a keypad key? What do "D" "*" and "#" do?

Sorry, I do not see obvious problems. So let's start trouble shooting...

Put Serial.println(currentPlayer); before and after this line. See what you get...

After you make changes, do you upload the new code?

Solved:
went down the State machine route much simpler

Thank you all anyway

1 Like

Great! That is the best option anyway!
Will you post your new code?
For other users with similar troubles to learn?

I agree with @build_1971 (although post #3 explains it better than I could have).
(To wit: here's my pedestrian explanation):
:notes: State machines, heck yeah. Comin' again to save the mothalovin' daa-ay! :musical_note:

Point is, I have found state machines to be maybe the most useful concept for everything I build, which is almost all interactive props and games for user experiences like Hallowe'en. State machines allow you to really snap between game events. You just have to really break down those events into levels that exist independently and also remembering the transitions in between.

Structuring your code using switch/case/default is a good way to achieve a state machine. For me anyway, it's a lot easier to keep track of and debug than as ton of if/else conditionals.

Your code looks to be pretty well thought out and it's not too long. Since you're a self described newbie, I highly recommend copying your existing code and pasting it into a new sketch, and starting over, restructuring it as a state machine, especially if making games or things related to games is your jam. It really helps me to make a mental model any time I think about a project now in terms of how I might structure it in code.

I like to use one global byte variable to drive everything.

The things that happen all the time in your code are outside of the state machine. So things like maybe a reset button you want to be able to use to reset the game at any time, you would constantly poll for right at the top of void loop().
So you have this:

// Game variables
int numPlayers = 1;           // Number of players
int scores[4] = {501, 501, 501, 501}; // Scores for up to 4 players
int currentPlayer = 0;        // Current player's turn

now add this to your globals

byte gameState = 0;

I use byte because it's plenty (0-255) and it seems to work well when I use some other game input (say, over Serial) of type char. I think it's just because they're both one byte wide and if I'm brutalizing the concept, no doubt one of my forum betters will correct me.

I typically reserve gameState = 0; to match my default, power on game state and simply increment that variable by one for every game level and transition, resetting back to zero when the reset button is pressed.

Here's an example of what I mean. I thought it more clear to actually name the variable gameState as level to drive home the point a bit. It's a very boring "game", you do nothing but watch it report what level it's on after every two seconds has passed, then reset.

/* April 03, 2024
   revised December 11, 2024 to make the concept a bit more obvious
    Hallowed31

    State/Mode Machine - events/functions driven by built in 
    millis timer and nothing else
    It's like a game, only much less fun. Just watch the game play itself!

    Circuit: just an Arduino

    Set Serial monitor to 9600 baud, no line ending
    
    Working as intended. Tested on Uno R3
*/

byte level = 0;  // you could have 0 - 255 levels if you wanted.
unsigned long currentTime;
unsigned long lastTimeAround;

void setup() {
  Serial.begin(9600);
  // just the sketch name so I know what's loaded on my Arduino
  Serial.println(F("automaticByteDrivenStateMachine"));
  delay(500);
  Serial.println();
  level = 0;
  currentTime = 0;
  lastTimeAround = 0;
}

void loop() {
  // start free running timer, this never stops running in the "game"
  currentTime = millis();
  /* set conditional statement whose condition is to 
     check built in millis clock and announce what "game"
    level it's on every two seconds */
  if (currentTime - lastTimeAround > 2000) {
    switch (level) {
      case 0:
        zero();
        break;
      case 1:
        one();
        break;
      case 2:
        two();
        break;
      case 3:
        three();
        break;
      case 4:
        four();
        break;
    }
    // synch the time marker to the free running clock
    lastTimeAround = currentTime;
  }
}
void zero() {
  // you might think of this as reset or awaiting players
  Serial.println("level 0");
  level += 1;  // increment level by one, to level 1
}
void one() {
  // this might be level one
  Serial.println("level 1");
  level += 1;  // exit to level 2
}
void two() {
  // level two
  Serial.println("level 2");
  level += 1;
}
void three() {
  // and so on
  Serial.println("level 3");
  level += 1;
}
void four() {
  Serial.println("level 4");
  level = 0;  // reset to level 0
}

Here's a great video (not mine) that shows in a familiar way how critical state machines are to games.

Darts Scoreboard for between 1 and 4 players playing 501( could maybe change to let players select either 501 or 301)
seems to work (for me anyway)
feel free to use and abuse anyone

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

// LCD configuration
LiquidCrystal_I2C lcd(0x3F, 20, 4);  // I2C address may vary, check yours (e.g., 0x3F)
// Keypad setup
const byte ROWS = 4; // Four rows
const byte COLS = 4; // Four columns
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; // Connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 3, 2}; // Connect to the column pinouts of the keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// State machine states
enum State {
  SELECT_PLAYERS,
  PLAY_GAME,
  GAME_OVER
};
State currentState = SELECT_PLAYERS;

// Game variables
int numPlayers = 0;
int scores[4] = {501, 501, 501, 501};
int currentPlayer = 0;
char inputBuffer[4]; // Buffer for score entry
int inputIndex = 0;

void setup() {
  lcd.init(); // Initialize the LCD
  lcd.backlight(); //turn on backlight
  lcd.setCursor(3, 0);
  lcd.print("501 Darts Game");
  lcd.setCursor(0, 1);
  lcd.print("Select no of players:");
  lcd.setCursor(5, 2);
  lcd.print("1-4 Players");
  
}

void loop() {
  char key = keypad.getKey();

  switch (currentState) {
    case SELECT_PLAYERS:
      if (key >= '1' && key <= '4') {
        numPlayers = key - '0';
        lcd.clear();
        lcd.print("Players: ");
        lcd.print(numPlayers);
        delay(2000);
        lcd.clear();
        displayScores();
        currentState = PLAY_GAME;
        lcd.setCursor(0, 2);
        
              }
      break;

    case PLAY_GAME:
    lcd.setCursor(0, 0);
    lcd.print("Enter Your");
    lcd.setCursor(0,1);
    lcd.print("Score");
     lcd.setCursor(0, 2);
          lcd.print("Player ");
          lcd.print(currentPlayer + 1);
          lcd.setCursor(0,3);
          lcd.print("Score: ");
    
         if (key >= '0' && key <= '9') {
        if (inputIndex < 3) {
          inputBuffer[inputIndex++] = key;
        //  lcd.setCursor(0, 2);
        //  lcd.print("Player ");
        //  lcd.print(currentPlayer + 1);
        //  lcd.setCursor(0,3);
       //   lcd.print("Score: ");
          for (int i = 0; i < inputIndex; i++) {
            lcd.print(inputBuffer[i]);
          }
        }
      } else if (key == '#') { // Submit score
        inputBuffer[inputIndex] = '\0';
        int score = atoi(inputBuffer);
        inputIndex = 0;

        if (score > 0 && scores[currentPlayer] - score >= 0) {
          scores[currentPlayer] -= score;
        }

        if (scores[currentPlayer] == 0) {
          currentState = GAME_OVER;
          lcd.clear();
          lcd.print("Player ");
          lcd.print(currentPlayer + 1);
          lcd.print(" Wins!");
          break;
        }

        currentPlayer = (currentPlayer + 1) % numPlayers;
        displayScores();
      } else if (key == '*') { // Clear input
        inputIndex = 0;
        lcd.setCursor(0, 3);
        lcd.print("Score:      ");
      }  if (key == 'D') { // Restart game
        resetGame();
      }
      break;

    case GAME_OVER:
      if (key == 'D') { // Restart game

        resetGame();
      }
      break;
  }
}

void displayScores() {
  
  lcd.clear();
  for (int i = 0; i < numPlayers; i++) {
    lcd.setCursor(13, i);
    lcd.print("P");
    lcd.print(i + 1);
    lcd.print(": ");
    lcd.print(scores[i]);
  }
}

void resetGame() {
  numPlayers = 0;
  for (int i = 0; i < 4; i++) {
    scores[i] = 501;
  }
  currentPlayer = 0;
  currentState = SELECT_PLAYERS;
  lcd.clear();
  lcd.setCursor(5, 0);
  lcd.print("Game Reset");
  delay (2500);
  lcd.clear();
  lcd.print("Darts Game");
  lcd.setCursor(0, 1);
  lcd.print("Select players:");
  lcd.setCursor(0, 2);
  lcd.print("1-4 Players");
  
}

//
//still to do maximum score is 180 or is declared void
//
//

Wow, even an enum for the state variable!
Well done!

cheated a little my m8`s son is a programmer and he gave me a hand

1 Like

Seen the code in post#12?
I can really recommend the enum...

1 Like

On my todo list. So far keeping track in my head hasn't done my head in yet. You know what they say: "necessity is the mother of all invention".

I kind of wish they included more examples in the IDE of things like using enum, struct, C/C++ so folks like me don't spend years using just the language style of Arduino and getting into the habit of doing things that way.

Cheers

see The C Programming Language, page 114 for struct. Do a find for enum to see various examples

1 Like

At the beginning one must make a selection of things to teach.
Otherwise the newbies drown in too much information.
The enum is not needed to get started. But with more complicated programs it is really nice.
So it all comes down to a balance.

1 Like