Build a Simple Next.js Interactive Number Guessing Game

Written by

in

Ever find yourself drawn to the thrill of a good guessing game? The anticipation, the strategic thinking, and the satisfying feeling of getting it right? Now, imagine creating that experience yourself, bringing it to life on the web using the power of Next.js. This tutorial will guide you, step-by-step, through building a fun and engaging Number Guessing Game. We’ll dive into the core concepts, from setting up your Next.js project to implementing the game logic, all while keeping things simple and beginner-friendly. By the end, you’ll not only have a functional game but also a solid understanding of how to build interactive web applications with Next.js.

Why Build a Number Guessing Game?

Creating a Number Guessing Game is more than just a fun project; it’s an excellent way to learn and practice essential web development skills. It allows you to:

  • Understand State Management: You’ll learn how to manage the game’s state, such as the secret number, the player’s guesses, and the game’s status (playing, won, lost).
  • Work with User Input: You’ll handle user input from a form, processing it to determine the outcome of each guess.
  • Implement Conditional Rendering: You’ll use conditional rendering to display different content based on the game’s state (e.g., showing the guess input, the result messages, or the game over screen).
  • Practice Component Composition: You’ll break down the game into reusable components, making your code more organized and maintainable.
  • Grasp Next.js Fundamentals: You’ll get hands-on experience with Next.js features like routing, server-side rendering (SSR), and static site generation (SSG) (although this project will primarily focus on client-side interactivity).

Plus, it’s a relatively small project, making it perfect for beginners to intermediate developers looking to expand their Next.js skills.

Prerequisites

Before we begin, make sure you have the following:

  • Node.js and npm (or yarn) installed: This is essential for running and managing your Next.js project.
  • A basic understanding of HTML, CSS, and JavaScript: Familiarity with these languages is necessary to follow along.
  • A code editor (like VS Code): This will help you write and manage your code efficiently.

Step-by-Step Guide to Building the Number Guessing Game

Step 1: Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app@latest number-guessing-game
cd number-guessing-game

This command creates a new Next.js project named “number-guessing-game”. Navigate into the project directory using the `cd` command.

Step 2: Project Structure and File Setup

Your project directory should look something like this:

number-guessing-game/
├── node_modules/
├── package.json
├── pages/
│   └── _app.js
│   └── index.js
├── public/
│   └── ...
├── .gitignore
├── next.config.js
└── README.md

The core of our application will reside in the `pages/` directory. `index.js` is the main page of our application. We’ll also use components, which we’ll create in a dedicated `components/` folder for better organization. Create a `components` folder in the root directory.

Step 3: Creating the Game Components

Let’s create the components that will make up our game. We’ll need at least three components:

  • GuessInput.js: This component will handle the user’s input for their guess.
  • GuessResult.js: This component will display feedback to the user based on their guess (too high, too low, or correct).
  • GameStatus.js: This component will display the game’s overall status (e.g., “Playing”, “Game Over”, or “You Won!”) and possibly a button to restart the game.

Create these files inside the `components` directory. Let’s start with `GuessInput.js`:

// components/GuessInput.js
import React, { useState } from 'react';

