Increasing an Arduino Reaction Game's Player Capacity

Hey!
I've made a 2 player reaction game with two modes, the ability to choose the number of games you want to play, and a whole lot of code. I was wondering if it would be possible to also add the ability to choose the number of players wanted (between 1, 2, 3 and 4 players). The problem is that I don't know how to do this without basically quadrupling the code. While this is possible, it isn't ideal. Does anyone have any ideas?

// include the library code:
#include <LiquidCrystal.h>

//setting button pins
const int rButtonPin = A3;
const int bButtonPin = A2;
const int sButtonPin = A1;
const int LightPin = A0;

//setting button states
int rButtonState = 0;
int bButtonState = 0;
int sButtonState = 0;

//seeing if the player is done or forfeited
int rDone = 0;
int bDone = 0;

int rForfeit = 0;
int bForfeit = 0;
int rForfeitCounter = 0;
int bForfeitCounter = 0;

//counting time
int time = 0;
int timeDone = 0;
int rTime = 0;
int bTime = 0;
int rOvrTime = 0;
int bOvrTime = 0;
int rAvg = 0;
int bAvg = 0;
int rWins = 0;
int bWins = 0;


//command to start the game, and other self explanatory variables
String command;
int game = 0;
int gtype = 0;
int stopPrint = 0;
int numgames = 0;
int Delay = 0;
int gnum = 1;

//setting up the lcd with the pin #'s
  const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
  LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  // declaring pins and starting the serial print
  pinMode(rButtonPin, INPUT);
  pinMode(bButtonPin, INPUT);
  pinMode(sButtonPin, INPUT);

  Serial.begin(9600);
  pinMode(LightPin, OUTPUT);

  //for generating a random number
  randomSeed(analogRead(0));

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);


}

void loop() {
  // put your main code here, to run repeatedly:
  //checking the if the button's are on or off
  rButtonState = digitalRead(rButtonPin);
  bButtonState = digitalRead(bButtonPin);
  sButtonState = digitalRead(sButtonPin);

  //for the new buttons
  digitalWrite(rButtonPin, HIGH);
  digitalWrite(bButtonPin, HIGH);
  digitalWrite(sButtonPin, HIGH);

  switch (game) {
    case 0:
      GetGame();
      break;
    
    case 1:
      GetNumGames();
      break;
    
    case 2:
      PreGame();
      break;

    case 3:
      MainGame();
      break;

    case 4:
      OVRGAME();
      break;

    case 5:
      NUMOFWINSGAME();
      break;
    
    case 6:
      OVRResults();
      break;
    
    case 7:
      Restart();
  }
}
      


void GetGame () {
  if (stopPrint == 0) {
    gtype = 0;
    lcd.clear();
    lcd.print("Choose Game Red");
    Serial.print("Press Red Button to change game type\n");
  }
  stopPrint = 1;
  if (rButtonState == LOW) {
    if(gtype == 2) {
      gtype = 1;
      lcd.clear();
      lcd.print("OVR GAME");
      delay(300);
    } else {
      gtype = 2;
      lcd.clear();
      lcd.print("# OF WINS GAME");
      delay(300);   
    }
  }
  if(sButtonState == LOW && gtype > 0) {
    game++;
    stopPrint = 0;
    Serial.print("Game type got: ");
    Serial.print(gtype);
  }
}



void GetNumGames () {
  if (stopPrint == 0){
    lcd.setCursor(0,1);
    lcd.print("Choose Game #: ");
  }
  stopPrint = 1;
  if (rButtonState == LOW) {
    numgames ++;       
    if (numgames > 9){
      numgames = 9;
    } 
    lcd.setCursor(14,1);
    lcd.print(numgames);
    delay(300);   
  }
  if (bButtonState == LOW) {
    numgames --;
    if (numgames < 1){
      numgames = 1;
    }
    lcd.setCursor(14,1);
    lcd.print(numgames);
    delay(300);
  }
  if (sButtonState == LOW && numgames > 0) {
    game++;
    Serial.print("Next step");
    Serial.print(numgames);
    Serial.print(sButtonState);
    stopPrint = 0;
    Serial.print("numgames = ");
    Serial.print(numgames);
    Serial.print("gnum = ");
    Serial.print(gnum);
  }
}



