35. Number Guessing Game

Objective

The task is to build a number-guessing game using a microcontroller, where the system randomly selects a number between 1 and 100, and the user guesses it through a serial terminal (e.g., PuTTY or Arduino IDE).

Requirements

  • The microcontroller compares each user's guess with the randomly selected number and provides feedback — “Too High”“Too Low”, or “Correct!” — until the correct number is guessed.
  • The total number of attempts taken by the user is recorded as their score and stored under a Leaderboard.
  • The game should retain the leaderboard data even after a reset or power loss; this is achieved by using EEPROM for data storage.

Why Use EEPROM?

  • EEPROM (Electrically Erasable Programmable Read-Only Memory) is a type of non-volatile memory that preserves stored data even when power is removed.
  • It is ideal for this application as it allows the system to store player scores and maintain a persistent leaderboard across power cycles and resets.

This task can be implemented with the following microcontrollers:

  1. ESP32
  2. Arduino UNO

We are using the ESP32 DevKitC v4 development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “ESP32 Dev Module” as the board to ensure correct settings and compatibility.

We are going to use EEPROM to implement this task, but ESP32 does not have a real EEPROM chip inside. Instead, part of its Flash memory is used to simulate EEPROM behaviour.

Hardware Connection

Connect the ESP32  development board to your computer using a USB cable.

Code

#include <EEPROM.h>

// EEPROM Emulation Configuration
#define EEPROM_SIZE 128  // Using 128 bytes of flash (can be adjusted)

// Game Constants
const int HISTORY_ADDR = 0;  // Starting address for score history
const int MAX_HISTORY = 10;  // Track last 10 scores
const int MAX_NUMBER = 100;  // Max number to guess
int randomNumber;            // Randomly generated number
int guesses;                 // Number of guesses in the current session

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

  // Initialize EEPROM emulation with specified size
  EEPROM.begin(EEPROM_SIZE);

  // Seed random number generator
  randomSeed(analogRead(34));
  randomNumber = random(1, MAX_NUMBER + 1);
  guesses = 0;

  Serial.println("\nWelcome to the Number Guessing Game!");
  Serial.println("Guess the number between 1 and 100.");
  Serial.println();
}

void loop() {
  if (Serial.available()) {
    int guess = Serial.parseInt();

    if (guess == 0) {
      // Handling newline/carriage return
      while (Serial.available()) Serial.read();  // Clear buffer
      return;
    } else if (guess < 1 || guess > MAX_NUMBER) {
      Serial.println("Invalid input. Please guess a number between 1 and 100.");
      while (Serial.available()) Serial.read();  // Clear buffer
      return;
    }

    guesses++;
    if (guess < randomNumber) {
      Serial.println("Too Low!");
    } else if (guess > randomNumber) {
      Serial.println("Too High!");
    } else {
      Serial.print("Correct! You guessed it in ");
      Serial.print(guesses);
      Serial.println(" attempts!");

      saveScore(guesses);
      displayLeaderboard(guesses);
      displayHistory();

      Serial.println("Game Over! Resetting...");
      delay(2000);
      resetGame();
    }
    while (Serial.available()) Serial.read();  // Clear buffer
  }
}

void saveScore(int score) {
  // Update history log by shifting older scores
  for (int i = (MAX_HISTORY - 1); i > 0; i--) {
    EEPROM.write(HISTORY_ADDR + i, EEPROM.read(HISTORY_ADDR + (i - 1)));
  }
  // Save the latest score
  EEPROM.write(HISTORY_ADDR, score);

  // Commit changes to flash (important!)
  EEPROM.commit();

  Serial.println("Saving your score...");
}

void displayLeaderboard(int currentScore) {
  Serial.println("\nLeaderboard:");

  // Fetch and sort the scores
  int scores[MAX_HISTORY];
  int validScores = 0;
  for (int i = 0; i < MAX_HISTORY; i++) {
    int score = EEPROM.read(HISTORY_ADDR + i);
    if (score > 0 && score != 255) {  // Ignore invalid or uninitialized scores
      scores[validScores++] = score;
    }
  }

  // Sort scores for ranking
  for (int i = 0; i < validScores - 1; i++) {
    for (int j = i + 1; j < validScores; j++) {
      if (scores[j] < scores[i]) {  // Bubble Sort Algorithm
        int temp = scores[i];
        scores[i] = scores[j];
        scores[j] = temp;
      }
    }
  }

  // Display sorted leaderboard with "You" tag for the current score
  bool currentScoreTagged = false;
  for (int i = 0; i < validScores; i++) {
    Serial.print(i + 1);
    Serial.print(". ");
    Serial.print(scores[i]);
    Serial.print(" Guesses");
    if (!currentScoreTagged && scores[i] == currentScore) {
      Serial.print(" (You)");
      currentScoreTagged = true;
    }
    Serial.println();
  }

  if (validScores == 0) {
    Serial.println("No scores available.");
  } else if (!currentScoreTagged) {
    Serial.println("\nYour score is not in the top 10.");
  }
}

