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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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:
- Create isolated model classes storing application state and implementing domain logic.
- Create controller classes that talk to the models and the DOM, implementing the GUI logic.
- Factor templates out of the controllers.
- Factor helper methods for the templates into view classes.