Lately, I’ve had the opportunity to be exposed to some web technologies with which I had little previous experience. While I’ve spent quite a bit of time in the world of client-side programming in the browser using tools like WebGL, Backbone.js, etc, I’ve yet to have occasion to get exposed to any of the new breed of DOM binding libraries.
I wanted to make a simple, roguelike game map that represented the UI using DOM elements, while keeping the actual state backed in a View Model. This seemed like the perfect nail in search of a Knockout-based hammer.
Aside from Knockout, I used bootstrap to style the markup. A usual coterie of utility libraries was employed (jQuery, underscore, etc). And since no self-respecting roguelike relies on the mouse, I ended up using Mousetrap for keyboard event hooks. Also mumble mumble require.js blah blah AMD.
For the purpose of my demo, my approach was that I’d deliver a JSON payload as part of the initial document. This would be accessed from my application setup code, which would parse the data into the View Model, set up keyboard events and call
The Demo Itself
The goal is to create a simple, “roguelike” demo that consists of the venerable
@ symbol, within a grid-like map that makes up its little world. Real games have all kinds of things, like: the passage of time, hunger, combat, magic, mongsters, death etc etc ad infinitum. Ours is concerned with just presenting a basic, 6x6 grid of tiles that’re passable (or not). The player’s location is marked by the
@’s location on the map. You can move the player about with the arrow keys on your keyboard. I could probably do something for mobile, but I haven’t gotten around to it.
What follows is a breakdown of the demo.
HTML & Data Bindings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Here we see the basic outline of the knockout mapping used. At the top-level, a
div element with a
data-bind attribute. It uses the foreach binding to iterate over a
map property, provided in the View Model. Within that, another
div element, styled as a Bootstrap
row that will, in turn, iterate over the individual tiles within its row via its own
The contents of the cell, itself, are simple. It’s a Bootstrap column, 2 units wide (6x6 map, remember), whose presentation reflects the current state of the Tile. On the column
div itself is a
css data-binding, which will apply a class based on the value of the
tileBackground observable for the View Model. A series of ko if comments bindings determine the content of the cell, based on a number of observables within the View Model.
The Raw JSON Input
1 2 3 4 5 6 7 8 9 10 11 12 13
This is a snippet of a single row of the
TileMap value, which is injected into the initial script black of our demo’s HTML markup. In a real application, this would be driven by logic on the server, but it’s stubbed out in this demo as a static value and attached to the
TileMap is just an array of objects, each with a single
Making TileMap Usable By Knockout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
We establish a
MapViewModel that will contain our data. Additionally, we have set up a
ko.subscribable to act as an event sink for input-driven changes to the
@ position on the map.
MapViewModel takes, as the
map property, the
TileMap covered earlier in this post. It will then iterate over the entire contents of that object, bolting-on several Knockout
ko.computed observable functions that reflect the tile’s state and update dynamically with changes to the View Model. You will recognize these observables as being referenced in the HTML markup shown previously. All of the individual tiles, by virtue of function environment capture, have access to their
y coordinates (
ty, respectively) to use for their logic.
playerPos– an observable representing the player’s global position. Changes this will update the
tileBackground– Sets a bootstrap class to change the tile element’s color based on whether it’s a wall or a passable space
playerIsHere– Indicates whether this is the space, on the map, where the player is located. Note that the tile itself doesn’t carry information on the player’s presence or lack thereof. Instead, the tile is listening for changes to the player’s position via
positionSubscribersubscribable. Every time the subscription event fires, the
playerPosobservable is updated, leading to
unoccupied– Naturally, the inverse of
Using external input to drive View Model changes
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
This code block wires up the input handling and binds the View Model to the DOM. It’s pretty straightforward. The
moveInDir function performs validation to make sure the player isn’t trying to move into a wall (and if they are, prevent that from actually happening). If the player’s desired destination, as per the input coords (you can see the four callers of
moveInDir each pass in a different object with the target
y offsets for the move), is available to be moved to. We call
positionSubscriber.notifySubscribers with the new player position. This triggers the subscriptions, in the previous snippet, to update the View Model, triggering changes in the DOM representation.
There are a number of things that could be improved upon in this demo.
- General cleanup/consistency, of course
- Some duplication in the observable logic (
unoccupied.. probably shouldn’t duplicate the logic in these)
- Move the DOM markup into Knockout templates