Countdown timer not waiting for start button

1.

 Serial.print(200000);

and

 Serial.print(200000UL);

are showing the same output. Then, what is the need of using UL suffix?

2. If I have uderstood your post #20 correctly, the Compiler automatically chooses unsigned long type buffer to hold 200000 during the processing of this expression if(millis() - previousMillis > 200000){. This proposition holds truth from my following simple experiment where I append ul or not after 33000, the sketch produces the same output. (The Compiler does not treat 33000 (0x80E8) as a negative number as I had thought.)

void setup() 
{
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  delay(2000);
  digitalWrite(13, LOW);
  delay(2000);
  unsigned long previousMillis = millis();
  Serial.begin(9600);
  while(millis() - previousMillis < 33000)
  {
    ;
  }
  digitalWrite(13, HIGH);
  delay(5000);
  digitalWrite(13, LOW);
}

void loop() {
  // put your main code here, to run repeatedly:

}

In this case? Nothing. As I said before, the explicit type symbol is needed in expressions where you need to make sure they must be correctly evaluated.

Sure it is, because you're comparing two integer values (long in this case), no matter if you add "UL" or not. Anyway, I don't know (and won't investigate) the internal C compiler assumptions when parsing a value, I'm just saying adding UL suffix comparing "unsigned long" values isn't an error and can be used. Except for specific expressions (not simple like this one) when you surely need to explicitly specify the data type to be used for a constant (like using "10" in a float operation, to avoid an integer operation better using "10F", but I generally use "10.0").

What will be output after the execution of the following codes:?

int x = 33000UL;
Serial.println(x);

It's the conversion from 33000UL and a signed 16 bit int, so it results in a negative numebr (2's complement, I guess 33000-32768something around -32500 something).
Here the problem is in the assignment of an UL value into an int, a smaller type. nothing to do with the original question/problem, so what's the deal in pointing that?

Just an academc interest in order to see what you predict.

I have found a while loop to be useful for holding some game state (waiting player entry, for example). That while loop will poll the button you want and wait for a state change that is compared to a previous or last button state already stored.

As @LarryD said:

These are both critical to understand. My brain still always wrestles with putting together discrete millis() timers together but it's well worth the trouble.

