AngularJS Game Programming: Making Minesweeper (Part VII)

In the last post we added code to detect and tell the player when they have won. In this post we’re going to add code to detect when the player has lost.

This is actually actually a lot easier, just check if the post where they clicked has as mine. If it does do pretty much the same thing we did when the player won, which is set a flag / variable in the scope.

In this case we set hasLostMessageVisible to true. And just like we did when the user won we add the message to the HTML and use ngIf to show the message when hasLostMessageVisible is true.

To check if they clicked on a mine we update the uncoverSpot() function. From this:

$scope.uncoverSpot = function(spot) {
    spot.isCovered = false;
    
    if(hasWon($scope.minefield)) {
        $scope.isWinMessageVisible = true;
    }
};

To this:

$scope.uncoverSpot = function(spot) {
    spot.isCovered = false;
    
    if(spot.content == "mine") { // new
        $scope.hasLostMessageVisible = true; // new
    } else { // new
        if(hasWon($scope.minefield)) { // original code
            $scope.isWinMessageVisible = true;
        }
    } // new
};

And we add the message to the HTML:

<h3 ng-if="hasLostMessageVisible">You Lost!</h3>

You can play with this code in this JS Fiddle: http://jsfiddle.net/luisperezphd/cdtphbue/

There, now we have the core features of Minesweeper. You can now play the game and win and lose. It doesn’t yet have all the features that the orignal Windows Minesweeper had but it’s certainly a starting point.

I hope you enjoyed it.

AngularJS: Difference between Service vs Provider vs Factory

If you are searching for this it’s probably because you are trying to figure out which one is the right one for you to use. Or because you’ve come across the three of them and are trying to determine the difference because they seem similar.

If you think they are similar – you’re right. They are very similar. In fact, they are all the the same thing.

They are all providers. The factory and the service are just special cases of the provider, but you can accomplish everything you want using just provider. I’ll show you.

[Read more…]

AngularJS Game Programming: Making Minesweeper (Part VI)

angularjs-game-programming-making-minesweeper-blog-part-vi

Last time on “AngularJS Game Programming”: We added numbers to our minefield! Now a player can figure out where the mines are. In this post were going to keep track whether the player has won and notify them.

Win or Lose

We now have a complete minesweeper minefield. But it’s not a game until you can win or lose. Let’s add the code that checks for that. But first, let’s clearly define what it means to win.

A player wins when they uncover all the safe spots. Said another way, a player wins when the only uncovered spots left on the minefield all contain mines. If an uncovered spot contains anything other than a mine they haven’t won yet.

function hasWon(minefield) {
    for(var y = 0; y < 9; y++) {
        for(var x = 0; x < 9; x++) {
            var spot = getSpot(minefield, y, x);
            if(spot.isCovered && spot.content != "mine") {
                return false;
            }
        }
    }
    
    return true;
}

We need to check whether the player has won each time they uncover a block. To do that let’s change the ngClick we have on the td to call a function instead of what it is now spot.isCovered = false. That way we can easily add this additional logic.

Create a function called uncoverSpot() and move the current ngClick code there. Unlike our other functions this function has to be in the scope so that it can be called from the view.

So replace the code in the ngClick with ng-click="uncoverSpot(spot)", it should look like this:

<td ng-repeat="spot in row.spots" ng-click="uncoverSpot(spot)">

Update the controller to add this:

$scope.uncoverSpot = function(spot) {
    spot.isCovered = false;
};

Add the check to see if they won after the setting isCovered. If they win track it in a variable so that we can display a message to the user. Like this:

if(hasWon($scope.minefield)) {
    $scope.isWinMessageVisible = true;
}

Add the message to the HTML:

<h3 ng-if="isWinMessageVisible">You won!</h3>

Play with it at: http://jsfiddle.net/luisperezphd/UWSWy/

Next Time

In the next post we’re going to add the ability to add “lose” detection.

Part 7: Lose Detection

AngularJS Game Programming: Making Minesweeper (Part V)

angularjs-game-programming-making-minesweeper-blog-part-v

Last time on “AngularJS Game Programming”: We added mines! In this post we’re going to add numbers, doesn’t seem quite as cool as things that boom but it does get us that much closer to having our own complete version of minesweeper.

Numbers

