jvandemo.com

How to build Minesweeper using Angular 2 and Immutable.js

Introduction

user

Jurgen Van de Moere

Follow @jvandemo

How to build Minesweeper using Angular 2 and Immutable.js

Posted by Jurgen Van de Moere on .
Featured

How to build Minesweeper using Angular 2 and Immutable.js

Posted by Jurgen Van de Moere on .

featureimage

This is the first in a series of articles on Angular 2. If you want me to send you a quick note when a new article is available, just leave your email at the bottom of the article or follow me on Twitter.

After watching Christian Johansen's talk on Immutable JavaScript I thought it would be really cool to port his React.js version of Minesweeper to Angular 2.

In this article I explain how I built Minesweeper in Angular 2 and Immutable.js.

The entire code is available here.

To prevent this article from becoming a book, I don't go into the inner workings of Angular 2 unless it is relevant to this application. If you are new to Angular 2, make sure the check out the official documentation for more details.

Preview

Here is a preview of what we will be building:

Goals:

  • You should be able to click "new game" at any time to start a new game
  • When you click a mine, an alert should show "GAME OVER!"
  • You should be able to click "undo" to undo a previous change (this is not usually a feature of Minesweeper, but rather a way to discover how cool immutability is)

Feel free to try out the embedded plunk above to get a feel of how it works.

Immutable.js

I decided to use Immutable.js to store the game data. I could have used plain old JavaScript arrays and objects but I wanted to explore how to use Immutable.js with Angular 2.

Don't worry if you are not familiar with Immutable.js. You will be perfectly capable to read the code.

In essence every mutative operation performed on immutable data returns new updated data while the old data remains untouched.

The benefit of using immutable data is that dirty checking does not need to perform a deep equality check. If every mutation returns a new object, dirty checking can use a blazingly fast strict equality check:

var map1 = Immutable.Map({a:1, b:2, c:3});  
var map2 = map1.set('b', 50);  
map1 === map2; // false  
map1.get('b'); // 2  
map2.get('b'); // 50  

Although performance is fine without Immutable.js in a small application like Minesweeper, it is a great way to explore it nonetheless for when we need to build larger Angular 2 apps in the future.

If you want to read more about immutable data, check out the Immutable.js website.

Data structure

A Minesweeper game is represented by an immutable list of tiles. So if we have a game with 16 x 16 tiles, the data structure will look like this:

let game = Immutable.List([  
  { id: 1, isRevealed: false, isMine: false },
  { id: 2, isRevealed: false, isMine: true },
  ...
  { id: 256, isRevealed: false, isMine: false }
]);

where each tile is an immutable map of properties:

let tile = Immutable.Map({

  // Id of the tile
  id: 1, 

  // Whether or not the tile is revealed
  isRevealed: false,

  // Whether or not the tile is a mine
  isMine: false
});

Component breakdown

The Angular 2 application can be broken down in 4 components:

  • app: the main application component
  • minesweeper: the minesweeper game board
  • row: a single row in the board
  • tile: a single tile in a row

The actual game logic originates entirely from Christian's React.js example and is stored in game.ts and util.ts. I didn't change a single character in these files.

This affirms how easy it is to import non-Angular specific code in an Angular 2 application.

Application structure

The application contains of the following files:

# SystemJS configuration
config.js

# Main index file
index.html

# Angular 2 app component
src/app/app.component.ts

# Bootstrap Angular 2
src/main.ts

# Game logic
src/minesweeper/game.ts

# Angular 2 minesweeper board component
src/minesweeper/minesweeper.component.ts

# Angular 2 row component
src/minesweeper/row.component.ts

# Angular 2 tile component
src/minesweeper/tile.component.ts

# Utilities
src/minesweeper/util.ts

# CSS Styles
style.css  

The entire source code can be found here.

In the remainder of this article we will be focusing on the Angular 2 components.

The app component

Let's start by having a look at the app component that is instantiated when the Angular 2 application is bootstrapped:

import {Component} from 'angular2/angular2';  
import {MinesweeperComponent} from '../minesweeper/minesweeper.component';  
import {createGame} from '../minesweeper/game';

