The Minesweeper game in 100 lines of JavaScript




Let's bring the 1989 Minesweeper game back to life! Actually the game is much older, but it gained worldwide fame after it was bundled with Windows 3.1. It probably led to millions of hours of productivity wasted when people played it at work. If you're younger than 30, let me teach you the rules. If you're older than 30, let me remind you the rules, since at our age memory no longer serves us well:

The board consists of rows and columns of tiles, some of which contain a mine. Initially all tiles are covered. The point of the game is to uncover all the tiles except the ones containing the mines. When you click a tile, its content is revealed. If it's a mine, you lose. Otherwise, the tile will show the sum of mines in the eight neighboring tiles. Blank means zero. Based on the revealed numbers, you calculate which of the neighboring tiles contain mines, mark them with a flag and click on the safe ones to reveal them. You repeat the process until all safe tiles are revealed. Right-clicking on a tile toggles between flag, question mark and hidden. You place a flag when you are sure the tile has a mine. This reduces the count of mines remaining. A question mark is placed if you suspect a mine might be there, but you're not certain. This allows you to simulate different scenarios - you can place a question mark and then verify if the neighboring tiles have the correct numbers.

Play the game by clicking the tiles on top of the page!

The algorithm





Coding this game in JavaScript is a lot of fun, since it is a relatively short and easy algorithm, but at the same time quite interesting. The mechanism of uncovering all tiles with zero value is a good exercise in recursive functions - the function calls itself multiple times.

We will create three arrays:
1. "board", which will contain: 'mine' for mine or a number 0 thru 8 which is the number of mines in the eight neighboring tiles.
2. "tile", which will consist of dynamically created image (IMG) objects, on which the player will click. The image can be:

0.png thru 8.png - number of mines in neighboring tiles (blank means zero)

hidden.png - covered tile

flag.png - flag

question.png - question mark

These two are only shown after the game is finished:

mine.png - a mine

misplaced.png - a place where a flag was incorrectly placed



3. "picture", which will contain on of the following strings: hidden, flag and question so that we don't have to get the image name and strip the '.png' ending each time we need to check what the tile is showing

Because JavaScript does not directly support two-dimensional arrays, we will create an array of arrays (lines 13-19). For example the main array 'board' will consist of 5 elements representing rows of the matrix. Each of those elements will also be an array, whose elements represent an actual tile.

First, in the 'init' function, we'll create the tiles and place them on then screen. Then we'll randomly place the mines.
When the player left clicks on a covered tile, we'll reveal its contents. If the value of the tile equals zero (which means none of the neighboring tiles have a mine), we'll also reveal all the surrounding tiles. If any of the surrounding tiles' value is zero, we'll reveal the tiles surrounding that tile... And so on and so on... The function 'reveal' will call itself recursively, until all the neighboring zero tiles are revealed. That's probably the most interesting part of this code.

The game ends when the player clicks on a tile that contains a mine (lose) or all non-mine tiles are revealed (win). In either case we display all the mines.

Enjoy sweeping!

The code



<html>
<body oncontextmenu='return false;' >
<br><br><br>
<label id='status'></label>
 
<script>
const rows = 4;
const columns = 20;
let minesremainingrevealed;
let status = document.getElementById('status');
status.addEventListener('click', init)
 
let board = new Array(rows);
let picture = new Array(rows);
let tile = new Array(rows);
for (let i = 0i < board.lengthi++) {
  board[i] = new Array(columns);
  picture[i] = new Array(columns);
  tile[i] = new Array(columns)
}
 
init();
 
function check(rowcolumn) {
  if (column >= 0 && row >= 0 && column < columns && row < rows)
    return board[row][column];
}
 