void PreGame() {
  if (stopPrint == 0){
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print(".");
    delay(300);
    lcd.print(".");
    delay(300);
    lcd.print(".");
    delay(300);
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("PressBlackButton");
    lcd.setCursor(0,1);
    lcd.print("To Start Game ");
    lcd.print(gnum);
    stopPrint = 1;
  } 
  delay(300);
  sButtonState = digitalRead(sButtonPin);

  if (sButtonState == LOW) {
    //delaying until button pressed
    Serial.print("pregame pressed");
    game++;
    lcd.clear();
    lcd.setCursor(0,0);
    stopPrint = 0;
  }
}



void MainGame() {
    delay(500);
    Serial.print("Get Ready!\n");
    lcd.print("Get Ready!");
    Delay = random(100, 300);
    //delaying a random time
    delay(Delay*10);
    //Checking for False Start
    rButtonState = digitalRead(rButtonPin);  
    bButtonState = digitalRead(bButtonPin);  

    if (rButtonState == LOW) {
      rForfeit = 1;
      rDone = 1;
      rForfeitCounter++;
    }

    if (bButtonState == LOW) {
      bForfeit = 1;
      bDone = 1;    
      bForfeitCounter++;  
    }    
    //STARTING!!!
    digitalWrite(LightPin, HIGH);
    game = game + gtype;         
}



void OVRGAME() {
  //counting time
    if (timeDone == 0) {
      delay(1);
      time ++;
    }

    //checking if they're done!
    if(rButtonState == LOW && rForfeit == 0 && rDone == 0) {
      rDone = 1;
      rTime = time;
      rOvrTime = rOvrTime + rTime;
    }

    if(bButtonState == LOW && bForfeit == 0 && bDone == 0) {
      bDone = 1;
      bTime = time;
      bOvrTime = bOvrTime + bTime;
    }

    //annoying processing thingy
    if (rDone == 1 && bDone == 1) {
      timeDone = 1;
      
      if (stopPrint == 0) {
        Serial.print(time);
        lcd.clear();
        digitalWrite(LightPin, LOW);
        Serial.print("processing.");
        lcd.print("processing.");
        delay(500);
        Serial.print(".");
        lcd.print(".");
        delay(500);
        Serial.print(".\n");
        lcd.print(".");     
        delay(500);
      
        lcd.clear();            
        //writing the time, or if they forfeited
        if (rTime == 0) {
          Serial.print("Red went too early\n");
          lcd.print("R Forf ");
        }
        else {
          Serial.print("Red time is: ");
          lcd.print("R ");
          Serial.print(rTime);
          lcd.print(rTime); 
          Serial.print("ms\n");   
          lcd.print("ms ");
        }

        if (bTime == 0) {
          Serial.print("Blue went too early\n");
          lcd.print(" B Forf ");        
        }
        else {
          Serial.print("Blue time is: ");
          lcd.print("B ");
          Serial.print(bTime); 
          lcd.print(bTime);
          Serial.print("ms\n");
          lcd.print("ms");
        }
      
        delay(300);

      //set cursor to second row
        lcd.setCursor(0, 1);
        lcd.print("RA ");
        rAvg = rOvrTime/(gnum - rForfeitCounter) + rForfeitCounter * 10;
        bAvg = bOvrTime/(gnum - bForfeitCounter) + bForfeitCounter * 10;
        if(rOvrTime == 0){
            lcd.print("N/A ");
          } else{
            lcd.print(rAvg);
            lcd.print("m ");
          }
          lcd.print("BA ");        
          if(bOvrTime == 0){
            lcd.print("N/A");
          } else{
            lcd.print(bAvg);
            lcd.print("m");
          }
        }
      
        stopPrint = 1;
        delay(300);

      if (sButtonState == LOW) {
        game = game + 2;
        stopPrint = 0;
      }      
    }
}



