A couple of my coworkers and myself have started a sort of bootcamp little challenge recently with the intention to become better JavaScript developers fast.

We talked a lot about how to improve our skills in the area, probably because we have been using Knockout extensively at work lately which has opened up our views of what can and cannot be done in JavaScript these days.

I’ve spent many years creating web apps that are rendered from the server side, read MVC in any language that you can imagine, but specially in .NET. Up until this point that has been the norm in the field, however with Chrome and Firefox pushing the limits of what browsers can do I’ve started to notice all these frameworks that allow you to create web apps that work entirely in the browser, like gmail for example.

Well, to keep up with the astonishing speed these things are moving at, we decided to each of us create a version of the classic Minesweeper, entirely made in JavaScript and HTML. No SPA frameworks, just vanilla JS.. we allowed jQuery because it will take us so much longer if we don’t, but the intention is to not even need jQuery at some point.

UPDATE: I have made my version of the game public in my github account here: https://github.com/javier-lerones-gallego/Minesweeper

Use the master branch, I have created a couple of other branches, one for github pages to be able to host the game itself for free, and another one for tests on how to create the game in Angular instead of vanilla JS.

Description and Rules

Ok so the game has very easy rules:

  • You can create three different difficulty levels: Easy, Medium, and Hard.
  • These three levels only change the size of the board, and the number of mines in it. They do not modify the rules of the game.
  • When you right click on a square that hasn’t been revealed you add a flag to it.
  • When you right click on a square that has a flag on it you remove the flaq.
  • When you left click on a square that hasn’t been revealed, it is revealed.
  • When you reveal a square and it has a mine in it, the game ends as a loss.
  • When you reveal a square that has no mine in it, it should switch to a disabled square with the total count of mines that rest around it.
  • When all the squares are revealed and there is the same amount of squares flagged as the number of mines in the board, the game ends with a win.
  • The game should track the time that takes you to finish each board, and store a list of best scores achieved during this session. Does not need to be persisted.
  • Optional: If you middle click on a square that has been revealed it will activate the squares around it while the middle mouse is pressed.
  • Optional: If you middle click on a square that has been revealed and has the same amount of neighbour squares flagged as the number it indicates itself, then all the neighbour squares are revealed too.

Ok so those are the rules. Piece of cake. Let’s do this.

My Implementation

There are a couple of considerations I need to make before deciding to go one way or another. First things first, how am I going to model this game?

It is easy to see quickly that we are going to have several entities, Board, Square, Timer, Score, and a Game Manager to handle it all.

However the first issue I can see happening is going to be deciding how to know which squares are the neighbours of the other. The first idea that comes to mind would be to calculate which are my neighbours after any click happens on a Square. We will have the X, Y coordinates of the one we have clicked on, so we can easily calculate which are the ones around it.

Ok so that would work, but I don’t like it. If I go this way I will have to ask the Board object to get all the squares around another square, that adds an unnecessary dependency between Board, Square, and Game Manager. But what else can I do?

Well, there is a second option, when we create the board, we could have a reference to all the neighbour squares inside each square. This way we don’t have to call the Board and ask it to calculate neighbours each time, we only go through the list of Squares and trigger the appropriate method for each. I kinda like this option even less than the first one. Feels crappy.

There is a third option that is growing on me, why not using pub-sub for the click events.. or for that matter for all events in the game. This completely removes all dependencies between Board, Square, and Game Manager. Each of them will subscribe to several event, and some of them will broadcast events.

I like it.

This is how it should work:

  • The Timer will subscribe to the board.game.win and board.game.loss events.
  • The Board will subscribe to the square.flagged, square.revealed, and square.count events.
  • The Squares will subscribe to the square.revealed events.

Those events will be triggered throughout the application by different entities, and probably when I am done there will be many other events added to different modules.

To jQuery or not to jQuery

After a little bit of thinking I am going to go with just two frameworks/tools to get help from, jQuery for easier DOM manipulation, and RequireJS to handle the dependencies.

I am always hesitant to use jQuery, you really don’t need it, if you don’t trust me go here http://www.youmightnotneedjquery.com/. I think jQuery makes web developers too dependent on its features, you can actually see people out there calling themselves javascript developers and not having a clue about what hoisting or scopes are. It is all jQuery’s fault.

However, this game is going to be DOM intensive, lots of things happening and changing constantly, and I really don’t want to spend 90% of the development time creating a wrapper for basic DOM stuff that already exists. So jQuery it is. But just this time.

I’ve decided to create the entire app as JavaScript AMD modules that will be loaded and handled by requirejs instead of having to take care of orders and stuff.

In any case the game is going to behave as a single page application, or to be more precise I would say the entire game will be placed in the same view, and we will only modify the DOM depending on the state, the URL will remain the same always.

