DEV Community

Cover image for How to Build a Minesweeper CLI Game in Node.js (Part 1/3)
Razvan
Razvan

Posted on

How to Build a Minesweeper CLI Game in Node.js (Part 1/3)

In this tutorial, you’ll implement the classic Minesweeper game in its command-line interface version using nothing but Node, standard input/output, and a few clean algorithms.

Here’s a preview of what you’ll build:

Minesweeper CLI Game Preview

In this part, you’ll learn how to:

  1. Parse the grid size from the CLI
  2. Generate a 2D grid and place random mines
  3. Count adjacent mines using neighbor deltas

We’ll start from an empty file and finish in Part 3 with a fully functional game you can extend.

Ready? Let’s build!

Game Rules

At the start of the game, a fixed number of mines are randomly placed across a grid of size N by N, where each mine occupies a hidden square. The goal is to uncover all the safe squares on the grid without revealing a mine.

When you reveal a square:

  1. If it contains a mine → you lose (game over).
  2. If it does not contain a mine, it shows a number (0–8).

That number is the count of mines in the 8 surrounding squares (up, down, left, right, and diagonals). For example, if a square shows 2, exactly two of its neighbors contain mines.

You can also “flag” a square if you suspect it contains a mine.

You win either by:

  1. Revealing all safe squares.
  2. Correctly flagging all mines.

Step 0: Create the script

Let’s start by creating a new file named minesweeper.js and open it in a text editor.

$ code minesweeper.js
Enter fullscreen mode Exit fullscreen mode

Step 1: Create an IIFE entry point

Within this file, let’s create the script’s entry point using an Immediately Invoked Function Expression (IIFE).

(() => {
  //
})();
Enter fullscreen mode Exit fullscreen mode

Step 2: Create an empty grid

Above the IIFE, let’s define a new function named createGrid() that will be used to generate a new square grid of an arbitrary size.

function createGrid() {
  //
}
Enter fullscreen mode Exit fullscreen mode

Within the function’s body, let’s declare a constant named size used to define the size of the grid and initialize with 6.

function createGrid() {
  const size = 6;
}
Enter fullscreen mode Exit fullscreen mode

Next, let’s declare a constant named grid used to represent the two-dimensional grid, and initialize it with an array of arrays, where each element of the subarrays contains an object that represents a square, where:

  • mine indicates if it holds a mine
  • adjacent indicates how many mines are around
  • revealed indicates if the square was revealed
  • flagged indicates if the square was flagged

And let's return the grid array.

function createGrid() {
  const size = 6;
  const grid = Array.from({ length: size }, () => Array.from({ length: size }, () => ({
    mine: false,
    adjacent: 0,
    revealed: false,
    flagged: false
  })));

  return grid;
}
Enter fullscreen mode Exit fullscreen mode

Finally, within the entry point, let’s declare a new constant named grid and initialize it with the value returned by the createGrid() function.

function createGrid() {
  /* ... */
}

(() => {
  const grid = createGrid();
})();
Enter fullscreen mode Exit fullscreen mode

Note that in order the keep the code and the changes made to it as readable as possible, I’ll often comment already written parts using either one of the following expressions:

  • // ...
  • /* ... */

Step 3: Parse the grid size from the CLI

Let’s define a new function named parseGridSize() used to dynamically determine and set the size of the grid from the command-line interface.

function parseGridSize() {
  //
}
Enter fullscreen mode Exit fullscreen mode

Within the function’s body, let’s declare a constant named size and initialize it with the value of the 3rd command-line argument of the script converted to an integer in base 10 using the parseInt() function.

function parseGridSize() {
  const size = parseInt(process.argv[2], 10);
}
Enter fullscreen mode Exit fullscreen mode

Next, let’s return the converted value if it is a valid number comprised between 3 and 10 included, or 6 otherwise.

function parseGridSize() {
  const size = parseInt(process.argv[2], 10);
  return isNaN(size) || size < 3 || size > 10 ? 6 : size;
}
Enter fullscreen mode Exit fullscreen mode

Then, let’s update the createGrid() function by:

  1. Changing its signature to include a new parameter named size.
  2. Removing the size constant from its body.
  3. Invoking the createGrid() function using the value returned by the parseGridSize() function as argument.
function parseGridSize() {
  // ...
}

function createGrid(size) {
  const grid = Array.from(/* ... */);

  return grid;
}

(() => {
  const size = parseGridSize();
  const grid = createGrid(size);
})();
Enter fullscreen mode Exit fullscreen mode

Which now allows us to execute the minesweeper.js script with an additional (and optional) argument .

$ node minesweeper.js 9
Enter fullscreen mode Exit fullscreen mode

Step 4: Place random mines in the grid

Generate random number in a range

To be able to generate a random position in the grid, let’s first define a new helper function named generateRandomNumber() that returns a random integer in a range of values.