So far so good. Now we let’s add the numbers. We do this by going through each spot, checking all the spots around it and counting the mines. Let’s start by writing the function to calculate the number for a single spot.

Only spots that are currently empty that have mines around them should be populate with a number, otherwise they should be left as “empty”. Spots that have a mine should be skipped.

When we count the number of mines around a spot we should be careful about going off the grid. For example if we’re checking the spot at the very top left, there are no spots to the left or above it. If we try to access it we will get an error.

Taking all that into account our function should look like this:

function calculateNumber(minefield, row, column) {
    var thisSpot = getSpot(minefield, row, column);
    
    // if this spot contains a mine then we can't place a number here
    if(thisSpot.content == "mine") {
        return;
    }
    
    var mineCount = 0;

    // check row above if this is not the first row
    if(row > 0) {
        // check column to the left if this is not the first column
        if(column > 0) {
            // get the spot above and to the left
            var spot = getSpot(minefield, row - 1, column - 1);
            if(spot.content == "mine") {
                mineCount++;
            }
        }

        // get the spot right above
        var spot = getSpot(minefield, row - 1, column);
        if(spot.content == "mine") {
            mineCount++;
        }

        // check column to the right if this is not the last column
        if(column < 8) {
            // get the spot above and to the right
            var spot = getSpot(minefield, row - 1, column + 1);
            if(spot.content == "mine") {
                mineCount++;
            }
        }
    }

    // check column to the left if this is not the first column
    if(column > 0) {
        // get the spot to the left
        var spot = getSpot(minefield, row, column - 1);
        if(spot.content == "mine") {
            mineCount++;
        }
    }
    
    // check column to the right if this is not the last column
    if(column < 8) {
        // get the spot to the right
        var spot = getSpot(minefield, row, column + 1);
        if(spot.content == "mine") {
            mineCount++;
        }
    }

    // check row below if this is not the last row
    if(row < 8) {
        // check column to the left if this is not the first column
        if(column > 0) {
            // get the spot below and to the left
            var spot = getSpot(minefield, row + 1, column - 1);
            if(spot.content == "mine") {
                mineCount++;
            }
        }

        // get the spot right below
        var spot = getSpot(minefield, row + 1, column);
        if(spot.content == "mine") {
            mineCount++;
        }

        // check column to the right if this is not the last column
        if(column < 8) {
            // get the spot below and to the right
            var spot = getSpot(minefield, row + 1, column + 1);
            if(spot.content == "mine") {
                mineCount++;
            }
        }
    }
    
    if(mineCount > 0) {
        thisSpot.content = mineCount;
    }
}

Now that we have the function we need to call it for every spot on the map, which we can do with this function:

function calculateAllNumbers(minefield) {
    for(var y = 0; y < 9; y++) {
        for(var x = 0; x < 9; x++) {
            calculateNumber(minefield, x, y);
        }
    }
}

Now we update createMinefield() to call calculateAllNumbers(), and that only leaves the display. The display is very similar to the code we already have, we just need to add one for every number (1 through 8).

<td ng-repeat="spot in row.spots" ng-click="spot.isRevealed = true">
    <img ng-if="!spot.isRevealed" src="block.png">
    <img ng-if="spot.isRevealed && spot.content == 'empty'" src="empty.png">
    <img ng-if="spot.isRevealed && spot.content == 'mine'" src="mine.png">
    <img ng-if="spot.isRevealed && spot.content == 1" src="number-1.png">
    <img ng-if="spot.isRevealed && spot.content == 2" src="number-2.png">
    <img ng-if="spot.isRevealed && spot.content == 3" src="number-3.png">
    <img ng-if="spot.isRevealed && spot.content == 4" src="number-4.png">
    <img ng-if="spot.isRevealed && spot.content == 5" src="number-5.png">
    <img ng-if="spot.isRevealed && spot.content == 6" src="number-6.png">
    <img ng-if="spot.isRevealed && spot.content == 7" src="number-7.png">
    <img ng-if="spot.isRevealed && spot.content == 8" src="number-8.png">
</td>

If you click on each block you should get something like this:

minesweeper-minefield-numbers

Play with it here: http://jsfiddle.net/luisperezphd/m3R3W/

Next Time

In the next post we’re going to add logic to detect whether you’ve won.

Next Post: Detecting if the player won.