Simple Javascript Minesweeper (Free Code Download)

Welcome to a tutorial on how to create a simple Minesweeper game with Javascript. Yes, Minesweeper is yet another of those classic games. There are plenty of “free-to-play” Minesweepers online, so here’s a slightly different one for those who want to learn – A minimal “no graphics” version, so you get to study the mechanics. Read on!

 

 

TABLE OF CONTENTS

 

JAVASCRIPT MINESWEEPER

All right, let us now get into the mechanics of the Minesweeper game.

 

JAVASCRIPT MINESWEEPER DEMO

 

 

PART 1) THE HTML

1-mine.html
<!-- GAME WRAPPER -->
<div id="mine-wrap"></div>

Yes, not kidding, a single <div> is all we need to get started. The “cells and mines” will be generated with Javascript, which will look something like this.

<div id="mine-wrap">
  <div class="cell"></div>
  <div class="cell"></div>
  <div class="cell"></div>
  ...
</div>

 

PART 2) THE CSS

2-mine.css
/* (A) MINESWEEPER WRAPPER */
#mine-wrap {
  display: flex;
  flex-wrap: wrap;
}
 
/* (B) MINESWEEPER CELLS */
#mine-wrap .cell {
  box-sizing: border-box;
  background: #ddd;
  border: 1px solid #ccc;
  font-size: 40px;
  text-align: center;
  cursor: pointer;
}
#mine-wrap .cell.mark { background: #fea; }
#mine-wrap .cell.reveal { background: #f7f7f7; }
#mine-wrap .cell.boom { background: #f00; }

Well, just some simple CSS styles to draw the board:

  • display: flex and flex-wrap: wrap are the ones that do the cells layout magic.
  • The width of each cell is automatically calculated in Javascript.
  • But otherwise, .cell controls the style of the cells.
    • .mark is attached when we right-click to mark a cell.
    • .reveal is a cell that is opened.
    • .boom when the player opens a cell with a mine in it…

 

 

PART 3) GAME PROPERTIES

3-mine.js
var mine = {
  // (A) PROPERTIES
  // (A1) GAME SETTINGS
  total : 10,  // total number of mines
  height : 10, // number of rows
  width : 5,   // number of columns
 
  // (A2) GAME FLAGS
  board : [], // current game board
  rCell : 0   // number of remaining hidden cells
}

The Javascript is pretty brutal, so let’s split it into sections.

  • First, Captain Obvious – The var mine object contains all the minesweeper mechanics.
  • (A1) The game settings should be self-explanatory.
    • mine.total The total number of mines to set.
    • mine.height mine.width The number of rows and columns the game board has.
  • (A2) To keep track of the game process, we need to define some game flags.
    • mine.board An array to store every cell – If it is an empty cell, has a mine, and the number of adjacent mines.
    • mine.rCell The remaining number of hidden cells. If mine.rCell == mine.total, then the player has successfully found all mines and wins.

 

 

PART 4) INITIALIZE/RESET GAME

