best way to set up a user-setting-menu in code?

I have a variety of settings that are user-adjustable in my code:

setting name / possible settings:
gamePoints / 5, 11, 21
matchSize / 3, 5, 7, 9
playerSides / random, red-green, green-red
servingSide / random, left, right
sound / on, off

Currently, I am building this project as a state-machine, and I broke out each setting into a state, for example:

boolean sOSelectGamePoints = false; (sO is my notation for "state of")
boolean sOSelectMatchSize = false;
...

and I'm using arbitrary number values to represent the different setting values, for example:

byte gamePoints = 1;
// 0=5, 1=11, 2=21

As a result, I end up with a lot of different but nearly identical functions, for example:

selectGamePoints()
selectMatchSize()
...

Here is the relevant code:

// *** libraries to include ****
#include <EEPROM.h> // so we can save settings


// *** global variables ***

// pin declarations:
const byte chooseSettingButton = 2; // arduino pin 2
const byte adjustSettingButton = 3; // arduino pin 3
const byte startButton = 4; // arduino pin 4


// *** variables for the state-machine ***

boolean sOIntro = false;
boolean sOSelectGamePoints = false;
boolean sOSelectMatchSize = false;
boolean sOSelectPlayerSides = false;
boolean sOSelectServingSide = false;
boolean sOSelectSound = false;


// setting variables (default values):

byte gamePoints = 1;
// 0=5, 1=11, 2=21
// if modified, saved as byte #0 in EEPROM

byte matchSize = 1;
// 0=3, 1=5, 2=7, 3=9
// if modified, saved as byte #1 in EEPROM

byte playerSides = 0;
// 0=random, 1=red/green, 2=green/red
// if modified, saved as byte #2 in EEPROM

byte servingSide = 0;
// 0=random, 1=left, 2=right
// if modified, saved as byte #3 in EEPROM

byte sound = 0;
// 0=ON, 1=OFF
// if modified, saved as byte #4 in EEPROM



void setup() {
  // set up pins:
  pinMode(chooseSettingButton, INPUT);
  pinMode(adjustSettingButton, INPUT);
  pinMode(startButton, INPUT);

  // prepping EEPROM (if need be):
  
  // *** user adjustable ***
  byte saveByte = 57; // a location within EEPROM
  // must be >10 because we reserve those spots for user-settings
  
  // *** user adjustable ***
  byte saveByteValue = 137; // a random value to check for
  // Change this value to ensure a flush of EEPROM

  // check if this is the very first time running this program,
  // and if so, flush EEPROM:
  if(EEPROM.read(saveByte) != saveByteValue){
    // then this is the first time we are running this code,
    // and we need to default all other bytes:

    for(int i=0; i< EEPROM.length(); i++){
      EEPROM.write(i, 255); // 255 is default value
    }

    // set the saveByte:
    EEPROM.write(saveByte, saveByteValue);
    // this ensures that next time through the eeprom won't
    // be flushed.
  }
  

  // load-in any saved settings if we have any:
  
  if(EEPROM.read(0) != 255) // 255 = nope, anything else = yep
  {
    // load that saved option:
    gamePoints = EEPROM.read(0);
  }
  if(EEPROM.read(1) != 255)
  {
    // load that saved option:
    matchSize = EEPROM.read(1);
  }
  if(EEPROM.read(2) != 255)
  {
    // load that saved option:
    playerSides = EEPROM.read(2);
  }
  if(EEPROM.read(3) != 255)
  {
    // load that saved option:
    servingSide = EEPROM.read(3);
  }
  if(EEPROM.read(4) != 255)
  {
    // load that saved option:
    sound = EEPROM.read(4);
  }


  // start us off:
  sOIntro = true;
  
} // end of setup()




void loop() {
  
  // Different States of the State-Machine:

  // intro:
  if(sOIntro) {
    intro();
    sOIntro = false;
  }

  // selectGamePoints:
  if(sOSelectGamePoints) {
    selectGamePoints();
    sOSelectGamePoints = false;
  }

  // selectMatchSize:
  if(sOSelectMatchSize) {
    selectMatchSize();
    sOSelectMatchSize = false;
  }

  // selectPlayerSides:
  if(sOSelectPlayerSides) {
    selectPlayerSides();
    sOSelectPlayerSides = false;
  }

  // selectServingSide:
  if(sOSelectServingSide) {
    selectServingSide();
    sOSelectServingSide = false;
  }

  // selectSound:
  if(sOSelectSound) {
    selectSound();
    sOSelectSound = false;
  }

} // end of loop()



void intro(){

  // while the "choose setting", "start", and player button(s)
  // remain un-pressed:
  while (1){

    // if the chooseSettingButton is pressed:
    if(buttonEdgeDetectLow(chooseSettingButton)){

      // go to:  
      // (change the state of)
      sOSelectGamePoints = true;
      
      // get out of the while-loop:
      break;
    }
  }
  
} // end of intro



