Introduction

When it comes to application development, technology is ever changing. New languages and runtimes are introduced, some with great fanfare, others with a whisper. Many fall away, never to see widespread use. Some reach a peak and cruise along for years; fewer become defacto standards.

At Liquid, we face many worlds of development. The enterprise tends to value stability and scalability. The small business leans towards flexibility, speed and cost. The start-up values innovation and the art of the possible. Leading companies value all of these factors.

Our job is to determine what fits in these worlds and create a solution that best fits the need. To do this we constantly evaluate develoment trends and new technologies. This post is a peek into that process.

Part 1: Backend

This guide is meant for developers, as an exploration and starting point for using modern web technologies to build a multiuser experience that works on both mobile and desktop.

By the end of this guide you’ll have a simple app that manages a shared space for multiple users.

To get started, you’ll need to install the following components:

Node.js

Node.js is a fast JavaScript runtime designed to run outside the browser. It supports the most common platforms including Linux, Windows and Mac OS X. To install it, head over to http://nodejs.org to download the package appropriate for your environment, or use your package manager of choice, like apt-get or Homebrew on Mac OS X.

Out of the box, Node.js isn’t exactly ready to go as a web application server, and the purpose of this starter guide is not to show you how to get Node.js off the ground. So to help us out and focus on the topic at hand, I have chosen to use StrongLoop’s LoopBack.

Why LoopBack?

LoopBack is an Express wrapper, with some very useful tooling that provides a great starting point to build a simple mobile backend or a complete web application with AngularJS or other MV frameworks like Ember.js. In this guide, we’ll forego the client-side frameworks.

Use the following command to install LoopBack:

npm install -g strongloop

If everything goes well you’ll be able to create a LoopBack app and move on to the next step. However, if you are impatient and want to tinker now, you can download our sample project.

OK, thanks for sticking with me. Create a LoopBack app with the following:

slc loopback

You’ll be asked a series of questions to set up the app. Use the following in response:

? What's the name of your application? (sites) my-multiplayer
? Enter name of the directory to contain the project: (my-multiplayer) 

Accept the default value by pressing enter. The command will finish by setting up the app in the my-multiplayer path. To make sure everything went as expected, cd into the my-multiplayer path and run:

slc run

This starts a new node server instance running at http://localhost:3000 . You should see something like this if you visit that URL in your browser:

{"started":"2015-02-02T01:21:54.145Z","uptime":7.149}

Kill the node process with Ctrl-C to create the player model.

The Player Model

Now that the default app is working, create a model with API endpoints to handle interaction with the browser.

While we can do this exclusively in code, for our purposes we’ll use the slc command to generate our model. At the command, prompt enter the following:

slc loopback:model Player

As with the create app command, there are a number of questions to answer.

? Enter the model name: (Player)

Accept the default value for this by pressing Enter.

? Select the data-source to attach Player to: (Use arrow keys)
(no data-source)
> db (memory)

Press Enter again to select the default. The in-memory db handler in LoopBack will work well for our purposes. While not production ready, it will work fine to manage our multiplayer space in this guide.

? Select model's base class: (Use arrow keys)
Model
> PersistedModel
ACL
AccessToken
Application
Change
Checkpoint
(Move up and down to reveal more choices)

Again, press Enter to select the default value. The PersistedModel base class has the functionality we need.

? Expose Player via the REST API? (Y/n)

Press Y and Enter to make the Player model available via default API endpoints.

? Custom plural form (used to build REST URL): Players

Enter Players for the plural form of the Player model.

Now we'll add some properties to our Player model. Using the guide below, repeat until you have created all of the following properties:

Property Name: GUID
Property type: string
Required? Yes
Property Name: LocationX
Property type: number
Required? Yes
Property Name: LocationY
Property type: number
Required? Yes
Property Name: LastCheck
Property type: date
Required? Yes

This is a simple model and just what we need for this simple app.

GUID is a unique identifier shared only between the server and the player.

LocationX and LocationY are used to track the position of the players in multiplayer space.