3-mine.js
// (B) RESET & INITIALIZE GAME
reset : () => {
  // (B1) RESET GAME FLAGS
  mine.board = [];
  mine.rCell = mine.height * mine.width;
 
  // (B2) GET + RESET HTML WRAPPER
  let wrap = document.getElementById("mine-wrap"),
  cssWidth = 100 / mine.width;
  wrap.innerHTML = "";
 
  // (B3) LOOP THROUGH ROWS & COLUMNS - GENERATE CELLS
  for (let row=0; row<mine.height; row++) {
    mine.board.push([]); 
    for (let col=0; col<mine.width; col++) {
      // (B3-1) ADD CELL TO MINE.BOARD[]
      mine.board[row].push({ 
        r : false, // cell is revealed?
        x : false, // cell is marked?
        m : false, // cell has a mine?
        a : 0,     // number of mines in adjacent cells
        c : document.createElement("div") // html reference
      });
 
      // (B3-2) ADD CELL TO HTML <DIV>
      let cell = mine.board[row][col].c;
      cell.classList.add("cell");
      cell.id = "mine-" + row + "-" + col;
      cell.dataset.row = row;
      cell.dataset.col = col;
      cell.onclick = () => mine.open(cell);
      cell.oncontextmenu = e => {
        e.preventDefault();
        mine.mark(cell);
      };
      cell.style.width = cssWidth + "%";
      cell.innerHTML = "&nbsp;";
      wrap.appendChild(cell);
    }
  }
 
  // (B4) RANDOMLY LAY MINES
  let mRow = mine.height - 1,
      mCol = mine.width - 1,
      mToLay = mine.total;
  while (mToLay > 0) {
    let row = Math.floor(Math.random() * mRow);
    let col = Math.floor(Math.random() * mCol);
    if (!mine.board[row][col].m) {
      mine.board[row][col].m = true;
      // CHEAT - SHOW MINE ON THE BOARD
      // mine.board[row][col].c.innerHTML = "*";
      mToLay--;
    }
  }
 
  // (B5) CALCULATE NUMBER OF ADJACENT MINES FOR EACH CELL
  // LOOP THROUGH ROW-BY-ROW
  for (let row=0; row<mine.height; row++) {
    let lastRow = row - 1,
        nextRow = row + 1;
    if (nextRow == mine.height) { nextRow = -1; }
 
    // LOOP THROUGH CELLS OF EACH ROW
    for (let col=0; col<mine.width; col++) {
      let lastCol = col - 1,
          nextCol = col + 1;
      if (nextCol == mine.width) { nextCol = -1; }
 
      // CALCULATE ONLY IF CELL DOES NOT CONTAIN MINE
      if (!mine.board[row][col].m) {
        // ADD NUMBER OF MINES IN LAST ROW
        if (lastRow != -1) {
          if (lastCol != -1) { if (mine.board[lastRow][lastCol].m) { mine.board[row][col].a++; } }
          if (mine.board[lastRow][col].m) { mine.board[row][col].a++; }
          if (nextCol != -1) { if (mine.board[lastRow][nextCol].m) { mine.board[row][col].a++; } }
        }
 
        // ADD NUMBER OF MINES IN CURRENT ROW
        if (lastCol != -1) { if (mine.board[row][lastCol].m) { mine.board[row][col].a++; } }
        if (nextCol != -1) { if (mine.board[row][nextCol].m) { mine.board[row][col].a++; } }
 
        // ADD NUMBER OF MINES IN NEXT ROW
        if (nextRow != -1) {
          if (lastCol != -1) { if (mine.board[nextRow][lastCol].m) { mine.board[row][col].a++; } }
          if (mine.board[nextRow][col].m) { mine.board[row][col].a++; }
          if (nextCol != -1) { if (mine.board[nextRow][nextCol].m) { mine.board[row][col].a++; } }
        }
      }
 
      // CHEAT - SHOW NUMBER OF ADJACENT MINES ON BOARD
      // if (mine.board[row][col].a != 0) { mine.board[row][col].c.innerHTML = mine.board[row][col].a ; }
    }
  }
}
window.addEventListener("DOMContentLoaded", mine.reset);

Not going to explain line-by-line, but what mine.reset() does is essentially do the calculations and set the game up:

  • (B1) Reset mine.board to an empty array and mine.rCell to WIDTH X HEIGHT.
  • (B2) Empties out the HTML container <div id="mine-wrap">, calculate the width of each cell.
  • (B3) Loop through mine.width and mine.height to generate the cells. Take note of how the data for the cells are kept in the format of mine.board[ROW][COL] = {DATA}, and the corresponding HTML cell is <div class="cell" data-row="ROW" data-col="COL">.
  • (B4) Randomly place the mines.
  • (B5) Lastly, loop through the cells again, calculate the number of adjacent mines each cell has.

 

 

PART 5) RIGHT-CLICK TO MARK CELL

3-mine.js
// (C) RIGHT CLICK TO MARK CELL
mark : cell => {
  // (C1) GET COORDS OF SELECTED CELL
  let row = cell.dataset.row,
      col = cell.dataset.col;

  // (C2) MARK/UNMARK ONLY IF CELL IS STILL HIDDEN
  if (!mine.board[row][col].r) {
    cell.classList.toggle("mark");
    mine.board[row][col].x = !mine.board[row][col].x;
  }
}

This is straightforward – mine.mark() is called when the player right-clicks on a cell. Basically, we just toggle the mark CSS class on the cell.

 

 

PART 6) LEFT-CLICK TO OPEN CELL