const GuessInput = ({ onGuess, gameOver }) => {
  const [guess, setGuess] = useState('');

  const handleChange = (event) => {
    setGuess(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (!gameOver) {
      const guessNumber = parseInt(guess, 10);
      if (!isNaN(guessNumber) && guessNumber >= 1 && guessNumber 
      <input
        type="number"
        value={guess}
        onChange={handleChange}
        disabled={gameOver}
        style={{ marginRight: '0.5rem' }}
      />
      <button type="submit" disabled={gameOver}>
        Guess
      </button>
      {gameOver && <span style={{marginLeft: '1rem'}}>Game Over!</span>}
    </form>
  );
};

export default GuessInput;

In `GuessInput.js`, we have:

  • A state variable `guess` to store the user’s input.
  • An `handleChange` function to update the `guess` state whenever the input value changes.
  • A `handleSubmit` function that is called when the form is submitted. It prevents the default form submission behavior, validates the input, and calls the `onGuess` prop function with the user’s guess.
  • The component renders an input field and a submit button. The input field is disabled when the game is over.

Next, let’s create `GuessResult.js`:


// components/GuessResult.js
import React from 'react';

const GuessResult = ({ result, guessesLeft }) => {
  let message = '';
  if (result === 'tooHigh') {
    message = 'Too high!';
  } else if (result === 'tooLow') {
    message = 'Too low!';
  } else if (result === 'correct') {
    message = 'Correct! You guessed it!';
  } else if (result === 'gameOver') {
      message = `Game Over! You ran out of guesses.`;
  } else {
      message = 'Make a guess!';
  }

  return (
    <div style={{ marginBottom: '1rem' }}>
      <p>{message} {result !== 'correct' && result !== 'gameOver' && `Guesses left: ${guessesLeft}`}</p>
    </div>
  );
};

export default GuessResult;

In `GuessResult.js`, we have:

  • The component receives a `result` prop, which indicates the outcome of the user’s guess (e.g., “tooHigh”, “tooLow”, “correct”, or “gameOver”). It also receives the `guessesLeft` prop.
  • It renders a message based on the `result` prop.

Finally, let’s create `GameStatus.js`:


// components/GameStatus.js
import React from 'react';

const GameStatus = ({ status, onRestart }) => {
  let message = '';
  if (status === 'playing') {
    message = 'Guess the number between 1 and 100!';
  } else if (status === 'won') {
    message = 'You won! Congratulations!';
  } else if (status === 'lost') {
    message = 'You lost! Try again.';
  }

  return (
    <div style={{ marginBottom: '1rem' }}>
      <p>{message}</p>
      {(status === 'won' || status === 'lost') && (
        <button onClick={onRestart}>Restart Game</button>
      )}
    </div>
  );
};

export default GameStatus;

In `GameStatus.js`, we have:

  • The component receives a `status` prop, which indicates the current state of the game (e.g., “playing”, “won”, or “lost”).
  • It renders a message based on the `status` prop.
  • It optionally renders a restart button if the game is over.

Step 4: Implementing the Game Logic in `index.js`

Now, let’s bring it all together in `pages/index.js`. This is where the core game logic will reside.


// pages/index.js
import React, { useState, useEffect } from 'react';
import GuessInput from '../components/GuessInput';
import GuessResult from '../components/GuessResult';
import GameStatus from '../components/GameStatus';

const randomNumber = () => Math.floor(Math.random() * 100) + 1;

const Index = () => {
  const [secretNumber, setSecretNumber] = useState(randomNumber());
  const [guesses, setGuesses] = useState([]);
  const [result, setResult] = useState('');
  const [guessesLeft, setGuessesLeft] = useState(10);
  const [gameStatus, setGameStatus] = useState('playing');

  useEffect(() => {
    if (guessesLeft === 0) {
      setResult('gameOver');
      setGameStatus('lost');
    }
  }, [guessesLeft]);

  const handleGuess = (guess) => {
    if (gameStatus !== 'playing') return;

    setGuesses([...guesses, guess]);

    if (guess === secretNumber) {
      setResult('correct');
      setGameStatus('won');
    } else if (guess > secretNumber) {
      setResult('tooHigh');
      setGuessesLeft(guessesLeft - 1);
    } else {
      setResult('tooLow');
      setGuessesLeft(guessesLeft - 1);
    }
  };

  const handleRestart = () => {
    setSecretNumber(randomNumber());
    setGuesses([]);
    setResult('');
    setGuessesLeft(10);
    setGameStatus('playing');
  };

  const gameOver = gameStatus !== 'playing';

  return (
    <div style={{ fontFamily: 'sans-serif', textAlign: 'center', padding: '20px' }}>
      <h2>Number Guessing Game</h2>
      <GameStatus status={gameStatus} onRestart={handleRestart} />
      <GuessResult result={result} guessesLeft={guessesLeft} />
      <GuessInput onGuess={handleGuess} gameOver={gameOver} />
      <p>Guesses: {guesses.join(', ')}</p>
    </div>
  );
};

export default Index;

Here’s a breakdown of what’s happening in `index.js`:

  • Importing Components: We import the components we created earlier (`GuessInput`, `GuessResult`, and `GameStatus`).
  • State Variables: We define several state variables using the `useState` hook to manage the game’s state:
    • `secretNumber`: The random number the player needs to guess.
    • `guesses`: An array to store the player’s guesses.
    • `result`: A string indicating the result of the last guess (“tooHigh”, “tooLow”, “correct”, or “gameOver”).
    • `guessesLeft`: The number of guesses remaining.
    • `gameStatus`: The current status of the game (“playing”, “won”, or “lost”).
  • `randomNumber()` Function: A helper function to generate a random number between 1 and 100.
  • `useEffect` Hook: This hook checks if `guessesLeft` has reached 0. If it has, it sets the `result` to “gameOver” and the `gameStatus` to “lost”.
  • `handleGuess()` Function: This function is called when the player submits a guess. It checks if the game is still playing, updates the `guesses` array, and compares the guess to the `secretNumber`. It then updates the `result`, and `guessesLeft` and `gameStatus` accordingly.
  • `handleRestart()` Function: This function resets the game to its initial state when the player wants to play again.
  • `gameOver` Variable: A derived variable to conveniently check if the game is over.
  • Rendering the UI: The component renders the `GameStatus`, `GuessResult`, and `GuessInput` components, passing in the necessary props. It also displays the player’s guesses.

Step 5: Styling (Optional but Recommended)

While the game will function without any styling, adding some CSS will make it look much better. You can add basic styling directly in the components using the `style` prop (as we’ve done for demonstration purposes) or, for more complex styling, create a CSS file (e.g., `styles.css`) in the `pages/` directory and import it into `index.js`.

Here’s an example of how you might add some basic styling to `index.js` (you can expand on this as you wish):


// pages/index.js
import React, { useState, useEffect } from 'react';
import GuessInput from '../components/GuessInput';
import GuessResult from '../components/GuessResult';
import GameStatus from '../components/GameStatus';

const randomNumber = () => Math.floor(Math.random() * 100) + 1;

const Index = () => {
  const [secretNumber, setSecretNumber] = useState(randomNumber());
  const [guesses, setGuesses] = useState([]);
  const [result, setResult] = useState('');
  const [guessesLeft, setGuessesLeft] = useState(10);
  const [gameStatus, setGameStatus] = useState('playing');

  useEffect(() => {
    if (guessesLeft === 0) {
      setResult('gameOver');
      setGameStatus('lost');
    }
  }, [guessesLeft]);

  const handleGuess = (guess) => {
    if (gameStatus !== 'playing') return;

    setGuesses([...guesses, guess]);

    if (guess === secretNumber) {
      setResult('correct');
      setGameStatus('won');
    } else if (guess > secretNumber) {
      setResult('tooHigh');
      setGuessesLeft(guessesLeft - 1);
    } else {
      setResult('tooLow');
      setGuessesLeft(guessesLeft - 1);
    }
  };

  const handleRestart = () => {
    setSecretNumber(randomNumber());
    setGuesses([]);
    setResult('');
    setGuessesLeft(10);
    setGameStatus('playing');
  };

  const gameOver = gameStatus !== 'playing';

  return (
    <div style={{ fontFamily: 'sans-serif', textAlign: 'center', padding: '20px', maxWidth: '500px', margin: '0 auto', border: '1px solid #ccc', borderRadius: '8px' }}>
      <h2 style={{ marginBottom: '1rem' }}>Number Guessing Game</h2>
      <GameStatus status={gameStatus} onRestart={handleRestart} />
      <GuessResult result={result} guessesLeft={guessesLeft} />
      <GuessInput onGuess={handleGuess} gameOver={gameOver} />
      <p>Guesses: {guesses.join(', ')}</p>
    </div>
  );
};

export default Index;

This example adds basic styling to center the game, add some padding, and a border. You can expand this to style the input fields, buttons, and result messages.

Step 6: Running Your Game

To run your game, open your terminal, navigate to your project directory (`number-guessing-game`), and run the following command:

npm run dev

or

yarn dev

This will start the development server. Open your web browser and go to `http://localhost:3000` to play your game!

Common Mistakes and How to Fix Them

Here are some common mistakes beginners make when building this type of game, along with solutions:

  • Incorrect Data Types: Forgetting to convert the user’s input from a string to a number using `parseInt()`. This will cause unexpected behavior when comparing the guess to the secret number.
    • Fix: Use `parseInt(guess, 10)` to convert the input string to an integer before comparison. The `10` specifies that the input is a base-10 number.
  • Not Handling Invalid Input: Not validating the user’s input to ensure it’s a number within the specified range (1-100). This can lead to errors and a poor user experience.
    • Fix: Add input validation in the `handleSubmit` function of `GuessInput.js` to check if the input is a valid number and within the correct range.
  • Incorrect State Updates: Incorrectly updating state variables, leading to unexpected game behavior. For example, forgetting to update `guessesLeft` after a wrong guess.
    • Fix: Double-check your state update logic to ensure that all relevant state variables are updated correctly after each guess.
  • Forgetting to Disable Input: Allowing the user to submit guesses after the game is over.
    • Fix: Disable the input field and guess button in the `GuessInput` component when the game is over, using the `gameOver` prop.
  • Ignoring Edge Cases: Not considering edge cases like the user entering a non-numeric value or entering a number outside the valid range.
    • Fix: Implement robust input validation to handle these edge cases gracefully. Provide clear feedback to the user about what went wrong.

Key Takeaways

This project provides a practical introduction to building interactive web applications with Next.js. You’ve learned how to:

  • Set up a Next.js project.
  • Create reusable components.
  • Manage state with the `useState` hook.
  • Handle user input.
  • Implement conditional rendering.
  • Structure your application for better organization.

You can expand this project further by adding features like:

  • Difficulty Levels: Allow the user to choose the range of numbers (e.g., 1-100, 1-1000).
  • Hint System: Provide hints to the user, such as whether their guess is even or odd.
  • Scorekeeping: Track the number of guesses it takes to win.
  • High Scores: Store and display high scores using local storage or a database.
  • Improved Styling: Make the game visually appealing.

Frequently Asked Questions (FAQ)

Q: How do I deploy this game online?

A: The easiest way to deploy your Next.js game is with Vercel, the platform created by the same team that built Next.js. Simply push your code to a Git repository (like GitHub), connect your repository to Vercel, and Vercel will automatically build and deploy your application.

Q: How can I add sounds to the game?

A: You can add sounds using the HTML5 `<audio>` element or by using a JavaScript library like Howler.js. You’ll need to include audio files (e.g., .mp3 or .wav) in your project and then play them at the appropriate times (e.g., when the user guesses correctly or incorrectly).

Q: How do I handle multiple players?

A: To handle multiple players, you’ll need to use a real-time communication technology like WebSockets or a service like Firebase Realtime Database or Supabase. This allows you to synchronize the game state across multiple devices.

Q: What if I want to use server-side rendering (SSR) for this game?

A: While this project focuses on client-side interactivity, you *could* incorporate SSR. For instance, you might use SSR to generate the initial random number on the server, although it’s not strictly necessary for this particular game. Next.js offers features like `getServerSideProps` for this purpose.

Q: How can I improve the game’s accessibility?

A: To improve accessibility, ensure your game is navigable using the keyboard. Use semantic HTML elements. Provide alt text for any images. Consider using ARIA attributes to provide additional information to screen readers. Ensure sufficient color contrast for text and other elements.

Building a Number Guessing Game with Next.js is a fantastic starting point for understanding how to create dynamic and engaging web applications. The combination of Next.js’s features, like its fast refresh and easy deployment, coupled with the core concepts of state management, user input handling, and component composition, provides a solid foundation for more complex projects. By tackling this project, you’ve not only created a fun game but have also equipped yourself with valuable skills that will serve you well in your web development journey. Keep experimenting, keep learning, and most importantly, keep building!