I created a simple minesweeper game as I am trying to get my hands dirty in JavaScript. I have tried to use the oops while doing this.
I am posting 3 files:
- index.html
- minesweeper.js
- minesweeper.css
Also:
- It does not have 2 major features - timer and flagging.
- You may find the UI not-so-pleasing.
I have hosted the game here.
It would be great if I can receive some critical comments about the code in general, code structure (mainly JavaScript) and best practices I should have followed.
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Minesweeper</title>
<link rel="stylesheet" type="text/css" href="minesweeper.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<script src="minesweeper.js"></script>
</head>
<body>
<div id="title">MineSweeper</div>
<div id="dashboard" class="controls">
<div id="help"></div>
<div id="git"></div>
<ul id="difficulty">
<li id="easy">Easy</li>
<li id="reg" class="selected">Reg</li>
<li id="hard">Hard</li>
</ul>
<div id="feedback">
<span id="value">5</span>
<i class="fa fa-bomb"></i>
</div>
</div>
<div id="board"></div>
<div id="new-game" class="controls">Play again?</div>
<script type="text/javascript">
var board;
$(document).ready(function () {
newGame('reg');
$('#new-game').hide();
$('#difficulty li').click(function (eventObject) {
$('#difficulty li').removeClass('selected');
$(this).addClass('selected');
var difficulty = $(this).attr('id');
newGame(difficulty);
$('#new-game').hide();
});
$('#new-game').click(function (eventObject) {
var difficulty = $('#difficulty li.selected').attr('id');
newGame(difficulty);
$('#new-game').hide();
});
});
/*
This is equivalent to-
$(function() {
});
*/
</script>
</body>
</html>
minesweeper.js
// Start New Game
function newGame(difficulty) {
// On selection of mode, create corresponding grids.
switch (difficulty) {
case 'easy':
board = new Board(6,8);
break;
case 'hard':
board = new Board(12, 15);
break;
case 'reg':
default:
board = new Board(8, 10);
break;
}
board.render();
board.gameOver = false;
$('.space').click(function (eventObject) {
board.click(eventObject.target);
});
return board;
}
// Board Object
function Board(row, col){
this.row = row;
this.col = col;
this.spaces = [];
this.gameOver = false;
this.spacesCleared = 0;
this.bombCount = 0;
this.click = function (target_elem) {
var row = $(target_elem).attr("data-row");
var col = $(target_elem).attr("data-col");
if (this.gameOver === true) {
return;
}
if (this.spaces[row - 1][col - 1].explored == true) {
return;
}
if (this.spaces[row - 1][col - 1].holds == -1) {
this.explode();
} else if (this.spaces[row - 1][col - 1].holds == 0) {
this.clear(row - 1, col - 1);
uncoverSurroundings.call(this, row - 1, col - 1);
} else {
this.clear(row - 1, col - 1);
}
}
this.render = function() {
var spaces = "";
for (i = 1; i <= row; i++) {
for (j = 1; j <= col; j++) {
spaces = spaces.concat('<div class="space" data-row="' + i + '" data-col="' + j + '"> </div>');
}
spaces = spaces.concat('<br />');
}
$('#board').empty();
$('#board').append(spaces);
}
this.explode = function() {
for (i = 0; i < this.row; i++) {
for (j = 0; j < this.col; j++) {
if (this.spaces[i][j].holds == -1) {
var dom_target = 'div[data-row="' + (i + 1) + '"][data-col="' + (j + 1) + '"]';
$(dom_target).addClass('bomb');
$(dom_target).html('<i class="fa fa-bomb"></i>');
}
}
}
this.gameOver = true;
$('#new-game').show();
}
var numBombNear = function(row, col) {
var sum = 0;
if (this.spaces[row][col].holds == -1) {
return -1;
}
sum += valueAt.call(this, row - 1, col - 1) + valueAt.call(this, row - 1, col) + valueAt.call(this, row - 1, col + 1)
+ valueAt.call(this, row, col - 1) + valueAt.call(this, row, col + 1)
+ valueAt.call(this, row + 1, col - 1) + valueAt.call(this, row + 1, col) + valueAt.call(this, row + 1, col + 1);
return sum;
}
function valueAt(row, col) {
if (row < 0 || row >= this.row || col < 0 || col >= this.col) {
return 0;
} else if(this.spaces[row][col].holds == -1){
return 1;
} else {
return 0;
}
}
//Initializing the Object
if (this.spaces !== undefined) {
this.spaces = new Array(this.row);
for (i = 0; i < this.row; i++) {
this.spaces[i] = new Array(this.col);
for (j = 0; j < this.col; j++) {
this.spaces[i][j] = new Space(false, 0);
}
}
var min = 1;
var max = this.row * this.col;
this.bombCount = Math.round((Math.random() * ((max / 2) - min) + (min)));
$('#value').html(this.bombCount);
for (i = 0; i < this.bombCount; i++) {
var bombIndex = Math.round(Math.random() * (max - 1));
var x = Math.floor(bombIndex / this.col);
var y = bombIndex % this.col;
this.spaces[x][y] = new Space(false, -1);
}
for (i = 0; i < this.row; i++) {
for (j = 0; j < this.col; j++) {
this.spaces[i][j].holds = numBombNear.call(this, i, j);
}
}
}
this.clear = function (row, col) {
var dom_target = 'div[data-row="' + (row + 1) + '"][data-col="' + (col + 1) + '"]';
$(dom_target).addClass('safe');
if (this.spaces[row][col].holds > 0) {
$(dom_target).text(this.spaces[row][col].holds);
} else {
$(dom_target).html(' ');
}
checkAllCellsExplored.call(this);
this.spacesCleared++;
this.spaces[row][col].explored = true;
}
function checkAllCellsExplored(){
if (this.row * this.col - this.spacesCleared == this.bombCount) {
for (i = 0; i < this.row; i++) {
for (j = 0; j < this.col; j++) {
if (this.spaces[i][j].holds == -1) {
var bomb_target = 'div[data-row="' + (i + 1) + '"][data-col="' + (j + 1) + '"]';
$(bomb_target).html('<i class="fa fa-smile-o"></i>');
this.gameOver = true;
$('#new-game').show();
}
}
}
}
}
function uncoverSurroundings(row, col) {
checkSpace.call(this, row - 1, col - 1); checkSpace.call(this, row - 1, col); checkSpace.call(this, row - 1, col + 1);
checkSpace.call(this, row, col - 1); checkSpace.call(this, row, col + 1);
checkSpace.call(this, row + 1, col - 1); checkSpace.call(this, row + 1, col); checkSpace.call(this, row + 1, col + 1);
checkAllCellsExplored.call(this);
}
function checkSpace(row, col) {
if (row < 0 || row >= this.row || col < 0 || col >= this.col || this.spaces[row][col].explored == true) {
return;
} else if (this.spaces[row][col].holds >= 0) {
this.clear(row, col);
if (this.spaces[row][col].holds == 0) {
uncoverSurroundings.call(this, row, col);
return;
}
}
}
}
//Space Object
function Space(explored, holds){
this.explored = explored;
this.holds = holds;
}
minesweeper.css
body {
margin: 0;
padding: 0;
border: none;
background-color: none;
color: #333;
font-family: Arial, Helvetica, sans-serif, 'Arial', sans-serif;
font-size: 16px;
text-align: center; /* Important */
}
/* Dashboard and Controls */
#dashboard {
text-align: left;
margin-top: 30px;
}
.controls {
width: 438px; /* (numCols * 44) - 2 */
margin: 0 auto;
font-weight: bold;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
position: relative;
}
#difficulty {
list-style: none;
padding: 0;
padding-left: 10px;
display: inline-block;
}
#difficulty li {
background-color: #bbb;
color: #fff;
cursor: pointer;
display : inline-block;
font-size: 14px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
text-align: center;
padding: 6px 8px;
}
#difficulty li:hover { background-color: #333; }
#difficulty li.selected { background-color: #777; }
#board {
min-width: 440px;
margin-bottom: 50px;
}
.space {
background-color: #bbb;
display: inline-block;
border: 2px solid none;
margin: 2px;
cursor: pointer;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
width: 38px;
height: 38px;
font-size: 22px;
line-height: 38px;
}
.safe {
background-color: #eee;
color: #777;
}
.bomb {
color: #333;
}
.space .fa { line-height: 40px; }
.space .fa-bomb { font-size: 24px; }
.space .fa-smile-o { font-size: 26px; }
.space:hover { background-color: #777; }
#new-game {
width: 420px;
background-color: #777;
color: #fff;
font-size: 38px;
padding: 15px 0;
cursor: pointer;
}
#new-game:hover { background-color: #333; }
#feedback {
display: inline-block;
font-size: 30px;
padding-top: 4px;
right: 0;
position:absolute;
}
#title {
margin-top: 10px;
font-size: 30px;
color: #777;
}