3-mine.js
// (D) LEFT CLICK TO OPEN CELL
open : cell => {
  // (D1) GET COORDS OF SELECTED CELL
  let row = cell.dataset.row,
      col = cell.dataset.col;

  // (D2) SELECTED CELL HAS MINE = LOSE
  if (!mine.board[row][col].x && mine.board[row][col].m) {
    cell.classList.add("boom");
    setTimeout(() => {
      alert("Opps. You lost.");
      mine.reset();
    }, 1);
  }
 
  // (D3) REVEAL SELECTED CELL + ALL EMPTY ADJACENT CELLS
  else {
    // (D3-1) FLAGS - WHICH CELLS SHOULD WE AUTOMATICALLY REVEAL?
    let toReveal = [], // all cells to reveal
        toCheck = [],  // all cells to check
        checked = [];  // all cell that have already been checked
    for (let i=0; i<mine.height; i++) { checked.push({}); }
    toCheck.push([row, col]);
 
    // (D3-2) LOOP & CHECK - ADD CELLS TO REVEAL
    while (toCheck.length>0) {
      // CURRENT "CHECK CELL" COORDINATES
      let thisRow = parseInt(toCheck[0][0]),
          thisCol = parseInt(toCheck[0][1]);

      // HAS MINE, ALREADY REVEALED, MARKED - DO NOTHING
      if (mine.board[thisRow][thisCol].m || mine.board[thisRow][thisCol].r || mine.board[thisRow][thisCol].x) {}
      else {
        // ADD CELL TO REVEAL
        if (!checked[thisRow][thisCol]) { toReveal.push([thisRow, thisCol]); }
 
        // CELL DOES NOT HAVE ADJ MINES - CHECK NEIGHBOURS
        if (mine.board[thisRow][thisCol].a == 0) {
          // PREVIOUS & NEXT CELL COORDINATES
          let lastRow = thisRow - 1,
              nextRow = thisRow + 1,
              lastCol = thisCol - 1,
              nextCol = thisCol + 1;
          if (nextRow == mine.height) { nextRow = -1; }
          if (nextCol == mine.width) { nextCol = -1; }
 
          // LAST ROW
          if (lastRow != -1) {
            if (lastCol != -1 && !checked[lastRow][lastCol]) { toCheck.push([lastRow, lastCol]); }
            if (!checked[lastRow][thisCol]) { toCheck.push([lastRow, thisCol]); }
            if (nextCol != -1 && !checked[lastRow][nextCol]) { toCheck.push([lastRow, nextCol]); }
          }
 
          // CURRENT ROW
          if (lastCol != -1 && !checked[thisRow][lastCol]) { toCheck.push([thisRow, lastCol]); }
          if (nextCol != -1 && !checked[thisRow][nextCol]) { toCheck.push([thisRow, nextCol]); }
 
          // NEXT ROW
          if (nextRow != -1) {
            if (lastCol != -1 && !checked[nextRow][lastCol]) { toCheck.push([nextRow, lastCol]); }
            if (!checked[nextRow][thisCol]) { toCheck.push([nextRow, thisCol]); }
            if (nextCol != -1 && !checked[nextRow][nextCol]) { toCheck.push([nextRow, nextCol]); }
          }
        }
      }
 
      // MOVE ON - CHECK NEXT CELL
      checked[thisRow][thisCol] = true;
      toCheck.shift();
    }
 
    // (D3-3) AUTOMATICALLY REVEAL CELLS (IF ANY)
    if (toReveal.length > 0) {
      for (let cell of toReveal) {
        let thisRow = parseInt(cell[0]);
        let thisCol = parseInt(cell[1]);
        mine.board[thisRow][thisCol].r = true;
        if (mine.board[thisRow][thisCol].a != 0) { 
          mine.board[thisRow][thisCol].c.innerHTML = mine.board[thisRow][thisCol].a;
        }
        mine.board[thisRow][thisCol].c.classList.add("reveal");
        mine.rCell = mine.rCell - 1;
      }
    }
 
    // (D3-4) NO CELLS LEFT TO OPEN - WIN!
    if (mine.rCell == mine.total) {
      alert("YOU WIN!");
      mine.reset();
    }
  }
}

Lastly, opening a cell also involves some grim calculations.

  • (D1) Get the coordinates of the selected cell (that is clicked on).
  • (D2) The player clicks on a mine and loses. Easy.
  • (D3) The not-so-easy part where the player clicks on an empty cell. Some heavy calculations are required to also automatically reveal all adjacent empty cells. If there are no more cells left to open mine.rCell == mine.total the player wins the game.

 

DOWNLOAD & NOTE

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

EXAMPLE CODE DOWNLOAD

Click here for the source code on GitHub gist, just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

EXTRA BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

COMPATIBILITY CHECKS

This example will work on all “Grade A” browsers.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

2 thoughts on “Simple Javascript Minesweeper (Free Code Download)”

  1. Synne Louise Lønnum

    Hi! 😀
    Was looking at your code and I believe I have noticed a bug. I am not that great at the game, so I might be completely wrong. If so, I apologize in advance 🙂 I have played through the game quite a few times, and I feel like the number of mines does not always correlate to the number in the cell. My friends and boyfriend have noticed the same.
    Otherwise, it’s an amazing code!
    Is this something you’ve spotted yourself? Would love to see the updated code if you are able to correct the bug!

    Have a great day!

    Kind regards,
    Synne

Leave a Comment

Your email address will not be published. Required fields are marked *