@Component({
  selector: 'app',
  template: `
  <minesweeper [game]="game" #minesweeper></minesweeper>
  <ul class="actions">
    <li>
      <a (click)="startNewGame()">New game</a>
    </li>
    <li>
      <a (click)="minesweeper.undo()"
         [hidden]="!minesweeper.canUndo()">Undo</a>
    </li>
  </ul>
  `,
  directives: [MinesweeperComponent]
})
export class App {  
  public game;

  // Keep constructor lightweight
  constructor(){}

  // Immediately called after constructor
  onInit(){
    this.startNewGame();
  }

  // Create a new game
  startNewGame(){
    this.game = createGame({cols: 16, rows: 16, mines: 48});
  }
} 

In the onInit() method of the component, we make sure a new game is created when the app component is initialized. onInit is a lifecycle hook that is called immediately after the constructor.

It is recommended to put our initialization logic in onInit instead of the constructor to make sure components can be unit tested properly. Learn more about component life cycle hooks in the official Angular 2 cheatsheet.

In the template we instantiate the minesweeper component and pass it the game we just created:

<minesweeper [game]="game" #minesweeper></minesweeper>  

Notice how we can pass the game object to the minesweeper component using property binding. At the same time we store a reference to the minesweeper component using #minesweeper. This reference then allows us to call minesweeper methods from within the action bar:

<a (click)="minesweeper.undo()"  
   [hidden]="!minesweeper.canUndo()">Undo</a>

How convenient is that!

Now let's have a look at the minesweeper component.

The minesweeper component

The minesweeper component is where most magic happens so let's have a look at the entire code first:

import {Component, Input, CORE_DIRECTIVES} from 'angular2/angular2';  
import {partition} from './util';  
import {revealTile, isGameOver} from './game';  
import {RowComponent} from './row.component';

@Component({
  selector: 'minesweeper',
  template: `
  <div class="board">
    <row *ng-for="#row of rows"
         [row]="row"
         (tile-click)="handleTileClick($event)"></row>
  </div>
  `,
  directives: [CORE_DIRECTIVES, RowComponent]
})
export class MinesweeperComponent {  
  @Input() game: any;
  rows;
  history = Immutable.List();

  onChanges(changes){

    // Only update game when game has actually changed
    if(changes.hasOwnProperty('game')){
      this.updateGame()
    }
  }

  updateGame(updateHistory = true){
    this.rows = partition(this.game.get('cols'), this.game.get('tiles'));
    if(updateHistory){
      this.history = this.history.push(this.game);
    }
  }

  handleTileClick(tile){
    if(!tile){
      return;
    }
    if (isGameOver(this.game)) {
      return;
    }
    const newGame = revealTile(this.game, tile.get('id'));
    if (newGame !== this.game) {
      this.game = newGame;
      this.updateGame();
    }
    if (isGameOver(this.game)) {
      window.alert('GAME OVER!');
    }
  }

  undo(){
    if (this.canUndo()) {
      this.history = this.history.pop();
      this.game = this.history.last();

      // Don't update the history so we don't end up with
      // the same game twice in the end of the list
      this.updateGame(false);
    }
  }

  canUndo(){
    return this.history.size > 1;
  }
}

Because we passed the game object to the minesweeper component in the app component, we have to make sure the minesweeper component accepts it. This is done by creating a decorated class property:

@Input() game: any;

Basically this tells Angular to accept what is passed in through the game property and store it as a game property in the component controller.

Because we want to instantiate row components in our template:

<div class="board">  
  <row *ng-for="#row of rows"
       [row]="row"
       (tile-click)="handleTileClick($event)"></row>
</div>  

we have to parse our game data into row data that we can pass into the row components.

We want to do this not only during initialization, but every time the passed in game changes. So we make use of the onChanges lifecycle hook:

onChanges(changes){

  // Only update game when game has actually changed
  if(changes.hasOwnProperty('game')){
    this.updateGame()
  }
}

The onChanges() method is called every time an input property changes. Learn more about component life cycle hooks in the official Angular 2 cheatsheet.

The onChanges() method receives a changes object that contains a property for every input property that has changed. So we check if the game was changed and if so, we update the data accordingly.

If we would have put this code in the onInit lifecycle hook instead of the onChanges lifecycle hook, the board would display fine initially, but it would not update when the passed in game changed later on.

