Opinionated Programmer - Jo Liss's musings on enlightened software development.

How I Hand-Rolled MVC for a JavaScript Game (and It Was Awesome)

In which

  • I sing the praises of frameworks like Ember.js,
  • explain why they don’t work so well for games, and
  • demonstrate how to structure any JavaScript app (game or not, framework or not) around MVC for cleaner code.

I recently decided to improve the Solitr game (GitHub) I had hacked together in a one-day hackathon (blogged about here). The code had turned out rather messy, so I went for a rewrite with better design, only copying bits and pieces from the first implementation.

A plethora of JavaScript MVC frameworks have cropped up since 2011 – Backbone, Spine, Knockout, Ember, Batman, and Serenade come to mind. I had used Backbone before, but I had heard interesting things about Ember, so I decided to give that a spin. I am rather enthused:

Why Ember.js Rocks

Probably the most important feature in Ember is attribute binding. It allows attribute changes to automatically propagate through all layers of your application. For example, say you change firstName on your model, then the firstName attributes on your controller and view class are updated (because they are “bound” to model.firstName), and the fullName helper attribute on your view class gets recalculated as well, because it in turn is “bound” to view.firstName.

This is the awesomest thing since sliced bread. It means that when your controller responds to an event, it only needs to update the model and every piece of your app that (directly on indirectly) depends on it is notified.

Ember’s in-place template updating deserves special attention too, and coming from server-side templating languages, the significance of this feature wasn’t obvious to me at first: When the fullName attribute in the previous example is updated, Ember’s template engine will change the attribute inside the template live in the DOM, and not just re-render the entire template. That’s important because otherwise you lose all GUI state (such as collapsible elements, or cursors in text fields) whenever a single attribute inside a template changes. I learned this the hard way with a Backbone application I worked on: Having to re-render views just because a counter increases is bothersome for small page elements, but once you get to reloading an entire pane, it becomes a usability issue, and one that’s rather hard to fix.

In other words, unlike on the server side, it’s useful for JavaScript templating engines to be more intelligent than just spitting out a chunk of HTML.

So attribute binding and template updating are the two things I recommend you pay attention to when choosing an MVC framework.

Enter Games: On States and State Transitions

So I started to rewrite Solitr with Ember.js. A day or so into the rewrite however, I was beginning to question whether Ember’s attribute-binding approach was right for a game. Let me explain why.

When a card moves to a new place, it needs to be animated. So you can’t just update the left and top CSS properties when the game state (and hence the location of a card) changes. My first instict was to simply slide the cards into place whenever the position changes, either by catching the changes to the position attribute and wrapping it in a call to jQuery’s .animate(), or using the CSS transition property. However, this doesn’t quite work: The type of animation you need depends on the last action: For instance, turning over three cards on the stock, playing a card you double-clicked, or snapping a card into place after it has been drag-and-dropped, all require different sliding speeds and easing functions.

Of course you can hack the animation code so that it infers the right type of animation from the difference between the current and the previous game state. This might works most of the time, but besides being ugly, there are cases where it’s not possible to know whether a given state change came about through, say, double-click, drag-and-drop, or undo (all of which are animated differently).

In this situation, compromising on the quality of the animations is a no-brainer for a regular web application. But for a game, animations should be first-class citizens. Since they are so ubiquitous in a game (even a simple one like solitaire), treating them as less than first-class citizen will yield hundreds of lines of unmaintainable animation spaghetti code.

So how do you make animations first-class citizens? Easy enough: You do not update the game state and then ask the controller to update the GUI according to the new state. Instead, you send a Command object (as described in GoF’s Design Patterns) to the game state so that it can update itself, and then send the same Command object to the animation method so it can update the GUI based on the type of command.

In other words, the application is now fundamentally structured around state transitions (commands, that is) rather than states.

This approach is probably untypical for “regular” web apps, and frameworks like Ember.js discourage it by exposing state and hiding state transitions behind the auto-updating mechanism for object attributes. Besides animations, the only use case I can think of is when you absolutely need to build an undo stack: With your code structured around state transitions, you can easily use Command objects for the undo stack, rather than ad-hoc closures (or clones of the model object). Other than that, I am not sure if for a non-game app that doesn’t require sophisticated animations, this is worth the effort.

However, for game code, I have found that this pattern works very well, and it has turned out to be very maintainable so far.

Hand-Rolling MVC

As I structured my app around Commands, my reliance on Ember’s features started to disappear, and eventually I was left with Ember’s slightly cumbersome attribute accessor syntax (card.get('rank') instead of card.rank) with no benefit from it, as all my attributes had become independent, and no auto-updating between objects (“binding” in Ember parlance) was going on anymore.

The only thing Ember was doing for me was templating with Handlebars, but since all I render are cards (essentially <div class="card" id="..."></div>), I felt safe pulling these one-liners into the JavaScript code without sacrificing maintainability.