function generateRandomNumber(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
Enter fullscreen mode Exit fullscreen mode

Place mines in the grid

Let’s now define another function named placeMines() that randomly places N mines in the grid, where N is an integer equal to the size of the grid.

function placeMines(grid) {
  //
}
Enter fullscreen mode Exit fullscreen mode

Within its body, let’s define two constants named min and max and initialize them with the lowest and the highest defined indexes in the grid array.

function placeMines(grid) {
  const min = 0;
  const max = grid.length - 1;
}
Enter fullscreen mode Exit fullscreen mode

Let’s declare a variable named mines to count the number of mines placed in the grid and initialize it with 0.

function placeMines(grid) {
  const min = 0;
  const max = grid.length - 1;
  let mines = 0;
}
Enter fullscreen mode Exit fullscreen mode

And let’s use a while loop to:

  1. Generate at each iteration a random row and column number.
  2. Check if the square at this position doesn’t already hold a mine.
  3. Place a mine at this position.
  4. Update the mines counter to keep track of how many mines have been placed.
function placeMines(grid) {
  const min = 0;
  const max = grid.length - 1;
  let mines = 0;

  while (mines < grid.length) {
    let row = generateRandomNumber(min, max);
    let col = generateRandomNumber(min, max);
    let square = grid[row][col];

    if (!square.mine) {
      square.mine = true;
      mines++;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Update the grid

Let’s update the createGrid() function and invoke the placeMines() function within it to update the newly generated grid.

function createGrid(size) {
  const grid = Array.from(/* ... */);

  placeMines(grid);
  return grid;
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Compute and place numbers in the grid

Compute adjacent mines

Let’s define a new function named placeNumbers() that will for each square of the grid count how many adjacent squares hold a mine and update the value of its adjacent property accordingly.

function placeNumbers(grid) {
  //
}
Enter fullscreen mode Exit fullscreen mode

Within its body, let’s set up two nested loops that will iterate on each square of each row, column by column, and skip the squares that hold a mine.

function placeNumbers(grid) {
  for (let row = 0 ; row < grid.length ; row++) {
    for (let col = 0 ; col < grid.length ; col++) {
      let square = grid[row][col];

      if (square.mine) {
        continue;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let’s declare a constant named deltas that contains a list of all the relative positions of the squares around.

function placeNumbers(grid) {
  const deltas = [
    [-1, -1], // top-left
    [-1, 0],  // top
    [-1, 1],  // top-right
    [0, 1],   // right
    [1, 1],   // bottom-right
    [1, 0],   // bottom
    [1, -1],  // bottom-left
    [0, -1],  // left
  ];

  for (let row = 0 ; row < grid.length ; row++) {
    for (let col = 0 ; col < grid.length ; col++) {
      let square = grid[row][col];

      if (square.mine) {
        continue;
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let’s declare a for...of loop that iterates on each element of the deltas array, and checks if the position of the square minus or plus the value of the current delta is out of the bounds of the grid.

function placeNumbers(grid) {
  const deltas = [[-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];

  for (let row = 0 ; row < grid.length ; row++) {
    for (let col = 0 ; col < grid.length ; col++) {
      let square = grid[row][col];

      if (square.mine) {
        continue;
      }

      for (const [deltaX, deltaY] of deltas) {
        if (row + deltaX < 0 || row + deltaX > grid.length - 1 || col + deltaY < 0 || col + deltaY > grid.length - 1) {
          continue;
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Let’s declare a variable named count that will keep track of the mines held in the adjacent squares present within the bounds of the grid, and update the adjacent property of the current square once the for...of loop has terminated.

function placeNumbers(grid) {
  const deltas = [[-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];

  for (let row = 0 ; row < grid.length ; row++) {
    for (let col = 0 ; col < grid.length ; col++) {
      let square = grid[row][col];

      if (square.mine) {
        continue;
      }

      let count = 0;

      for (const [deltaX, deltaY] of deltas) {
        if (row + deltaX < 0 || row + deltaX > grid.length - 1 || col + deltaY < 0 || col + deltaY > grid.length - 1) {
          continue;
        }
        if (grid[row + deltaX][col + deltaY].mine) {
          count++;
        }
      }

      square.adjacent = count;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Update the grid

Let’s update the createGrid() function and invoke the placeNumbers() function within it to update the grid.

function createGrid(size) {
  const grid = Array.from(/* ... */);

  placeMines(grid);
  placeNumbers(grid);
  return grid;
}
Enter fullscreen mode Exit fullscreen mode

Final thoughts

Congratulations — you’ve built the full Minesweeper model: a square grid, randomly placed mines, and correctly computed adjacency numbers for every safe cell.

In Part 2, you’ll turn this model into a playable terminal game: render a readable grid, parse commands, update state, and handle win/lose conditions.

Thank you for reading and see you in Part 2.

What's next?

👉 New to programming? Check out the Learn Backend Skill Surge Challenge — a beginner challenge designed to teach you how to use the CLI, run JavaScript/Node.js code, and think like a developer in just 21 days.

👉 Ready to go pro with backend development? Join the Learn Backend Mastery Program — the complete zero-to-hero roadmap to become a professional Node.js backend developer in just 12 months.

Top comments (0)