void displayHistory() {
  Serial.println("\nLast 10 Scores:");
  for (int i = 0; i < MAX_HISTORY; i++) {
    int score = EEPROM.read(HISTORY_ADDR + i);
    if (score > 0 && score != 255) {
      Serial.print(score);
      Serial.print(" Guesses");
    } else {
      Serial.print("-");
    }
    if (i < MAX_HISTORY - 1) Serial.print(", ");
  }
  Serial.println();
}

void resetGame() {
  randomNumber = random(1, MAX_NUMBER + 1);
  guesses = 0;
  Serial.println("\nNew game started! Guess the number between 1 and 100.");
}

Code Explanation

  • setup();
    • Runs once at startup. Initialises serial communication, emulates a 128-byte EEPROM in flash memory, creates a target random number (1–100), resets guesses, and displays the game intro.
  • loop();
    • Continuously waits for user input from Serial. Reads the number with Serial.parseInt(), checks if it’s within 1–100, and increments the guess count.
    • Compares the guess with the target: prints “Too Low”, “Too High”, or if correct, saves the score, shows the leaderboard and history, waits 2 seconds, and resets the game.
    • Clears the Serial buffer before the next round.
  • saveScore(int score);
    • Stores the latest score in EEPROM by shifting old scores down and writing the new one at address 0. Commits changes to Flash using EEPROM.commit().
  • displayLeaderboard(int currentScore);
    • Reads the last 10 scores from EEPROM, filters out invalid values, sorts them in ascending order, and prints a ranked list with the current score marked as “You”.
  • displayHistory();
    • Prints the most recent 10 scores in stored order, showing each valid score or “–” if empty.
  • resetGame();
    • Generates a new random number between 1 and 100, resets the guess counter, and displays a “New Game Started” message.
  • EEPROM.begin(size);
    • Allocates the defined amount of Flash memory as a virtual EEPROM. Must be called before any EEPROM access.
  • EEPROM.write(address, value);
    • Writes one byte of data to the specified address in EEPROM memory (requires commit() to save).
  • EEPROM.read(address);
    • Reads one byte from the given EEPROM address and returns its value.
  • EEPROM.commit();
    • Writes all pending EEPROM data from RAM to Flash memory permanently.
  • randomSeed(analogRead(34)); / random(1, MAX_NUMBER + 1);
    • Seeds the random number generator and produces a random number between 1 and 100 (inclusive).
    • In Arduino (and C/C++ in general), random() (or its lower-level version rand()) is a pseudo-random number generator (PRNG).
    • A PRNG doesn’t generate truly random numbers — it produces a deterministic sequence based on a starting value called the seed.
    • If the seed is the same every time, the sequence of random numbers will also be the same every time, so to make sure the seed is different every time, we used the given approach.

We are using the Arduino UNO development board and programming it using the Arduino IDE.

  • Before uploading, make sure to select “Arduino UNO” as the board to ensure correct settings and compatibility.

Hardware Connection

Connect the Arduino UNO  development board to your computer using a USB cable.

Firmware Implementation

We are going to use EEPROM.h library to implement this task. In Arduino UNO, there is a separate EEPROM chip available of 1KB.

Code

#include <EEPROM.h>


// EEPROM Structure
const int HISTORY_ADDR = 0;      // Starting address for score history
const int MAX_HISTORY = 10;      // Track last 10 scores
const int MAX_NUMBER = 100;      // Max number to guess
int randomNumber;                // Randomly generated number
int guesses;                     // Number of guesses in the current session


void setup() {
 Serial.begin(9600);
 randomSeed(analogRead(A0));    // Seed for random number generation
 randomNumber = random(1, MAX_NUMBER + 1);
 guesses = 0;


 Serial.println("Welcome to the Number Guessing Game!");
 Serial.println("Guess the number between 1 and 100.");
 Serial.println();
}