So I removed the dependence on the Ember.js library. What was left was an application handsomely structured around the MVC pattern, and compared to the initial version I hacked together, the logical separation into model and presentation code is the biggest single design improvement.

A Look at the Code

The application essentially consists of two files, models.js.coffee and controllers.js.coffee (written in CoffeeScript).

I haven’t found it necessary to derive either models or controllers from a common base class.

Models

The models hold the game state and implement the game (domain) logic. I deliberatedly kept the models isolated from the rest of the application, so the code doesn’t know about the controllers or views, and also doesn’t need a DOM. This has made the code much more maintainable. I’m also hoping that it will help me when I write tests, perhaps with Mocha.

Card Model

The Card model is very simple. It only holds three attributes – rank, suit, and id – and no logic at all. Whether it is face-up or face-down is inferable from its place in the game (stock cards are always face-down, waste cards are always face-up, etc.), so that is something that is determined by the controller that renders it, not stored by the model.

1
2
3
4
5
_nextId = 0

class App.Models.Card
  constructor: (@rank, @suit) ->
    @id = "id#{_nextId++}"

Game State Model

The game state model, on the other hand, is rather more complicated. It holds several arrays of cards (stock, waste, tableau piles, foundations), and encodes the rules of the game (the domain logic, that is). Here is a slightly simplified version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class App.Models.KlondikeTurnThree
  cardsToTurn: 3
  numberOfFoundations: 4
  numberOfTableaux: 7

  # Initialization
  constructor: -> # initialize deck and arrays
  createDeck: -> # create all the cards for the deck
  deal: -> # set up initial layout

  # Rules
  foundationAccepts: (foundationIndex, cards) ->
  tableauPileAccepts: (tableauPileIndex, cards) ->
  isMovable: (card) ->

  # Commands
  executeCommand: (cmd) ->
    # The Big Method that parses and executes commands,
    # thereby managing all state transitions

  # Helpers
  nextAutoCommand: ->
    # Return command to auto-play and auto-flip
    # cards in some situations
  ... other helpers ...

While the model implements rule logic (with methods like foundationAccepts), it does not enforce it. The controller has to check whether a given action is legal in order to give proper user feedback. It then seemed redundant to implement another such check in the game state model.

For simplicity’s sake, I decided not to let model classes proliferate: The piles that make up the game state model (stock, waste, etc.) are dumb arrays, not models on their own. Any logic that might conceivably be attached to those piles is moved up into the all-knowing game state model.

Controllers

Paralleling the models, there is a simple Card controller performing basic rendering duties, and a game controller (App.Controllers.KlondikeTurnThree) ordering the cards around.

Card Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class App.Controllers.Card
  size: {width: 79, height: 123}
  element: null

  constructor: (@model) ->

  # Insert / remove from DOM
  appendTo: (rootElement) ->
  destroy: ->

  # Move around
  setRestingPosition: (position) ->
  jumpToRestingPosition: ->
  animateToRestingPosition: (animationOptions) ->

Game Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class App.Controllers.KlondikeTurnThree
  model: null

  constructor: ->

  newGame: ->

  processCommand: (cmd) ->
    # Call @model.executeCommand and @animateCards
  animateCards: (cmd) ->
    # Paralleling executeCommand in the model, this method
    # animates the GUI

  # Housekeeping
  calculateGeometry: () ->
  appendBaseElements: () ->
  registerEventHandlers: ->
  removeEventHandlers: ->

  # The actual event handlers
  turnStock: =>
  redeal: =>
  ...

  # And tons of helper functions dealing with minutiae like
  # drop zone geometry for drag'n'drop ...
  ...

To be honest, I’m finding that my game controller is becoming a sprawling assortment of semi-related methods. I think this in part due to GUIs being messy business, and in part due to suboptimal design. Eventually I will have to split it into more classes, but for now it actually works quite well.

Views and Templates

The view classes and templates that most frameworks expect you to create have been folded into the Controller classes. There is the occasional $('<div class="button">...</div>'), and the cards are rendered by programmatically by creating DOM nodes:

1
2
3
4
5
6
7
class App.Controllers.Card
  appendTo: (rootElement) ->
    @element = document.createElement('div')
    @element.className = 'card'
    @element.id = @model.id
    $(@element).css(@size)
    $(rootElement).append(@element)

So far I haven’t needed to separate these from the controllers because they are so miniscule. (I am sure that this will not scale though, and if you are using an MVC framework that comes with templating functionality, you should definitely use it from the get-go.)

Conclusions

  • Ember.js rocks because it can propagate (bind) attributes between objects, and it auto-updates templates live without rerendering.

  • But attribute propagation is suboptimal for games, since animation requires that you know about state transitions, not just state.

  • For any app, it greatly helps the design to follow MVC:

    1. Create isolated model classes storing application state and implementing domain logic.
    2. Create controller classes that talk to the models and the DOM, implementing the GUI logic.
    3. Factor templates out of the controllers.
    4. Factor helper methods for the templates into view classes.