LastCheck is a date property for tracking when the player last checked the service. This is used to remove disconnected players from the space. If the player hasn’t checked in in a certain time period, we remove the player from the data source.

This process registers your model in server/model-config.json and generates two files in the common/models path of your app. player.js and player.json. player.json is the model definition, and player.js is the controller for your application logic related to the Player model.

Before adding our own code to player.js, we need to install the node-uuid package to generate the unique IDs.

npm install node-uuid

LoopBack automatically creates the API endpoints for CRUD type operations. For our purposes, we’ll need to create our own endpoints using remoteMethod. The process for this is to first create a method to handle the endpoint, then point the configuration at the method.

Replace the contents of player.js with the following and review the setlocation and initgame remote methods. The remoteMethod  method exposes the method at the URL http://localhost:3000/Players/methodname

// REQUIRE
var uuid = require('node-uuid'); // required package for generating unique, hard to predict player IDs.




module.exports = function (Player) {
   
    // when POST "/setlocation" is called, update the model data with new values and return player list
    Player.setlocation = function (guid, x, y, cb) {




        // find player model to update by GUID
        Player.findOne(
                    {
                        where: { GUID: guid }
                    }, function (err, player) {
                       
                        if (player != null) {




                            // udpate the values and save
                            player.LocationX = x;
                            player.LocationY = y;
                            player.LastCheck = new Date(); // update this to prevent the player from being wiped later
                            player.save();




                            // get all players and return to client
                            Player.find(function (err, players) {
                                if (players != null) {
                                    for (var p = 0; p < players.length; p++) {
                                        var player = players[p];




                                        // before returning player, check if player hasn't posted for 10 seconds.
                                        var currentTime = new Date();
                                        if (currentTime.getTime() > player.LastCheck.getTime() + 10000) {




                                            // remove the player if stale
                                            Player.destroyById(player.id, function (err, players) { });




                                        }




                                        // hide the player GUID
                                        player.GUID = "";
                                    }
                                }




                                // return players
                                cb(null, players);
                            });
                           
                        } else {
                            // TODO: return error
                            cb(null, null);
                        }
                    });
    }
    // expose remote method and configure paramters.
    Player.remoteMethod(
        'setlocation',
        {
            accepts: [{ arg: 'guid', type: 'string' }, { arg: 'x', type: 'number' }, { arg: 'y', type: 'number' }],
            returns: { arg: 'players', type: 'object' }
        }
    );
   




    // first call from client upon page load
    Player.initgame = function (cb) {
       
        // generate unique ID
        var playerGuid = uuid.v1();       




        // create player
        Player.create([
            { GUID: playerGuid, LocationX: 0, LocationY: 0, LastCheck: new Date() }
        ], function (err, players) {
            if (err) throw err;
            if (players != null) {
                console.log('Player Model Created: \n', players);




                // return GUID and ID
                cb(null, players[0].GUID, players[0].id);




            } else {




                cb(null, 'ERROR', -1);




            }
        });




    }
    // expose remote method and configure return paramters.
    Player.remoteMethod(
        'initgame',
        {
            returns: [{ arg: 'GUID', type: 'string' }, { arg: 'id', type: 'number' }]
        }
    );




};

Before we move on to the client side of the application, we need to allow the app to deliver static content from the client path. Remove root.js from the server/boot path, then add the following to the files section of the server/middleware.json file:

"loopback#static": {
     "params": "$!../client"
}

That's it for Part 1. In Part 2, we'll focus on creating the HTML5 Canvas client.

Other Posts in This Series

Related Links

Let us put our HTML5 skills to work for you! Contact us to find out how Liquid can help.

Lawrence Wolfe

About Lawrence Wolfe

Larry has over 20 years experience in interactive media, delivering innovative and functional solutions for heavy hitters like Mack Trucks and Air Products. As our CTO and Chief Architect, Larry crosses the divide between the left and right brain to produce experiences that are compelling, easy to use and scalable. 

Published Feb 26, 2015