State machines are just how games are made. I often post a YouTube link in threads where state machines apply, especially in a game context (that's most of what I make on Arduino), and I will post it here again, it's key to understanding game design.

I will also supply a full, working game I wrote for Arduino for someone else a while back here, and am happy to share with you. It's super easy to set up and the part you require about holding until a button is pressed in in there. You may also find other things useful, like retaining a high score in EEPROM. Very simple circuit, see the notes at top of sketch. Good luck.

/* Blast 'Em! Note to self: DO NOT MODIFY!
 
  by Hallowed31
    May 14-15, 2024

    Additional parts needed: 
     - One or two light-dependent resistors. No 
       additional pullup or pulldown resistors needed.
     - One or two servos. Attach some target to the servo
       horn after installing LDR through each one, like a 
       bullseye.
     - One pushbutton. 
     - Some light source as blaster. Cat laser toy, perhaps
     - External power supply for servos. Never use Arduino
       +5V regulator to power servos.
     - Computer connected to Arduino to use Serial Monitor
       or why not try your favorite terminal emulation program
       such as puTTY or CoolTerm?  

    Gameplay circuit:
     - LDRs from A0 and A1 to ground.

     - Servos powered externally (+5V) from Arduino, signal pins
        to D9 and D10 and don't forget
        to ground servos to both Arduino AND external power supply

     - Start button (pushbutton) to D2 and Arduino GND. 

     - Works with one sensor/servo combo or two. 

     - Known issue: the timer will occasionally overflow/keep running
          when cycling between modes such as manual, BIOS, gameplay

     - Ensure Serial monitor is open, default size prints prettiest.
        Autoscroll should be checked, show timestamp unchecked, 
        set to 115200 baud, no line ending.

     - CoolTerm settings: "blastEm.stc" 
        
*/
#include <EEPROM.h>
#include <Servo.h>

Servo servoOne, servoTwo;

int targetOne = A0;
int targetTwo = A1;
int led = 13;
int playerButton = 2;
int target1Value, target2Value;
int gameMode, highScore, playerState, lastPlayerState;
int eeAddress;
unsigned long score, gameTime, lastGameTime, gameTimeLimit, targetDown, lastTargetDown, tar2Down, lastTar2Down;
unsigned long previousGameTimer;
const long interval = 1000;
const long biosWait = 3200;
unsigned long prevBios = 0;
int timer = 30;
int biosTimer = 3;
int bios;

void  setup() {
  Serial.begin(115200);
  eeAddress = 0;
  pinMode(targetOne, INPUT_PULLUP);
  pinMode(targetTwo, INPUT_PULLUP);
  pinMode(playerButton, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  /* uncomment resetHighScore() function and upload to reset
    saved high score to zero,
    then comment out again, upload sketch again
    to resume playing normal game */
  // resetHighScore();
  servoOne.attach(10);
  servoTwo.attach(11);
  servoOne.write(0);
  servoTwo.write(0);
  cycleServos();
  gameTimeLimit = 31330;
  targetDown = 500;
  tar2Down = 500;
  lastTargetDown = 0;
  lastTar2Down = 0;
  lastPlayerState = LOW;
  target1Value = 0;
  target2Value = 0;
  credits();
  biosPrompt();
  unsigned long biosT = millis();
  while (biosT + biosWait >= millis()) {
    unsigned long thisBiosTimer = millis();
    if (Serial.available() > 0) {
      char biosMode = Serial.read();
      switch (biosMode) {
        case 'b':
          bios = true;
          break;
        case 'B':
          bios = true;
          break;
      }
    }
    if (thisBiosTimer - prevBios >= interval) {
      if (biosTimer > 0) {
        Serial.println(biosTimer);
      }
      biosTimer --;
      if (biosTimer == -1) {
        timer = 0;
      }
      prevBios = thisBiosTimer;
    }
  }
  playerState = digitalRead(playerButton);
  while (playerState != lastPlayerState) {
    playerState = digitalRead(playerButton);
    awaitingPlayerMessage();
    viewHighScore();
  }
  lastPlayerState = playerState;
  score = 0;
  gameTime = 0;
  lastGameTime = 0;
  previousGameTimer = 0;
  if (bios == true) {
    gameMode = 1;
  }
  else if (bios == false) {
    gameMode = 2;
  }
}

void loop() {
  // start game timer
  gameTime = millis();
  if (Serial.available() > 0) {
    char switchMode = Serial.read();
    switch (switchMode) {
      case 'b':
        gameMode = 1; // viewBios/menu
        break;
      case 'n':
        gameMode = 2; // new player
        break;
      case ' ':
        gameMode = 3; // play game
        break;
      case 'r':
        gameMode = 4; // gameover
        break;
      case '?':
        gameMode = 5; // view raw
        break;
      case 'h':
        gameMode = 6; // view hi scores
        break;
      case 'm':
        gameMode = 7; // read manual
        break;
    }
  }
  switch (gameMode) {
    case 1:
      viewBios();
      lastGameTime = gameTime;
      break;
    case 2:
      newPlayer();
      lastGameTime = gameTime;
      break;
    case 3:
      normalGameplay();
      if (gameTime - lastGameTime > gameTimeLimit) { // outta time
        gameMode = 4;
      }
      break;
    case 4:
      gameOver();
      break;
    case 5:
      viewRawTargetReadings();
      break;
    case 6:
      viewHighScore();
      break;
    case 7:
      manual();
      break;
  }
}
// gameMode 2
void newPlayer() {
  newPlayerMessage();
  lastGameTime = 0;
  score = 0;
  timer = 30;
  gameMode = 3;
}
// gameMode 3
void normalGameplay() {
  unsigned long thisGameTimer = millis();
  if (thisGameTimer - previousGameTimer >= interval) {
    timer --;
    if (timer == -1) {
      timer = 30;
    }
    previousGameTimer = thisGameTimer;
  }
  target1Value = analogRead(targetOne);
  target2Value = analogRead(targetTwo);
  if (target1Value < 75) {
    lastTargetDown = gameTime;
    if (gameTime - lastTargetDown <= targetDown) {
      digitalWrite(led,  HIGH);
      servoOne.write(90);
      score = score + 1;
      if (score > highScore) {
        highScore = score;
        EEPROM.put(eeAddress, highScore);
      }
    }
  }
  if (target2Value < 75) {
    lastTar2Down = gameTime;
    if (gameTime - lastTar2Down <= tar2Down) {
      digitalWrite(led,  HIGH);
      servoTwo.write(90);
      score = score + 1;
      if (score > highScore) {
        highScore = score;
        EEPROM.put(eeAddress, highScore);
      }
    }
  }
  if (gameTime - lastTargetDown >= targetDown) {
    digitalWrite(led, LOW);
    servoOne.write(0);
  }
  if (gameTime - lastTar2Down >= tar2Down) {
    digitalWrite(led, LOW);
    servoTwo.write(0);
  }
  Serial.print("score ");
  Serial.print(score);
  Serial.print("\t\t");
  if (timer >= 10) {
    Serial.print("Time ");
  }
  else if (timer < 10) {
    Serial.print("Time 0");
  }
  Serial.println(timer);
}

// gameMode 4
void gameOver() {
  gameOverMessage();
  awaitingPlayerMessage();
  playerState = digitalRead(playerButton);
  delay(20); // simple debouncing
  playerState = digitalRead(playerButton);
  if (playerState == LOW) {
    gameMode = 2; // reset
  }
}

/*****************************************************
 ***********        UTILITIES       ******************
 *****************************************************/
 unsigned long ma, mb, mc, lastMa, lastMb, lastMc;

void viewBios() {
  Serial.println();
  Serial.println(F("                              Blast 'Em v2"));
  Serial.println();
  Serial.println(F("            Type SPACE during game to resume normal gameplay"));
  Serial.println();
  Serial.println(("                   Type r during game to reset game"));
  Serial.println();
  Serial.println(("                        Type m to view manual"));
  Serial.println(("               Type ? during game to view raw sensor readings"));
  Serial.println(("           Type h during game to pause game and see high score"));
  EEPROM.get(eeAddress, highScore);
  Serial.println();
  Serial.print(("                 All Time High Score: "));
  Serial.println(highScore);
  Serial.println();
  Serial.println(("                     Type X to exit BIOS and return"));
  Serial.println();
  while (!Serial.available()) {
  }
  if (Serial.available() > 0) {
    char myBios = Serial.read();
    switch (myBios) {
      case 'x':
        gameMode = 2;
        bios = false;
        lastGameTime = gameTime;
        break;
      case 'X':
        gameMode = 2;
        bios = false;
        lastGameTime = gameTime;
        break;
      case ' ':
        gameMode = 3; // play game
        break;
      case 'r':
        gameMode = 4; // gameover
        break;
      case '?':
        gameMode = 5; // view raw
        break;
      case 'h':
        gameMode = 6; // view hi scores
        break;
      case 'm':
        gameMode = 7; // read manual
        break;
    }
  }
}


void manual() {
  Serial.println(F("press button when prompted to begin normal play"));
  Serial.println();
  Serial.println(F("Enter key commands during game and press ENTER"));
  Serial.println();
  Serial.println(("Key Commands: "));
  Serial.println(("  ? during game to view sensor data for calibration"));
  Serial.println(("  h to view high score"));
  Serial.println(("  r to reset game"));
  Serial.println(("  h to view high score"));
  Serial.println(("  Space bar + Enter to return to game time and score"));
  Serial.println(("  r to reset game"));
  Serial.println();
  Serial.println();
  Serial.println(("                             Type X to return"));
  Serial.println();
   while (!Serial.available()) {
  }
    if (Serial.available() > 0) {
      char escape = Serial.read();
      switch (escape) {
        case 'x':
          gameMode = 2;
          lastGameTime = gameTime;
          break;
        case 'X':
          gameMode = 2;
          lastGameTime = gameTime;
          break;
      }
    }
}

// called in newPlayer() gameMode 2
void newPlayerMessage() {
  score = 0;
  Serial.println();
  Serial.println(F("                         New Player Blast Em!"));
  Serial.println();
  delay(2000);
  Serial.print("score ");
  Serial.println(score);
}

// called in gameOver() gameMode 4
void awaitingPlayerMessage() {
  ma = millis();
  const unsigned long maTime = 5000;
  if (ma - lastMa >= maTime) {
    lastMa = ma;
    Serial.println();
    Serial.println(F("                          Press Button to Play"));
    Serial.println();
    Serial.println(F("                            Awaiting Player"));
    Serial.println();
  }
}

// called in gameOver() gameMode 4
void gameOverMessage() {
  mb = millis();
  const unsigned long mbTime = 7000;
  if (mb - lastMb >= mbTime) {
    lastMb = mb;
    Serial.println();
    Serial.println(F("                     Time's Up -------- Game Over"));
    Serial.println();
    Serial.print(F("                   Your Score: "));
    Serial.print(score);
    Serial.print(F("     High Score: "));
    Serial.println(highScore);
    Serial.println();
  }
}

// gameMode 6
void viewHighScore() {
  mc = millis();
  EEPROM.get(eeAddress, highScore);
  const unsigned long mcTime = 11000;
  if (mc - lastMc >= mcTime) {
    lastMc = mc;
    Serial.println();
    Serial.print(F("                            High Score: "));
    Serial.println(highScore);
    Serial.println();
  }
}

void credits() {
  Serial.println(F("Blast 'Em!"));
  Serial.println(F("by Hallowed31"));
  delay(1000);
  for (int i = 0; i < 3; i++) {
    Serial.println();
    delay(200);
  }
}

void biosPrompt() {
  Serial.println(F("Type in B and use button"));
  Serial.println(F("at prompt to enter BIOS"));
  delay(1500);
  for (int i = 0; i < 3; i++) {
    Serial.println();
    delay(200);
  }
}

// gameMode 5
void viewRawTargetReadings() {
  unsigned long readInterval = 1000;
  if (gameTime - lastGameTime >= readInterval) {
    lastGameTime = gameTime;
    target1Value = analogRead(targetOne);
    target2Value = analogRead(targetTwo);
    Serial.print(F("ADC readings :  Target 1 (A0) value is "));
    Serial.print(target1Value);  // for checking Photoresistor  input
    Serial.print(F("  Target 2 (A1) value is "));
    Serial.println(target2Value);
    Serial.println();
  }
}

void resetHighScore() {
  highScore = 0;
  EEPROM.put(eeAddress, highScore);
  while (1);
}

void cycleServos() {
  servoOne.write(90);
  servoTwo.write(90);
  delay(250);
  servoOne.write(0);
  servoTwo.write(0);
}

and the excellent video on state machines by YouTuber NesHacker, that is like a super power in game design once you understand it:

PS: all you need is a pushbutton in the circuit to see the while loop holding a game state in action (think insert coin thing on arcade machine).

PPS: The note on CoolTerm settings at bottom of notes is just for me. I like the look of CoolTerm fonts and stuff for simple game screens.

Good, but a scientific/academic test should be made on the same conditions: the first problem was related to how a constant is automatically converted and this result how it'd be compared to an unsigned long value. The latter was just another "automatic" casting made when assignign an unsigned long value to an unsigned int, and this is the case where a wrong cast must be taken in consderation.

May I suggest you following:

  • read the "Blink Without Delay" example
  • close the example and now write it on your own
  • If it doesn't compile/ if you can't fix it - check the example
  • Repeat this as long as it takes that you can write this example sketch by heart

I guess you will need not more than two cheats to revisit the Blink Without Delay example.
At least this way worked for me.

@smurfeous
Coming back to our OP's QUESTION FOR A MINUTE,
I note that in IDE1.8.19 the line
#define start 12
has the word start hilighted; I believe this means it is a keyword, and may already have a value, or may be given one.

Just for S&G, rename 'start' 'startpin' throughout your code, and see if that changes the behaviour.

Good point.

My advice is always to follow the standards, so constants or symbols should ever be all caps, and this alone could avoid that ambiguity. But for pin definitions I also use a prefix like "P_" to let me remember the usage, so either:
#define P_START 12
or (better):
const byte P_START = 12;
Those two simple rules can solve any potential collision, together with a more standard and readable code.

Well, what's the purpose of this definition?

#define uint8 unsigned char 
uint8 flag =0; uint8 b1State,b2State,b3State,b4State =0; 

There's no need to define a symbol for an unsigned char, neither to make the code celarer not to save code size.
And, except for "flag", those "uint8" (aka "byte") variables are there to store the button state values from a digitalRead(), returning an "int" but they seem to be better configured as "bool" to reflect the button state, pressed or not. The same for "flag", it looks like to have 0 or 1 values only, so why not define it "bool" too, and use "true" or "false"?...
Together with always comparing digitalRead() values with LOW and HIGH to make it clear we're talking about a digital state?

This is a better and clearer way to do that:

...
bool flag,b1State,b2State,b3State,b4State = false; 
...
  // With pullups, if LOW the button has been pressed. This way the state variables 
  // will be "true" when the user activated that button 
  b1State = (digitalRead(P_PLAYER1) == LOW); 
  b2State = (digitalRead(P_PLAYER2) == LOW);
  if(b1State) // Player 1 pressed the button
  {    
    flag =false; 
    digitalWrite(relay1 ,LOW);  // Release the magnet holding one side down
...

I tend to be a tad more explicit, hence startPin, etc. Helps me when I look at the code months down the road.

In this case, the OP's code needs a more wholesale renaming to get all the variables(and constants) named for their purpose,

const int startPin = 12;

would be highly preferred, for a start.

Yep, as it's just a conventional naming, anyone can choose different solutions, no constraint is applied so you're right.
But as a "standard" convention, since capitalization is (almost) mandatory for constants and symbols, your "const int startPin = 12;" should then be "const int STARTPIN = 12;", but IMO this makes the suffix less readable/recognizable. Furthermore pin declarations don't require an "int": a "byte" is enough and is a good habit because it saves some (precious) bytes on boards with little RAM like UNOs.

Said that, in general I prefer a "const byte P_START = 12;" because I can immediately recognize it is a constant, with a pin definition, and the prefix is more compact than "PIN_" (not bad anyway).

Or, in case the author wanted to emphasize that we are talking about buttons:
const byte B_START = 12;
or, more clearly:
const byte BTN_START = 12;

But it's just a matter of conventions.

Well, it might, depends on the optimization I think. In this case,
declared as an int, but value less than 128,
the item won't be allocated space in ram, as the compiler can just put the constant inline in the code (using an address, and storing it somewhere, just takes more work).

Since it's putting a constant inline in the code, and the constant is < 128, I suspect it becomes a single byte. So, no advantage to declaring it as a byte, or a uint8_t, or whatever.
But, I'd have to actually compile it, then look at the output, to be sure that's the case.

But you're right, much of this is down to convention, and conventions are up to the programmer to adhere to religiously, nod to occasionally, or ignore flagrantly. I'm generally too lax for some, too anal for others, and happy in my own sandbox.

I abhore the ALLCAPS approach, I suspect because I don't like my code shouting at me. Again, personal preference

More particularly, a button might be wired to
const int startButtonPin,
and have values in the code of
bool startButtonState
and
bool startButtonOldState

But, YMMV around the loop. The above is mostly for the consideration of our OP.

I use ALLCAPS for #defines only (and avoid them as far as possible).
A constant variable is still a variable and therefore I name it in camelCase.
by the way ... for pins i usually use uint8_t as type because in most platforms digitalWrite accepts an uint8_t (and I don't use microcontrollers with more than 256 pins).

Personally, my only use of defines is for conditional inclusion of test conditions, test output, etc., such as
#define SERIALDEBUG
...
#ifdef SERIALDEBUG
//fill the serial buffer with useful diagnostics

Otherwise, I find the constant approach more useful, because the compiler knows more about such a construct, and might keep me out of trouble more easily.

But, YMMV.

When i looked up how to use neopixles that was the example i was given. The author of that tutorial said arrays were too hard to explain.

All the things to do with the buttons I didn't actually program myself. I got it here:

https://www.ijprems.com/uploadedfiles/paper//issue_11_november_2024/36892/final/fin_ijprems1732419036.pdf

I built the code in Wokwi so you can see how it works
button code

I took code from different sources and mashed them together. Most of the mashing parts work. There is one bit of mashing together that doesn't function the same way as the other parts.

Here is the project so far
Perfection