void NUMOFWINSGAME() {
  //counting time
    if (timeDone == 0) {
      delay(1);
      time ++;
    }

    //checking if they're done!
    if(rButtonState == LOW && rForfeit == 0 && rDone == 0) {
      rDone = 1;
      rTime = time;
      rOvrTime = rOvrTime + time;
    }

    if(bButtonState == LOW && bForfeit == 0 && bDone == 0) {
      bDone = 1;
      bTime = time;
      bOvrTime = bOvrTime + time;
    }

    //annoying processing thingy
    if (rDone == 1 && bDone == 1) {
      timeDone = 1;

      if(stopPrint == 0) {
        Serial.print(time);
        lcd.clear();
        digitalWrite(LightPin, LOW);
        Serial.print("processing.");
        lcd.print("processing.");
        delay(500);
        Serial.print(".");
        lcd.print(".");
        delay(500);
        Serial.print(".\n");
        lcd.print(".");     
        delay(500);
      
        lcd.clear();            
        //writing the time, or if they forfeited
        if (rTime == 0) {
          Serial.print("Red went too early\n");
          lcd.print("R Forf ");
        }
        else {
          Serial.print("Red time is: ");
          lcd.print("R ");
          Serial.print(rTime);
          lcd.print(rTime); 
          Serial.print("ms\n");   
          lcd.print("ms ");
        }

        if (bTime == 0) {
          Serial.print("Blue went too early\n");
          lcd.print(" B Forf ");        
        }
        else {
          Serial.print("Blue time is: ");
          lcd.print("B ");
          Serial.print(bTime); 
          lcd.print(bTime);
          Serial.print("ms\n");
          lcd.print("ms");
        }
      
        delay(300);

        //set cursor to second row
        lcd.setCursor(0, 1);

        //seeing who wins!
        if (rTime == bTime && rForfeit == 0){
          Serial.print("TIE\n");
          lcd.print("TIE");
          bWins++;
          rWins++;
        } 
        if (rTime == bTime && rForfeit == 1){
          Serial.print("No-one wins\n");
          lcd.print("Both Forfeited");
        } 
        if (rTime < bTime && rForfeit == 0) {
          Serial.print("RED WINS!!!\n");
          lcd.print("RED WINS");
          rWins++;
        } 
        if(rTime < bTime && rForfeit == 1){
          Serial.print("BLUE WINS!!!\n");
          lcd.print("BLUE WINS");
          bWins++;  
        }
        if (bTime < rTime && bForfeit == 0) {
          Serial.print("BLUE WINS!!!\n");
          lcd.print("BLUE WINS");
          bWins++;        
        } 
        if(bTime < rTime && bForfeit == 1){
          Serial.print("RED WINS!!!\n");
          lcd.print("RED WINS");
          rWins++;
        }

        stopPrint = 1;
      }  
        
      delay(300);
      
      if (sButtonState == LOW) {
        game ++;
        stopPrint = 0;       
      }        
    }
}



void OVRResults() {
  if (stopPrint == 0 && gtype == 1) {
    lcd.clear();
    lcd.setCursor(0,0);
    if(rOvrTime > 0 && bOvrTime > 0){ 
      if(rAvg > bAvg) {
        lcd.print("rA = +");
        lcd.print(rAvg - bAvg);
        lcd.print("ms");
    } else if(bAvg > rAvg) {
        lcd.print("bA = +");
        lcd.print(bAvg - rAvg);
        lcd.print("ms");
    }  else {
      lcd.print("r = b");
    } 
  } else {
    lcd.print("N/A");
  }
    lcd.setCursor(0,1);
    lcd.print("PressBlackButton");
    stopPrint = 1;
  }

  if (stopPrint == 0 && gtype == 2) {
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("RW = ");
    lcd.print(rWins);
    lcd.print(" BW = ");
    lcd.print(bWins);
    lcd.setCursor(0,1);
    lcd.print("PressBlackButton");
    stopPrint = 1;
  } 
  
  if (sButtonState == LOW) {
    game++;
    gnum++;
    stopPrint = 0;
    Serial.print(game);
  } 
}



