Complete JavaScript Course Module 5: Beginner: Functions

Beginner Project: Build a Number Guessing Game

  • Beginner
  • Lesson 5.7
  • 8 min read
  • 23 of 76 in the course

In short

In this beginner JavaScript project, you will build a number guessing game step by step, using functions, conditions and variables in a real web page. What you will build The computer picks a secret number between 1 and 100. The player types a guess and clicks a button.

In this beginner JavaScript project, you will build a number guessing game step by step, using functions, conditions and variables in a real web page.

What you will build

The computer picks a secret number between 1 and 100. The player types a guess and clicks a button. The game says "too high" or "too low", counts the attempts, and shows a "Play again" button when the player wins.

You will use almost everything from the Beginner modules: variables, if/else, comparison with ===, type conversion with Number(), template literals, the ternary operator and functions. You will also meet three new tools that connect JavaScript to the page:

  • document.querySelector() finds an element on the page, like a search box for your HTML.
  • addEventListener() tells an element "when this happens (for example a click), run this function".
  • textContent reads or changes the text inside an element.

Note: These three are part of the DOM (Document Object Model), the way JavaScript sees and changes a web page. You will learn the DOM properly in the DOM module. For now, just follow along and focus on the logic.

Create a folder called guessing-game with two empty files: index.html and game.js.

Step 1: Build the HTML page

Put this into index.html. It has a heading, an input box for the guess, a "Check" button, a paragraph for messages, a paragraph for the attempts count, and a hidden "Play again" button.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Number Guessing Game</title>
</head>
<body>
  <main>
    <h1>Guess the Number</h1>
    <p>I am thinking of a number between 1 and 100. Can you find it?</p>

    <label for="guess-input">Your guess:</label>
    <input type="number" id="guess-input" min="1" max="100">
    <button id="guess-button">Check</button>

    <p id="message">Make your first guess!</p>
    <p id="attempts">Attempts: 0</p>

    <button id="restart-button" hidden>Play again</button>
  </main>

  <script src="game.js"></script>
</body>
</html>

Each element we need to control has an id, a unique name we will use to find it from JavaScript. The hidden attribute hides the restart button until the game is won. The <script> tag is at the end of the body, so the page is fully loaded before the script runs. Open the file in your browser: you will see the page, but clicking "Check" does nothing yet.

Step 2: Pick a secret number and find the elements

Now open game.js. First, we find the elements and store them in constants. Then we create the secret number and the attempts counter.

// Find the elements on the page
const guessInput = document.querySelector("#guess-input");
const guessButton = document.querySelector("#guess-button");
const message = document.querySelector("#message");
const attemptsText = document.querySelector("#attempts");
const restartButton = document.querySelector("#restart-button");

// Create a random whole number from 1 to 100
function getRandomNumber() {
  return Math.floor(Math.random() * 100) + 1;
}

// Game state
let secretNumber = getRandomNumber();
let attempts = 0;

console.log(`Secret number (for testing): ${secretNumber}`);
// Output (random, for example): Secret number (for testing): 42

document.querySelector("#guess-input") finds the element whose id is guess-input. The # means "id", the same as in CSS. Math.random() gives a decimal from 0 up to (but not including) 1. Multiplying by 100 gives 0 to 99.99..., Math.floor() rounds down to 0–99, and + 1 shifts it to 1–100.

We use let for secretNumber and attempts because they will change. The console.log line lets you peek at the answer while testing. Open the console (F12) and reload to see it.

Tip: Remove the "for testing" line when you share the game with friends, or they can cheat by opening the console.

Step 3: Check the guess and give higher/lower hints

Add this function below the code from Step 2. It runs every time the player clicks "Check".

function checkGuess() {
  const guess = Number(guessInput.value);

  // 1. Reject empty or invalid input (return early)
  if (guessInput.value === "" || !Number.isInteger(guess) || guess < 1 || guess > 100) {
    message.textContent = "Please enter a whole number from 1 to 100.";
    return;
  }

  // 2. Count this attempt
  attempts++;
  attemptsText.textContent = `Attempts: ${attempts}`;

  // 3. Compare with the secret number
  if (guess === secretNumber) {
    const word = attempts === 1 ? "attempt" : "attempts";
    message.textContent = `Correct! The number was ${secretNumber}. You got it in ${attempts} ${word}.`;
    endGame();
  } else if (guess < secretNumber) {
    message.textContent = `${guess} is too low. Try a higher number.`;
  } else {
    message.textContent = `${guess} is too high. Try a lower number.`;
  }

  // 4. Clear the box for the next guess
  guessInput.value = "";
  guessInput.focus();
}

guessButton.addEventListener("click", checkGuess);

Let's walk through it:

  • guessInput.value is what the player typed. It is always a string, even in a number input, so we convert it with Number().
  • The first if checks for bad input. Number.isInteger(guess) is true only for whole numbers, so 2.5 is rejected. If anything is wrong, we show a message and return early, so the attempt is not counted.
  • attempts++ adds one, and textContent updates the paragraph on the page.
  • The if/else if/else chain gives the hint. The ternary picks "attempt" or "attempts" so the sentence is correct.
  • guessInput.focus() puts the cursor back in the box so the player can type again right away.
  • The last line connects everything: when the button is clicked, run checkGuess.

Common mistake: Writing addEventListener("click", checkGuess()) with brackets. That calls the function once immediately, when the page loads, instead of on every click. Pass the function name without brackets.