I will also use bootstrap just to make everything else look prettier than the default browser styles, besides it has a grid that I might end up using, and other very handy utilities that might end up proving useful.

JavaScript Modules

Using requirejs makes this process really simple, just create your modules this way:

define(['jquery'], function($) {
    var mediator = $('<b/>');
    return {

        subscribe: function(topic, fn) {
            // Call fn, stripping out the 1st argument (the event object).
            function wrapper() {
              return fn.apply( this, Array.prototype.slice.call( arguments, 1 ) );
            }

            // Add .guid property to function to allow it to be easily unbound. Note
            // that $.guid is new in jQuery 1.4+, and $.event.guid was used before.
            wrapper.guid = fn.guid = fn.guid || ( $.guid ? $.guid++ : $.event.guid++ );

            // Bind the handler.
            mediator.on( topic, wrapper );
        },

        // Unsubscribe from a topic.
      unsubscribe: function() {
           mediator.off.apply( mediator, arguments );
      },

      // Publish a topic
      publish: function() {
        mediator.trigger.apply( mediator, arguments );
      }
    };

});

This is my pub-sub service, I have this code in its own file named pubsub.service.js. Then you can inject this module into others easily using requirejs, like for example what I did for the square.viewmodel.js module:

UPDATED CODE: I’ve removed some of this module’s code to make it shorter, you can find the entire version on the file in the repo: https://github.com/javier-lerones-gallego/Minesweeper/blob/master/app/scripts/viewmodels/square.js

define(['scripts/services/pubsub', 'scripts/services/util'], function(pubsubService, toolsService) {

    function SquareViewModel(constructor_arguments) {
        var self = this;

        this.state = 'active';
        this.neighbours = constructor_arguments.neighbours;
        this.bombCount = 0; // -1 = mine, 0..8 number of mines around
        this.id = constructor_arguments.id;
        this.neighbouringFlagCount = 0;

        this.square_on_square_mousedown = function(args) {
            if(args.id === self.id) {
                if(args.event.button === 1) {
                    // Cancel the scrolling for the middle button
                     return false;
                } else if(args.event.button === 2 && self.isRevealed() && self.bombCount > 0) {
                    // If the square is revealed highlight the neighbours on mousedown
                    pubsubService.publish('square.neighbours.highlight', { id: self.id });
                }
            }
        };

        //
        // Pretty much this is the constructor logic
        // make it an iffe to make it more obvious
        //
        (function() {
            // First: Create the subscriptions
            //
            // Events triggered by the UI
            pubsubService.subscribe('ui.square.mousedown', self.square_on_square_mousedown);
            pubsubService.subscribe('ui.square.mouseup', self.square_on_square_mouseup);
            pubsubService.subscribe('ui.square.dblclick', self.square_on_square_dblclick);
            // Events triggered by other objects in the application
            pubsubService.subscribe('square.show.mine', self.square_on_square_show_mine);
            pubsubService.subscribe('square.disable', self.square_on_square_disable);
            // Events triggered by other squares
            pubsubService.subscribe('square.neighbours.highlight', self.square_on_square_neighbours_highlight);
            pubsubService.subscribe('square.neighbours.unhighlight', self.square_on_square_neighbours_unhighlight);
            pubsubService.subscribe('square.neighbours.reveal', self.square_on_square_neighbours_reveal);
            pubsubService.subscribe('square.neighbours.add.mine', self.square_on_square_neighbours_add_mine);
            pubsubService.subscribe('square.flagged', self.square_on_square_flagged);
            pubsubService.subscribe('square.questioned', self.square_on_square_questioned);
            pubsubService.subscribe('square.neighbours.click', self.square_on_square_neighbours_click);
        }());

    }

    SquareViewModel.prototype.reveal = function() {
        if(!this.isRevealed() && !this.isFlag() && !this.isQuestion()) {
            if(this.bombCount === 0) {
                // change to white, and empty
                pubsubService.publish('ui.square.reveal.empty', { id: this.id });

                // Mark it as revealed BEFORE invoking the neighbours so the neighbours events don't come back here
                this.setRevealed();

                // Trigger the neighbours
                pubsubService.publish('square.neighbours.reveal', { id: this.id });
            } else if(this.bombCount > 0) {
                // change to white with the bomb number inside it, and don't trigger the neighbours
                pubsubService.publish('ui.square.reveal.count', { id: this.id, count: this.bombCount });

                // Mark it as revealed so the neighbours events don't come back here
                this.setRevealed();
            }

            // Trigger the square has been revealed event for everybody listening
            pubsubService.publish('square.revealed');
        }
    };

    return SquareViewModel;

});

Alright that’s it for today, I will write more about this game as soon as I finish it. Thanks for reading.