Another thing we want to do is update the game when a user clicks a tile inside a row, so we will listen for tile-click events and run handleTileClick whenever such an event is emitted from the row component.

The rest of the code is not Angular 2 specific so I won't discuss it in this article. Should you have any additional questions, feel free to post a comment below.

The row component

The row component is responsible for showing a single row of tiles:

import {Component, Input, Output, EventEmitter, CORE_DIRECTIVES} from 'angular2/angular2';  
import {TileComponent} from './tile.component';

@Component({
  selector: 'row',
  template: `
  <div class="row">
      <tile *ng-for="#tile of row"
            [tile]="tile"
            (click)="handleTileClick(tile)"></tile>
  </div>
  `,
  directives: [CORE_DIRECTIVES, TileComponent]
})
export class RowComponent {  
  @Input() row: any;
  @Output() tileClick: EventEmitter = new EventEmitter();

  handleTileClick(tile){
    this.tileClick.next(tile);
  }
}

As previously with the minesweeper component, the row component accepts an input property. This time it accepts a row:

@Input() row: any;

The row is assigned to the row property of the component controller so we can easily loop through it in the template and create a tile component for every tile in the row:

<tile *ng-for="#tile of row"  
      [tile]="tile"
      (click)="handleTileClick(tile)"></tile>

We also attach a click handler so we can take action when a tile is clicked.

Because the minesweeper component expects the row component to emit a tile-click event, we create a corresponding event emitter:

@Output() tileClick: EventEmitter = new EventEmitter();

To emit an event we can now call:

this.tileClick.next(tile);  

Angular converts a camelcased event emitter name to a slugified event on the component. So calling the next() method on the titleClick event emitter causes a tile-click event to be fired.

Now the row component emits a custom tile-click event whenever a tile is clicked. How cool is that!

The tile component

The tile component is responsible for displaying a single tile:

import {Component, Input, CORE_DIRECTIVES} from 'angular2/angular2';

@Component({
  selector: 'tile',
  template: `
  <div class="tile" [class.mine]="tile.get('isMine')">
    <div class="lid" *ng-if="!tile.get('isRevealed')"></div>
    <div *ng-if="tile.get('isRevealed') && !tile.get('isMine')">
      {{ tile.get('threatCount') > 0 ? tile.get('threatCount') : '' }}
    </div>
  </div>
  `,
  directives: [CORE_DIRECTIVES]
})
export class TileComponent {  
  @Input() tile: any;
}

First of all it accepts an input property tile. We already learned how this works so we won't go into that again.

Other than that the template uses a few Angular 2 core directives to conditionally instantiate elements and apply css classes.

If you're not familiar with the new template syntax or core directives, check out the official Angular 2 documentation.

Look how easy conditional CSS classes can be applied:

<div class="tile" [class.mine]="tile.get('isMine')">  

This will apply the mine CSS class whenever tile.get('isMine') returns a truthy value.

How convenient is that!

Summary

Believe it or not, but that is all it takes to create Minesweeper using Angular 2 and Immutable.js.

By now you should hopefully have a better understanding of:

  • how to split an application into Angular 2 components
  • how to pass data to into a component using property bindings
  • how to listen for custom events emitted by a component
  • how to make a component accept input properties
  • how to make a component emit custom events
  • how to use lifecycle hooks to plug into the lifecycle of a component

You can read the entire source code here. Feel free to fork the plunk and play around with it.

Conclusion

It amazes me how little code is needed to perform amazingly powerful things in Angular 2.

Once you have an understanding of the core concepts, creating components will become second nature.

Hopefully this article helps you understand some of these concepts. Should you have additional questions, please don't hesitate to create a comment below.

I will be creating more applications and sharing my experiences along the way, so if you're interested in staying up-to-date, feel free to leave your email address below and I'll send you a quick note when a new article is available.

Thanks for reading and have a great one!

UPDATE 2015-11-27

In How I optimized Minesweeper using Angular 2 and Immutable.js to make it insanely fast I write about how I optimized this version of Minesweeper to increase performance.

user

Jurgen Van de Moere

Front-end architect at The Force. Gymnast. Dad. Family man. Creator of Angular Express.