function init() {
  mines = 5;
  remaining = mines;
  revealed = 0;
  status.innerHTML = 'Click on the tiles to reveal them';
  for (let row = 0row < rowsrow++)
    for (let column = 0column < columnscolumn++) {
      let index = row * columns + column;
      tile[row][column] = document.createElement('img');
      tile[row][column].src = 'hidden.png';
      tile[row][column].style = 'position:absolute;height:30px; width: 30px';
      tile[row][column].style.top = 150 + row * 30;
      tile[row][column].style.left = 50 + column * 30;
      tile[row][column].addEventListener('mousedown', click);
      tile[row][column].id = index;
      document.body.appendChild(tile[row][column]);
      picture[row][column] = 'hidden';
      board[row][column] = '';
    }
 
  let placed = 0;
  while (placed < mines) {
    let column = Math.floor(Math.random() * columns);
    let row = Math.floor(Math.random() * rows);
 
    if (board[row][column] != 'mine') {
      board[row][column] = 'mine';
      placed++;
    }
  } 
 
  for (let column = 0column < columnscolumn++)
    for (let row = 0row < rowsrow++) {
      if (check(rowcolumn) != 'mine') {
        board[row][column] =
          ((check(row + 1column) == 'mine') | 0) +
          ((check(row + 1column - 1) == 'mine') | 0) +
          ((check(row + 1column + 1) == 'mine') | 0) +
          ((check(row - 1column) == 'mine') | 0) +
          ((check(row - 1column - 1) == 'mine') | 0) +
          ((check(row - 1column + 1) == 'mine') | 0) +
          ((check(rowcolumn - 1) == 'mine') | 0) +
          ((check(rowcolumn + 1) == 'mine') | 0);
      }
    }
}
 
function click(event) {
  let source = event.target;
  let id = source.id;
  let row = Math.floor(id / columns);
  let column = id % columns;
 
  if (event.which == 3) {
    switch (picture[row][column]) {
      case 'hidden':
        tile[row][column].src = 'flag.png';
        remaining--;
        picture[row][column] = 'flag';
        break;
      case 'flag':
        tile[row][column].src = 'question.png';
        remaining++;
        picture[row][column] = 'question';
        break;
      case 'question':
        tile[row][column].src = 'hidden.png';
        picture[row][column] = 'hidden';
        break;
    }
    event.preventDefault();
  }
  status.innerHTML = 'Mines remaining: ' + remaining;
 
  if (event.which == 1 && picture[row][column] != 'flag') {
    if (board[row][column] == 'mine') {
      for (let row = 0row < rowsrow++)
        for (let column = 0column < columnscolumn++) {
          if (board[row][column] == 'mine') {
            tile[row][column].src = 'mine.png';
          }
          if (board[row][column] != 'mine' && picture[row][column] == 'flag') {
            tile[row][column].src = 'misplaced.png';
          }
        }
      status.innerHTML = 'GAME OVER<br><br>Click here to restart';
    } else
    if (picture[row][column] == 'hidden') reveal(rowcolumn);
  }
 
  if (revealed == rows * columns - mines)
    status.innerHTML = 'YOU WIN!<br><br>Click here to restart';
}
 
function reveal(rowcolumn) {
  tile[row][column].src = board[row][column] + '.png';
  if (board[row][column] != 'mine' && picture[row][column] == 'hidden')
    revealed++;
  picture[row][column] = board[row][column];
 
  if (board[row][column] == 0) {
    if (column > 0 && picture[row][column - 1] == 'hidden') reveal(rowcolumn - 1);
    if (column < (columns - 1) && picture[row][+column + 1] == 'hidden') reveal(row, +column + 1);
    if (row < (rows - 1) && picture[+row + 1][column] == 'hidden') reveal(+row + 1column);
    if (row > 0 && picture[row - 1][column] == 'hidden') reveal(row - 1column);
    if (column > 0 && row > 0 && picture[row - 1][column - 1] == 'hidden') reveal(row - 1column - 1);
    if (column > 0 && row < (rows - 1) && picture[+row + 1][column - 1] == 'hidden') reveal(+row + 1column - 1);
    if (column < (columns - 1) && row < (rows - 1) && picture[+row + 1][+column + 1] == 'hidden') reveal(+row + 1, +column + 1);
    if (column < (columns - 1) && row > 0 && picture[row - 1][+column + 1] == 'hidden') reveal(row - 1, +column + 1);
  }
}
 
</script>
</body></html>



Check out these programming tutorials:

JavaScript:

Optical illusion (18 lines)

Oldschool fire effect (20 lines)

Animated fractal (32 lines)

Physics engine for beginners

Starfield (21 lines)

8-bit style sine text scroller (18 lines)

Yin Yang with a twist (4 circles and 20 lines)

Interactive animated sprites

Your first program in JavaScript: you need 5 minutes and a notepad


Fractals in Excel

Python in Blender 3d:

Domino effect (10 lines)


Wrecking ball effect (14 lines)

3d fractal in Blender Python