void loop() {
 if (Serial.available()) {
   int guess = Serial.parseInt();


   if(guess == 0)
   {
    /*
     * Handling the NL and CR here
     */
     return;
   }
   else if (guess < 1 || guess > MAX_NUMBER) {
     Serial.println("Invalid input. Please guess a number between 1 and 100.");
     return;
   }


   guesses++;
   if (guess < randomNumber) {
     Serial.println("Too Low!");
   } else if (guess > randomNumber) {
     Serial.println("Too High!");
   } else {
     Serial.print("Correct! You guessed it in ");
     Serial.print(guesses);
     Serial.println(" attempts!");


     saveScore(guesses);
     displayLeaderboard(guesses);
     displayHistory();


     Serial.println("Game Over! Resetting...");
     delay(2000);
     resetGame();
   }
 }
}


void saveScore(int score) {
 // Update history log by shifting older scores
 for (int i = (MAX_HISTORY - 1); i > 0; i--) {
   EEPROM.write(HISTORY_ADDR + i, EEPROM.read(HISTORY_ADDR + (i - 1)));
 }
 // Save the latest score
 EEPROM.write(HISTORY_ADDR, score);


 Serial.println("Saving your score...");
}


void displayLeaderboard(int currentScore) {
 Serial.println("\nLeaderboard:");


 // Fetch and sort the scores
 int scores[MAX_HISTORY];
 int validScores = 0;
 for (int i = 0; i < MAX_HISTORY; i++) {
   int score = EEPROM.read(HISTORY_ADDR + i);
   if (score > 0 && score != 255) { // Ignore invalid or uninitialized scores
     scores[validScores++] = score;
   }
 }


 // Sort scores for ranking
 for (int i = 0; i < validScores - 1; i++) {
   for (int j = i + 1; j < validScores; j++) {
     if (scores[j] < scores[i]) { // Bubble Sort Algorithm
       int temp = scores[i];
       scores[i] = scores[j];
       scores[j] = temp;
     }
   }
 }


 // Display sorted leaderboard with "You" tag for the current score
 bool currentScoreTagged = false;
 for (int i = 0; i < validScores; i++) {
   Serial.print(i + 1);
   Serial.print(". ");
   Serial.print(scores[i]);
   Serial.print(" Guesses");
   if (!currentScoreTagged && scores[i] == currentScore) {
     Serial.print(" (You)");
     currentScoreTagged = true;
   }
   Serial.println();
 }


 if (validScores == 0) {
   Serial.println("No scores available.");
 } else if (!currentScoreTagged) {
   Serial.println("\nYour score is not in the top 10.");
 }
}


void displayHistory() {
 Serial.println("\nLast 10 Scores:");
 for (int i = 0; i < MAX_HISTORY; i++) {
   int score = EEPROM.read(HISTORY_ADDR + i);
   if (score > 0 && score != 255) {
     Serial.print(score);
     Serial.print(" Guesses");
   } else {
     Serial.print("-");
   }
   if (i < MAX_HISTORY - 1) Serial.print(", ");
 }
 Serial.println();
}


void resetGame() {
 randomNumber = random(1, MAX_NUMBER + 1);
 guesses = 0;
 Serial.println("New game started! Guess the number between 1 and 100.");
}


Code Explanation

  • setup();
    • Initialises serial communication, seeds the random generator using analogRead(A0), creates a random target number (1–100), resets the guess counter, and prints the welcome message.
  • loop();
    • Continuously checks for user input via Serial.
       Reads the number using Serial.parseInt(), ignores blank input, and validates if it’s between 1–100.
       If valid, increments the guess count and compares it with the target number:
    • Prints “Too Low!” or “Too High!”
    • If correct, it shows the total attempts, saves the score to EEPROM, displays the leaderboard and history, waits 2 seconds, and resets the game.
  • saveScore(int score);
    • Shifts older scores down in EEPROM and writes the latest score at address 0, keeping the last 10 results saved permanently.
  • displayLeaderboard(int currentScore);
    • Reads the 10 stored scores, removes invalid ones, sorts them (lowest = best), and prints them in rank order.
    • Marks the current game score as “(You)”.
    • If your score isn’t found in the sorted top 10 (e.g., due to corruption), it prints “Your score is not in the top 10.”
  • displayHistory();
    • Shows the most recent 10 saved scores in stored order, printing “–” for empty entries.
  • resetGame();
    • Generates a new random number between 1 and 100, resets the guess counter, and prints “New game started!”
  • EEPROM.read() / EEPROM.write();
    • Used to read or write one byte of data (score) at a specific EEPROM address. Data remains even after power off.
  • randomSeed(analogRead(A0)); / random(1, MAX_NUMBER + 1);
    • Seeds and generates a pseudo-random number between 1 and 100.
       The seed ensures a new random sequence each time the board powers up.

OUTPUT