9. Building a Multiplayer Game Server

In this chapter, we will build a game server to house the logic for our games and provide a communal experience for the players. Keeping in the spirit of the games we have been developing, we will be using the server-side JavaScript framework Node.js for our backend work. This will allow us to deeply integrate between our game and server-side code. Assisting Node.js will be the socketing library Socket.IO.

Introduction to Node.js

Node.js is an asynchronous server-side JavaScript environment built on the Google V8 JavaScript engine. V8, which is written in C++, is the engine that runs JavaScript in the Google Chrome browser and is known for its speed. Node treats items such as sockets, HTTP, TCP/UDP, and file I/O as first-class citizens and provides little abstraction over them. Listing 9-1 shows the code for a simple Node.js application that responds to all requests with the text “Hello World.” It begins with a require statement to retrieve the HTTP package for use in the applications. The require statements are similar to require and import statements from Ruby and Python, respectively, in that they make the contents of another file available for use by the currently running application.

Listing 9-1. Simple Node.js Application


http = require 'http'
http.createServer((req, res) ->
    res.writeHead 200, {'Content-Type': 'text/plain'}
    res.end 'Hello World '
).listen 8124, "127.0.0.1"
console.log 'Server running at http://127.0.0.1:8124/'


Using require statements is an easy way to modularize your code and make it more readable and maintainable.

It is important to realize that Node.js is a general-purpose event-driven framework and not the end-all and be-all for a web application. In most cases for any nontrivial example, you will want to use a Node.js web framework.

Node has been embraced by developers because it allows them to code from front to back in the same language. As a result, Node is being used for more than just web servers and is powering utility scripts, testing frameworks, and command-line applications. The official CoffeeScript compiler is implemented using Node. To further build on the work we did with CoffeeScript in Chapter 8, “Creating Games Without JavaScript,” we will be using CoffeeScript for all the examples and demos in this chapter. If CoffeeScript isn’t your favorite beverage-themed programming language, the CoffeeScript compiler produces reasonably readable JavaScript that you could use directly.

Extending Node with the Node Package Manager

By itself, Node is fairly lightweight. That’s not to say you can’t build fairly involved applications with just Node. Node can be extended using the Node Package Manager (or npm for short). npm functions like a software app store where the modules can be full applications in their own right or pieces that can be integrated into other applications. npm can be installed by executing the following command:

curl http://npmjs.org/install.sh | sh

If that fails, you can visit http://npmjs.org/ for further instructions. A list of available modules can be found at http://npm.mape.me/. Modules can be installed or uninstalled, respectively, by executing the following command:

npm install <name of module>

or

npm uninstall <name of module>

Managing Multiple Node Versions

Node is very much in its early days when it comes to stability. Some npm packages need to run against a specific version or have unpredictable behavior. As a result, you can save a bit of pain by installing Node using a version manager.