void selectGamePoints(){

  // while the "choose setting" and "start" button remains
  // un-pressed:
  while (1){

    // each time the adjustSetting button is pressed:
    if(buttonEdgeDetectLow(adjustSettingButton)){
      // cycle through the options:
      // 0=5, 1=11, 2=21
      
      // check if we are already at the end of the line:
      if(gamePoints == 2){
        // back to the start:
        gamePoints = 0;
      }
      else{
        // increment:
        gamePoints ++;
      }
    }

    // if the chooseSetting button is pressed:
    if(buttonEdgeDetectLow(chooseSettingButton)){

      // save to appropriate eeprom address:
      EEPROM.write(0, gamePoints);

      // go to:  
      // (change the state of)
      sOSelectMatchSize = true;
      
      // get out of the while-loop:
      break;
    }

    // if the startButton is pressed:
    if(buttonEdgeDetectLow(startButton)){

      // save to appropriate eeprom address:
      EEPROM.write(0, gamePoints);
      
      // go to:  
      // (change the state of)
      sOIntro = true;
      
      // get out of the while-loop:
      break;
    }
  }
} // end of selectGamePoints



void selectMatchSize(){

  // while the "choose setting" and "start" button remains
  // un-pressed:
  while (1){

    // each time the adjustSetting button is pressed:
    if(buttonEdgeDetectLow(adjustSettingButton)){
      // cycle through the options:
      // 0=3, 1=5, 2=7, 3=9
      
      // check if we are already at the end of the line:
      if(matchSize == 3){
        // back to the start:
        matchSize = 0;
      }
      else{
        // increment:
        matchSize ++;
      }
    }

    // if the chooseSetting button is pressed:
    if(buttonEdgeDetectLow(chooseSettingButton)){

      // save to appropriate eeprom address:
      EEPROM.write(1, matchSize);

      // go to:  
      // (change the state of)
      sOSelectPlayerSides = true;
      
      // get out of the while-loop:
      break;
    }

    // if the startButton is pressed:
    if(buttonEdgeDetectLow(startButton)){

      // save to appropriate eeprom address:
      EEPROM.write(1, matchSize);
      
      // go to:  
      // (change the state of)
      sOIntro = true;
      
      // get out of the while-loop:
      break;
    }
  }
} // end of selectMatchSize



// similar functions for all other user-adjustable settings.

My question to you guys:

Is there a better way for me to be writing this code, such that I don't have as much repetition?

Maybe the more streamlined solution involves structs, or typedef's, or pointers (?)
Maybe it involves consolidating my different states of settings into a single "setting" state?

  // while the "choose setting" and "start" button remains
  // un-pressed:
  while (1){

That comment does NOT reflect the extent of the while loop.

Maybe it involves consolidating my different states of settings into a single "setting" state?

What each function does is get a value and store the value in EEPROM in the appropriate place, and set up for the next function that makes sense. It's that last part that does NOT belong in the function. If you removed that part, the functions would differ only in which variable they stored data in, and where in EEPROM they stored data. You could, then, have one function that took two arguments - a reference argument to define where the store the value and an address for where to store the data in EEPROM.

When the function returned after getting a value, you would change the state for that variable.

Thanks for the tips.

PaulS:

  // while the "choose setting" and "start" button remains

// un-pressed:
  while (1){



That comment does NOT reflect the extent of the while loop.

Good point, I'll change the comment to:
// loop (till we call break):

PaulS:
What each function does is get a value and store the value in EEPROM in the appropriate place, and set up for the next function that makes sense. It's that last part that does NOT belong in the function. If you removed that part, the functions would differ only in which variable they stored data in, and where in EEPROM they stored data. You could, then, have one function that took two arguments - a reference argument to define where the store the value and an address for where to store the data in EEPROM.

When the function returned after getting a value, you would change the state for that variable.

So you are saying the only thing I can do to shorten my code is to create a function for storing in the eeprom? (I'd still need to call that function within each of my setting functions though... so still a lot of copy/pasting going on).

The reason I can't easily create a "setting" function is because each setting has a different number of options. For example: the sound setting only has 2 options: 0=off, 1=on. While the other settings have more.

perhaps something like this as a function:

void settingFunction(byte numOfOptions, byte settingNum)

but this is all getting too abstract to easily follow, hence me thinking I should be using typedef's.

So you are saying the only thing I can do to shorten my code is to create a function for storing in the eeprom? (I'd still need to call that function within each of my setting functions though... so still a lot of copy/pasting going on).

No copy/pasting, except for the CALL to the ONE function.

how are you planning to display the options.
This looks like it would be easier to write and follow using switch/case. You can add switchs with in switch/case which will take care of the display side. That way the button ++ code could be written once for all selections.
You can also keep a prev for the memory then update only if a change has been made rather than updating because you went to a certain spot in the program.