Reload the page and play. The hints should work, but after you win, you can keep guessing. Let's fix that.

Step 4: End the game and add a restart button

Add these two functions and one more event listener at the bottom of game.js:

function endGame() {
  guessInput.disabled = true;
  guessButton.disabled = true;
  restartButton.hidden = false;
}

function restartGame() {
  secretNumber = getRandomNumber();
  attempts = 0;

  attemptsText.textContent = "Attempts: 0";
  message.textContent = "New game! Make your first guess.";

  guessInput.disabled = false;
  guessButton.disabled = false;
  restartButton.hidden = true;

  guessInput.value = "";
  guessInput.focus();

  console.log(`Secret number (for testing): ${secretNumber}`);
}

restartButton.addEventListener("click", restartGame);

endGame() turns off the input and the "Check" button by setting disabled to true, and shows the restart button by setting hidden to false. restartGame() does the opposite and resets the game state: a new secret number and zero attempts. Because secretNumber and attempts were created with let at the top level of the file, both functions can read and change them. In a small project like this, that shared state is fine; in bigger programs you would keep it more contained, as you learned in the Scope lesson.

Notice that checkGuess calls endGame(), even though endGame is written further down the file. That works because function declarations are hoisted, as you saw in the Functions lesson.

Complete final code

Here is the full project. index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Number Guessing Game</title>
</head>
<body>
  <main>
    <h1>Guess the Number</h1>
    <p>I am thinking of a number between 1 and 100. Can you find it?</p>

    <label for="guess-input">Your guess:</label>
    <input type="number" id="guess-input" min="1" max="100">
    <button id="guess-button">Check</button>

    <p id="message">Make your first guess!</p>
    <p id="attempts">Attempts: 0</p>

    <button id="restart-button" hidden>Play again</button>
  </main>

  <script src="game.js"></script>
</body>
</html>

And game.js:

// Find the elements on the page
const guessInput = document.querySelector("#guess-input");
const guessButton = document.querySelector("#guess-button");
const message = document.querySelector("#message");
const attemptsText = document.querySelector("#attempts");
const restartButton = document.querySelector("#restart-button");

// Create a random whole number from 1 to 100
function getRandomNumber() {
  return Math.floor(Math.random() * 100) + 1;
}

// Game state
let secretNumber = getRandomNumber();
let attempts = 0;

function checkGuess() {
  const guess = Number(guessInput.value);

  if (guessInput.value === "" || !Number.isInteger(guess) || guess < 1 || guess > 100) {
    message.textContent = "Please enter a whole number from 1 to 100.";
    return;
  }

  attempts++;
  attemptsText.textContent = `Attempts: ${attempts}`;

  if (guess === secretNumber) {
    const word = attempts === 1 ? "attempt" : "attempts";
    message.textContent = `Correct! The number was ${secretNumber}. You got it in ${attempts} ${word}.`;
    endGame();
  } else if (guess < secretNumber) {
    message.textContent = `${guess} is too low. Try a higher number.`;
  } else {
    message.textContent = `${guess} is too high. Try a lower number.`;
  }

  guessInput.value = "";
  guessInput.focus();
}

function endGame() {
  guessInput.disabled = true;
  guessButton.disabled = true;
  restartButton.hidden = false;
}

function restartGame() {
  secretNumber = getRandomNumber();
  attempts = 0;

  attemptsText.textContent = "Attempts: 0";
  message.textContent = "New game! Make your first guess.";

  guessInput.disabled = false;
  guessButton.disabled = false;
  restartButton.hidden = true;

  guessInput.value = "";
  guessInput.focus();
}

guessButton.addEventListener("click", checkGuess);
restartButton.addEventListener("click", restartGame);

Open index.html in your browser. A typical game looks like this:

You type 50  -> "50 is too high. Try a lower number."   Attempts: 1
You type 25  -> "25 is too low. Try a higher number."   Attempts: 2
You type 37  -> "Correct! The number was 37. You got it in 3 attempts."
The input and Check button turn off, and "Play again" appears.

Ideas to extend the game

Once it works, make it your own. Each idea uses only what you already know:

  • Limit the attempts: give the player 10 tries. If attempts reaches 10 without a win, show "Game over! The number was ..." and call endGame().
  • Best score: keep a let bestScore variable and update it when the player wins in fewer attempts than before.
  • Warmer/colder hints: if the guess is within 5 of the secret number, say "Very close!".
  • Difficulty levels: change the range to 1–10 (easy) or 1–1000 (hard) by giving getRandomNumber a max parameter with a default value of 100.
  • Press Enter to guess: listen for the "keydown" event on the input and call checkGuess() when event.key === "Enter". You will learn events in depth in the DOM module.

Practice exercise

Add the "Limit the attempts" feature. Create const maxAttempts = 10; near the top. Show the remaining tries in the attempts paragraph, for example Attempts: 3 (7 left), and end the game when no tries are left.

Hint: After attempts++, calculate maxAttempts - attempts. In the final else branches, check if (attempts === maxAttempts) after setting the hint, then change the message and call endGame(). Remember to keep the win check first, so a correct guess on the last try still wins.

Key takeaways

  • A real project combines small pieces: variables for state, functions for actions, and conditions for decisions.
  • document.querySelector() finds elements, addEventListener() runs a function on an event, and textContent changes text on the page.
  • Input values are always strings, so convert them with Number() and validate them before use.
  • Returning early keeps invalid input from being counted.
  • Splitting the game into checkGuess, endGame and restartGame makes the code easy to read and extend.