n (https://github.com/visionmedia/n) is a script file that allows you to manage your node versions. Not only can the versions exist along side each other, but you can specify a version of Node to be the default to route all commands through or you can selectively run commands through a certain version. You can install n by executing

npm install n

if you already have some version of Node installed, or by cloning the git repository and executing the following command:

make install

The command n ls lists the versions of Node available for installation, whereas the commands n latest [or version] and n rm [version] install/use the specified version and remove the specified version of Node, respectively.

Making Web Apps Simpler with ExpressJS

ExpressJS (http://expressjs.com/) is a rapid web application development framework for Node.js. Think of it as a focused web development layer over the more general-purpose Node. Express focuses on the following:

• URL routing DSL

• Middleware

• View templating and rendering

• Deep integration with Connect, a middleware layer for Node, to manage things such as cookies, sessions, and routing

Listing 9-2 shows a very basic ExpressJS application. After having installed Express with npm by executing npm install express, we need to tell Node to retrieve the Express library for us to use. We next create a server, a route (explained momentarily), and a port on which the server will listen. The sys package is imported to give us some status messages. sys.puts is equivalent to console.log.

Listing 9-2. Example ExpressJS Application


express = require 'express'
app = express.createServer()
sys = require 'sys'

app.get '/', (req, res) ->
    res.send('Hello World')
sys.puts "Server started on http://localhost:3000"
app.listen(3000);


Serving Requests with URL Routing

ExpressJS lets you define routes or URL endpoints to which your application can respond. Routes can respond to any of the verbs GET, PUT, POST, and DELETE. Listing 9-3 shows the definition of a route that responds to the base server URL and prints “Hello, World.”

Listing 9-3. Simple ExpressJS Route


app.get "/", (req, res) ->
    res.send("Hello, World")


You can create separate blocks for each HTTP verb, or you could use all, which routes all verbs through the same block of code.

ExpressJS’s routing API is fairly flexible, allowing you to route to a base URL, as shown in Listing 9-3, or to a static URL and formatted URLs, as shown in Listing 9-4. The use of a colon before a portion of the endpoint means that component will be exposed as a request parameter property. A question mark at the end of a fragment tells ExpressJS that portion of the endpoint is optional. We can separate the root and extended routes as we did with /about and /about/:id or combine them with conditional logic as we did with /profile/:id?. The final example, /updateProfile, uses a POST. It demonstrates how we can get access to the transmitted data by inspecting the req.body object.

Listing 9-4. Advanced Routes


express = require 'express'
app = express.createServer()
sys = require 'sys'
app.configure ->
    app.use express.logger()
    app.use express.cookieParser()
    app.use express.bodyParser()
    app.use express.static(__dirname + '/public')

app.get '/about', (req, res) ->
    res.send('About Page')

app.get '/about/:id', (req, res) ->
    id = req.params.id
    res.send('About Page for '+id)

app.get '/profile/:id?', (req, res) ->
    id = req.params.id
    if (id is undefined)
        res.send("Profile not found")
    else res.send(id+"'s Profile")

app.post '/updateProfile', (req, res) ->
   newData = req.body
   name = req.body.name
   doStuffWithData(data)
   res.send("Update successful.")

sys.puts "Server started on http://localhost:3000"
app.listen(3000);


ExpressJS has a middleware layer exposed to it by the bundled Connect (http://senchalabs.github.com/connect) library. Connect’s middleware layer provides reusable functions for common things an application might need, including but not limited to authentication, logging, session management, cookies, and parsing request bodies. We’ve already written a few of the most basic middleware functions in our sample applications. Listing 9-4, which shows some advanced routes and uses the bodyDecoder middleware to enable seeing the POST request’s body. It also specifies that there would be a static directory called public that serves up assets such as client-side JavaScript, images, CSS, and HTML.

Managing Sessions

In order to use sessions, we need to include a couple middleware components. You can see a sample route that uses sessions in Listing 9-5. cookieDecoder and bodyDecoder are required to be instantiated before session. The secret parameter of session helps the middleware to encode the GUIDs for the session IDs. The session object is created seamlessly for us behind the scenes, and we can attach [serializable] objects to it, as well as modify or remove them. If we don’t specify a backing store, it defaults to in-memory. Although not bundled directly with either Connect or ExpressJS, backing stores are available for virtually every popular database.

Listing 9-5. Logging Session Visit Count


app.configure ->
  app.use(express.cookieParser())
  app.use(express.bodyParser())
  app.use(express.session({secret:'asdf'}))

app.get '/', (req, res) ->
    if req.session.visitCount == undefined
      req.session.visitCount = 1
    else
      req.session.visitCount = req.session.visitCount + 1;
      res.send "Session ID:"+req.session.id+"<br/>"+'You have visited this page '
+ req.session.visitCount + ' times';


Understanding the ExpressJS Application Structure

ExpressJS is pretty  liberal when it comes to application structure, letting you set where to find the specific directories, as demonstrated in the following snippet:

# Set directory for views to render
app.set 'views', __dirname + '/views'

Be that as it may, all the applications in this chapter will follow the structure shown in Figure 9-1. It provides a clear separation of concerns and makes it easier to learn ExpressJS. Views define how the information will be presented to the user. The public directory holds static assets that the application will serve to users, such as CSS, images, and JavaScript files. All applications should be tested in some shape or form, and the tests directory will house those tests. Last but not least is the app.coffee (or alternatively app.js) file. It initializes the server with our core logic for the application and sets up routes, logging, middleware, templating, and so on. It should be noted that executing the express script in an empty directory will create a sample app.js file along with views, public, logs, and tests directories.

Figure 9-1. ExpressJS application structure

Image

Templating HTML with CoffeeKup

CoffeeKup is a templatinge engine for HTML that allows you to create HTML entirely by using CoffeeScript. Provided that Node.js is already installed, CoffeeKup can be installed by executing

npm install coffeekup

on a command-line prompt. Frameworks that support CoffeeKup as a rendering engine convert the CoffeeScript code at runtime into HTML and JavaScript. Listing 9-6 shows the CoffeeKup code to create a simple HTML page and set the content of a div to “Hello World!” Notice that we can assign and use variables inside the code.

Listing 9-6. A Simple CoffeeKup Page


@title = "Hello World"
doctype 5
html ->
    head ->
        title @title
    body ->
          div id: 'content', ->

          div id: 'fragment', ->
              @body

          coffeescript ->
                  document.getElementById('content').innerText = "Hello World!"


Listing 9-7 shows the HTML code that is generated when the preceding CoffeeKup file is served by ExpressJS.

Listing 9-7. Generated HTML Code


<!DOCTYPE html>
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <div id="content"></div>
        <div id="fragment"></div>
<script>;(function () {
            return document.getElementById('content').innerText = "Hello World!";
          })();
        </script>
    </body>
</html>


So far, we have seen CoffeeKup generating full pages. Using it as a templating engine in ExpressJS means we can modularize our UI code and render fragments of pages on the fly using a set of common layouts. We can register CoffeeKup with ExpressJS by including the following snippet in our application file:

app.register '.coffee', require('coffeekup')
app.set 'view engine', 'coffee'

The code tells ExpressJS that, by default, we will be using views that are CoffeeScript files and that we can omit using the file extension when referring to them. Using the full filename allows us to mix layouts using the other libraries: Jade, Haml, JQuery Templates, and EJS. When a fragment file is referenced, the system looks for an associated layout file that describes the outer structure of the HTML to display. In the case of CoffeeKup, it searches for layout.coffee in the views directory, failing over to other directories if it is not found. Listing 9-6 is an example of what could be defined in a layout file. You might have noticed that there is a @body variable that hasn’t been assigned a value and doesn’t seem to appear in the generated HTML. When rendering a view, ExpressJS takes the content of the view we are rendering and puts it into the layout file as the value of the body variable. So given a view fragment named index.coffee that contains the code

div ->
    "The time is now #{new Date()}"

we could set up a route on /getTime, as shown in Listing 9-8, that, when visited, would return the composition of index.coffee and layout.coffee.

Listing 9-8. Creating a Route for /getTime


app.get "/getTime", (req, res) ->
    util.log "Client visited /getTime"
    res.render 'getTime'


View variables can also be passed at runtime. If we wanted to pass the time zone to the getTime page, we could modify the view as shown in Listing 9-9, passing the desired variables in a map property named context.

Listing 9-9. Modified Route


app.get "/getTime", (req, res) ->
    util.log "Client visited /getTime"
    res.render 'getTime', context:{timezone: 'America/Los Angeles'}


Persisting Data with Caching

In most applications, there will be a backing database of some sort to store data. Like a computer hard drive, reading the data is fairly quick—the time consuming part is finding the data. Caching allows us to have quicker access to data. Once data is accessed, it’s fairly likely that it will be accessed in the future. Caching sets aside a bit of memory to store recently accessed objects. Caches don’t have infinite storage; otherwise, they lose their advantage over databases. Caching strategies for choosing which objects to eject can vary greatly. Many of them center around some variant or combination of ejecting the Least Frequently Used (LFU), Least Recently Used (LRU), and Most Recently Used (MRU) objects. Alternatively, the cache can eject based on a period of inactivity, as is done with website sessions.

The node-cache project (https://github.com/ptarjan/node-cache) provides some basic functionality that we can further build upon. node-cache’s API is pretty straightforward, providing the six functions listed in Table 9-1.

Table 9-1. node-cache API

Image

Managing Client/Server Communication

In the early days of the Web, back when using the “World Wide” part of the name was still in vogue, interaction was pretty limited. You clicked a link, and it send you somewhere. You filled in a long form of data, clicked Send, and were routed to a new static page. Slowly but surely this static means of interaction became more dynamic as more websites began using AJAX to update their information without redirecting the user. The innovation didn’t end there. Further advances brought server-side push, long polling, and has culminated in TCP sockets being supported natively in web browsers. Web Sockets in their current incarnation are new, but the concept itself is not. Early Java applets were able to open up a socket between the server and the client’s browser.

Communicating with Socket.IO

Socket.IO is librarys for Node.js that seeks to provide a common interface that different transport mechanisms can use regardless of the destination or source of the message.

Socket.IO supports communicating using the following technologies:

• Native Web Sockets

• Adobe Flash sockets

• AJAX long polling

• AJAX multipart streaming

• Forever IFrames

• JSONP polling

This combination of transports allows Socket.IO to support almost all versions of every major desktop and mobile browser. In addition to the official Node.js implementation, there are unofficial server implementations for Java, Python, Google Go, and Rack (Ruby).

Setting Up a Simple Socket.IO Application with Express

Listings 9-10 and 9-11 show the server and client sides for a simple Socket.IO application that echoes back whatever the server or client send each other. The server has an event called connection that it listens for. When that even is fired, it prints to the console that a new client has connected and it registers a function to be executed whenever there is a message. It is empty in the code sample, but you can see that responding to the disconnect event is also possible.

Listing 9-10. Server Code for Hello World


# Setup socket.io
socket = io.listen app
socket.on 'connection', (client) ->
    sys.puts "new client connected."
    client.on 'message', (data) ->
        sys.puts data
        client.send data
    client.on 'disconnect', ->


Listing 9-11 shows the other side of the equation with the client’s code. After including the requisite JavaScript file, we instantiate a new socket and register functions for the connection and message events like we did on the server.

Listing 9-11. CoffeeKup Template for Hello World


doctype 5
html ->
    head ->
        title "#{@title}"
        script src: '/js/socket.js'

    body ->
          coffeescript ->
              socket = new io.Socket 'localhost'
              socket.connect()
              socket.on 'connection', ->
                           console.log 'Connected.'
              socket.on 'message', (data)->
                   console.log data
                    socket.send "Hello World"


Making Web Sockets Simpler with NowJS

Socket.IO is great for simple communication, but complex conversations over sockets would require you to do something such as formatting messages as JSON, sending them over the socket, and decoding/encoding them on each side. As the number of conditions we have to handle grows, making our mini-language for all the features of our application becomes untenable. It would be so much easier for the server to be able to call the specific methods in the client’s domain without complex message passing—and vice versa. With NowJS, we can do just that.

NowJS is a client- and server-side JavaScript library that enables clients and servers to make remote procedure calls over a Web Socket pipeline. NowJS uses Socket.IO for the communications bit by wrapping it with several important features. There are two shared namespaces, called everyone.now and now. The former is implemented on the server side, and the latter is the client’s interface to NowJS. Any variables or object graphs set within the everyone.now namespace will be shared among all clients and the server. If the server calls a function in the everyone.now namespace and it exists on the client, it will attempt to execute the function of the same name in the client’s now namespace. An example will help illustrate this better. One of the common tasks we will perform a lot on our game server is to retrieve player data. We are going to use NowJS to have the client request a list of the currently connected players. On the server we have a function in the everyone.now namespace called getPlayerList. It takes a callback to the client to return the code as its only parameter. Listing 9-12 shows only the code that would change from an initial application file.

Listing 9-12. NowJS Demo Application File


nowjs = require("now")

app = express.createServer()
everyone = nowjs.initialize(app)

everyone.now.getPlayerList = (callback) ->
    players = ['Jake','John','Cathy']
    callback(players)


Alternatively, we could have defined a function on the client to be called by the server. The server could have used this to communicate rather than a callback. Our client-side code, after the required now.js script file has been included, defines a function called getPlayers that calls the server’s getPlayerList function and prints the results to the console. The last snippet of code is the call to now.ready. The ready function allows us to specify code to be executed when the connection has been successfully made to the server. This is shown in Listing 9-13.

Listing 9-13. NowJS Client Code


doctype 5
html ->
    head ->
        title "#{@title || 'NowJS Demo'}"
        script src: '/nowjs/now.js'

    body ->
    coffeescript ->
        window.getPlayers = ->
            now.getPlayerList (data) ->
                console.log(data)

        now.ready ->
            console.log('ready')
    div ->
        input type:'button', value:'Get Players', onclick:'getPlayers();'


Debugging Node Applications

With all those asynchronous callbacks firing, being able to effectively debug our applications becomes extra important. If we need to find the value of a single object for a short time, then console debugging (where we print out data to the console) might work. The sys object has a function called inspect that will convert all the details of an object to a String. We can use that with sys.put to output to the command line, as in the following snippet:

sys.put(sys.inspect(obj))

Great in a pinch, console debugging becomes untenable when you are trying to check flow control or when an error is several levels down. For those more advanced cases, a full-fledged debugger is more helpful, giving more control over the running application. The node-inspector project (https://github.com/dannycoates/node-inspector) provides this functionality for Node applications. You install node-inspector by executing the following command:

npm install node-inspector

You can then start the debugger with the following command:

node-inspector &

Now node-inspector is ready to start monitoring your applications. The last steps are to start Node in debug mode by executing either

node –debug <app file>

or

coffee –nodejs <app file>

and navigating to the address provided by node-inspector (generally http://0.0.0.0:8080/debug?port=5858) in a WebKit-based browser. There, you’ll see the Developer Console repurposed to show details about the application. Turn back to Chapter 1, “Introducing HTML5,” if needed, for a refresher. One really cool feature of node-inspector is that the Scripts tab shows you CoffeeScript files as the compiled JavaScript that will actually run on the server side.

Creating a Game Server

A typical multiplayer game server includes several areas for users to interact, at minimum, a game lobby, specific game rooms or areas, and a means for users to chat with one another. In the coming sections, we will tackle each of these areas.

Making the Game Lobby

The game lobby is first part of our application that the users see. From the lobby, the users can see games in progress that they can join, chat with other users, and create their own game rooms.

Listing 9-14 shows the CoffeeKup code needed to implement the application’s landing page. On the left, we have a large container that will hold our Canvas. On the right is a chat window for our players to communicate with others.

Listing 9-14. Application Landing Page Code


@title = 'Game Lobby'
div style:'float:left;height:600px;width:800px', ->
div style:'float:right;',->
    textarea id:'chat', rows:'10', columns:'50', style:'width:200px;height:550px'
    br ->
    input type:'text', columns:'40', id:'message'
    input type:'button', value:'Send',
        onclick:"distributeMessage($('#message').get(0).value)"


When the user clicks the Send button, it passes the content of the message text area to our distributeMessage function, which in turn calls the function of the same name on the server. receiveMessage is invoked by the server on all the clients when a client sends out a distributeMessage call. We can see the client code in Listing 9-15.

Listing 9-15. Client-Side Code to Send and Receive Chat Messages


window.now.receiveMessage = (name, message) ->
    val = $('#chat').get(0).value
    val += name + ':' + message + ' '
    $('#chat').get(0).value = val
    console.log('Received message: ' + message + ' from: '+name)

window.distributeMessage = (message) ->
    if now.name is 'Unknown'
now.name = prompt("What is your name?")
    now.distributeMessage(message)


Notice in Listing 9-16 that the server only has to implement the function where it is doing work. It calls receiveMessage with the blind faith that it will exist on the clients.

Listing 9-16. Server-Side Code to Send and Receive Chat Messages


everyone.now.distributeMessage = (message) ->
    console.log("Received message:"+ message + " from: "+this.now.name)
    everyone.now.receiveMessage(this.now.name, message)


Creating Game Rooms with NowJS Groups

From the lobby, players can select an existing game room they would like to enter or create one of their own. Each game room has properties with a preset range, such as the game in play for that room, the maximum set of players, and the game logic for the game. Shortly after all participants leave a game room, the server removes it from view.

NowJS allows you to segregate users by using groups. Groups have their own set of now objects that the clients share with the server. Groups make it so that the people not in our game room don’t get bombarded with messages and notifications. getGroup retrieves an existing group with the name of the passed parameter or creates one. Listing 9-17 shows this in action in the bit of code we use to create our game rooms. After the game room is created, player entrances and exits are announced to the people in the room thanks to the on 'connect' and on 'disconnect' events.

Listing 9-17. Creating a Game Room


everyone.now.createRoom = (roomName, callback) ->
console.log("Created room: "+roomName)
    group = nowjs.getGroup(roomName)
    group.on 'connect', (clientId) ->
        group.now.receiveMessage(this.user.clientId +" joined the game room.")
    group.on 'disconnect', (clientId) ->
        group.now.receiveMessage(this.user.clientId + " left the game room.")
    gameRooms.push(group)
    callback(group)


Managing Game Participants and Moving Between Game Rooms

Now that we have our game rooms, we need people to put into them. That is where our game participants come into play. We can have two types of game participants: players and watchers. Players engage in the game and are affected by the game logic, whereas watchers can merely watch the play as the game unfolds. Either type of participant is able to broadcast messages to others in the game room. Listing 9-18 shows how the code we have wrapped around the NowJS objects to add or remove a user from a game room.

Listing 9-18. Moving Between Game Rooms


everyone.now.joinRoom = (roomName) ->
    group = nowjs.getGroup(roomName)
    group.addUser(this.user.clientId)

everyone.now.leaveRoom = (roomName) ->
    group = nowjs.getGroup(roomName)
    group.removeUser(this.user.clientId)


Managing Game Play

In this section, we get to the nitty-gritty of our game server. We will code the game logic for the Tic-Tac-Toe game we coded in Chapter 5, “Creating Games with the Canvas Tag.”

Moving a game from running locally to running on a server is much like the transition in moving a web application from a traditional web framework to a purely event-driven model. Whereas you could just busy-wait for a single application running locally, on something like Node, you instead have to keep track of the dozens or hundreds of games at once. We will use the database and caching to allow us to manage many games at a time and to scale.

Back in Chapter 5, we used the Canvas to code some of the basics of tic-tac-toe, and in Chapter 4, “How Games Work,” we coded the artificial intelligence powering it. We can use our game server to finish that effort and put the two pieces together.

After a room is created, users can create a new game. The now object for that game room stores the current state of the game board, which player is X and which is O, and whose turn it is. When a player takes his or her turn, the move is sent over the wire to the server’s now object to validate and apply it if valid. Another change from the previous version is the use of the cache to store the current game state. Although groups have a now object, they cannot have arbitrary objects attached to it like the everyone.now object can. The cache also solves the problem of everything in the now object being shared between all clients. If we were to, for instance, store all the cards for a game in the now object, nothing would keep a less-than-honest player from easily peeking or changing the cards in his or her hand. You can see this code in Listing 9-19.

Listing 9-19. Validating and Applying Player Moves


    everyone.now.completeTicTacToeMove = (room, x, y, player) ->
        rooms = cache.get("rooms")
        room = rooms[roomName]
        group = nowjs.getGroup(room)
        roomState = cache.get(room)
        board = roomState.board

        otherPlayer = if player is 'X' then 'O' else 'X'
        if board[x][y] is '-'
            board[x][y] = player
            # check for win

            cache.put(this.now.room, roomState)
        else
            roomState.message = 'Player #{player}, Please try again.'
            cache.put(room, roomState)
            group.now.receiveGameState(roomState)


We could just as easily wire in a computer player by having it calculate its moves and adjusting the game board on the server’s now object. The receiveGameState function operates in a similar way to the receiveMessage function does with chat. The state on the server is treated as the source of truth. After each action, the game state is pushed to all the clients and they redraw their screens. The solution we used for tic-tac-toe works well for games that can be represented concisely, but fails as the scale of players goes up.

One way to handle scale, especially for free-roaming games such as arena-style first-person shooters, is to move most of the interaction to the server. Whereas in the tic-tac-toe game server example, the user could run specific functions on the server, albeit with some basic verification, other games might need more safeguards against cheating. One way to do that is to run most of the logic on the server and push updates to the client. On the client side, the client no longer can run functions—it can only send updates of what input keys were pressed. It is the server’s job to make sense of what a button being pressed means. RealtimeMultiplayerNodeJS (https://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs) exposes many of these features. Although it is outside the scope of this book to cover in any depth, it bears mentioning for the benefit of those who have their hearts set on massive multiplayer Scrabble or similar technology.

Summary

This chapter provided a crash course in the server-side JavaScript web framework and the surrounding ecosystems. You learned how to use ExpressJS to create routes for web pages and also how to template HTML to be served when the specific route is visited. You also learned how to use Web Sockets with the NowJS and Socket.IO projects for real-time communication between the server and clients. Next, we dove into data persistence using a simple cache. We finished this chapter with a tech demo around creating a game server.

Exercises

1. Write code that creates a NowJS group and prints “Welcome” to the console when someone joins the group.

2. What is the n command to install Node.js version 0.4.8?

3. Create a route that responds to the “location” and takes a parameter called city.

You can download chapter code and answers to the chapter exercises at www.informit.com/title/9780321767363.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.102.160