3
\$\begingroup\$

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:

  1. index.html
  2. minesweeper.js
  3. minesweeper.css

Also:

  1. It does not have 2 major features - timer and flagging.
  2. 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 + '">&nbsp;</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('&nbsp');
        }
        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;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.