void Restart() {
  if (gnum > numgames) {
    if(stopPrint == 0) {
      Serial.print("numgames = ");
      Serial.print(numgames);
      Serial.print("\ngnum = ");
      Serial.print(gnum);
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Results");
        delay(500);
        lcd.clear();
        if(rOvrTime == 0) {
          rAvg = 1000;
        } else {
          rAvg = rOvrTime/(gnum - rForfeitCounter - 1) + rForfeitCounter * 10;
        }

        if(bOvrTime == 0) {
          bAvg = 1000;
        } else {
          bAvg = bOvrTime/(gnum - bForfeitCounter - 1) + bForfeitCounter * 10;
        }   
        Serial.print(rForfeitCounter);
        Serial.print(bForfeitCounter);
        if (gtype == 1) {
          if (rAvg == bAvg){
            Serial.print("TIE\n");
            lcd.print("TIE");
          } 
          if (rAvg < bAvg) {
            Serial.print("RED WINS!!!\n");
            lcd.print("RED WINS");
          } 
          if (bAvg < rAvg){
            Serial.print("BLUE WINS!!!\n");
            lcd.print("BLUE WINS");
          }
          lcd.setCursor(0,1);
          lcd.print("r:");
          if(rAvg == 1000){
            lcd.print("N/A ");
          } else{
            lcd.print(rAvg);
            lcd.print("ms ");
          }
          lcd.print("b:");
          if(bAvg == 1000){
            lcd.print("N/A");
          } else{
            lcd.print(bAvg);
            lcd.print("ms");
          }
          Serial.print("rAvg:");
          Serial.print(rAvg);
          Serial.print("ms R Penalty: ");
          Serial.print(rForfeitCounter * 10);
          Serial.print("\n bAvg:");
          Serial.print(bAvg);
          Serial.print("ms B Penalty: ");
          Serial.print(bForfeitCounter * 10);      
        }

        if (gtype == 2) {
          if (rWins == bWins){
            Serial.print("TIE\n");
            lcd.print("TIE");
          } 
          if (rWins < bWins) {
            Serial.print("RED WINS!!!\n");
            lcd.print("RED WINS");
          } 
          if (bWins < rWins){
            Serial.print("BLUE WINS!!!\n");
            lcd.print("BLUE WINS");
          }
          lcd.setCursor(0,1);
          lcd.print("rWins:");
          lcd.print(rWins);
          lcd.print(" bWins:");
          lcd.print(bWins);
        } 
        stopPrint = 1;
      }
      delay(300);
      sButtonState = digitalRead(sButtonPin);

      if(sButtonState == LOW){
        stopPrint = 0;
        bWins = 0;
        rWins = 0;
        rOvrTime = 0;
        bOvrTime = 0;
        gnum = 1;
        numgames = 0; 
        rForfeitCounter = 0;
        bForfeitCounter = 0; 
        game = 0;
        gtype = 0;
        rDone = 0;
        bDone = 0;

        rForfeit = 0;
        bForfeit = 0;
        time = 0;
      }  
  } else {

      rDone = 0;
      bDone = 0;

      rForfeit = 0;
      bForfeit = 0;

      time = 0;
      timeDone = 0;
      rTime = 0;
      bTime = 0;
      stopPrint = 0;
      game = 2;      
    }

}

I would start with grouping all variables associated with a player in one structure, e.g.,

struct Player {
  uint8_t const pin;
  uint8_t state;
  bool done;
  bool forfeit;
  uint16_t forfeitCounter;
  uint16_t avg;
  uint16_t wins;
  unsigned long time;
  unsigned long ovrTime;
};

Then you can make an array of players, e.g.,

Player players[] = {
  {A3, 0, false, false, 0, 0, 0, 0, 0},
  {A2, 0, false, false, 0, 0, 0, 0, 0}};

And whenever you want to do something with a player, you can loop over the array, e.g.,

void setup() {
  for (Player& player: players) {
    pinMode(player.pin, INPUT);
  }
  // ...

You can organize your player data into your own variable types using structs or classes.
That lets you make generic players as an array of players that all work the same way and which to work is selected by number. One function that takes player number as an arg is the code for all.

https://www.tutorialspoint.com/structs-in-arduino-program

Arduino Reference on Arrays.

When you have the player struct array and functions, your code can shrink.

Ooh interesting. That’s a really good idea and I’ll be sure to try this out. Thank you so much!

Good idea! Thank you so much for sharing the tutorials. I’ll be sure to check them out!