Let's Make a Drawing Game with Node.js

Download

By now you have probably heard of node.js. It is an asynchronous web server built ontop of Google's V8 JavaScript engine (the same one that makes Chrome lighning fast). Using node, you can write scalable web services in JavaScript, that can handle a huge number of simultaneous connections, which makes it perfect as the backend of games, web chats and other real time tasks.

The Idea

Today we will be making a simple online drawing game. The app will let users draw on the page by dragging and moving their mice, and will display the results on a large canvas element. What sets is apart from all the other similar experiments though, is that people will see each other in real time as they do so. To achieve this, we will leverage the socket.io library for node.js, which uses a range of technologies from websockets to AJAX long polling to give us a real time data channel. Because of this, the example works in all modern browsers.

Installing node.js

To run the game you will need to install node.js. It shouldn't take more than a few minutes and is fairly straightforward. You can go ahead and download the installers from the official site. Or you can run this set of commands if you wish to install it from your terminal in Linux or OSX (you only need to run the first script: node-and-npm-in-30-seconds.sh).

After you finish installing, you will also get access to npm, the node package manager. With this utility you can install useful libraries and bits of code that you can import into your node.js scripts. For this example, we will need the socket.io library I mentioned above, and node-static, which will serve the HTML, CSS and JS files of the drawing application. Again, open up your terminal (or a new command prompt window if you are on Windows) and write the following command:

npm install [email protected] node-static

This shouldn't take more than a few minutes to complete.

Running the Application

If you want to just grab the files and test the app on your computer, you will need to download the archive from the button above, and extract it somewhere on your hard drive. After this, open a command prompt / terminal and navigate to the folder (of course you remember how the cd command works, don't you?). After this, type this command and hit return:

node app.js

You should be greeted with a socket.io debug message (otherwise probably your path is wrong; keep practicing with that cd command!). This means that everything is up and running! Now open http://localhost:8080 and you should see your very own copy of the demo. Nice!

These instructions also apply if you are following the steps of the article and are building the app from scratch. Which brings us back to the tutorial:

The HTML

The first step is to create a new HTML document. Inside it, we will put the canvas element which users will be drawing upon, and a div for holding the mouse pointers. Each mouse pointer will be a div with the .pointer css class that is absolutely positioned on the page (we won't be discussing the styling in this article, open assets/css/styles.css to take a look).

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Node.js Multiplayer Drawing Game | Tutorialzine Demo</title>

        <!-- The stylesheets -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>
        <div id="cursors">
            <!-- The mouse pointers will be created here -->
        </div>

        <canvas id="paper" width="1900" height="1000">
            Your browser needs to support canvas for this to work!
        </canvas>

        <hgroup id="instructions">
            <h1>Draw anywhere!</h1>
            <h2>You will see everyone else who's doing the same.</h2>
            <h3>Tip: if the stage gets dirty, simply reload the page</h3>
        </hgroup>

        <!-- JavaScript includes. Notice that socket.io.js is served by node.js -->
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
        <script src="assets/js/script.js"></script>

    </body>
</html>

You can see that the canvas is set to a fixed width of 1900px and height of 1000px, but users with smaller displays will see only a part of it. A possible enhancement would be to enlarge or reduce the canvas in relation to the screen size, but I will leave that to you.

For the real-time communication channel between the users's browser and node.js to work, we need to include the socket.io library in both places, however you won't find the socket.io.js file included in the bottom of index.html in the download archive. This is because socket.io intercepts requests to /socket.io/socket.io.js and serves it itself so you don't have to explicitly upload this file with your application.

drawing-game.jpg
Draw Anywhere!

The Client Side

In other tutorials, we would usually name this section JavaScript, but this time we have JavaScript on both the client (the person's browser) and the server (node.js), so proper distinction must be made.

The code you see below runs in the person's browser. It uses socket.io to connect to the server and notifies us when an event occurs. That event is a message emitted by other clients and relayed back to us by node.js. The messages contain mouse coordinates, unique id for the user, and whether they are drawing or not in the moment.

assets/js/script.js

$(function(){

    // This demo depends on the canvas element
    if(!('getContext' in document.createElement('canvas'))){
        alert('Sorry, it looks like your browser does not support canvas!');
        return false;
    }

    // The URL of your web server (the port is set in app.js)
    var url = 'http://localhost:8080';

    var doc = $(document),
        win = $(window),
        canvas = $('#paper'),
        ctx = canvas[0].getContext('2d'),
        instructions = $('#instructions');

    // Generate an unique ID
    var id = Math.round($.now()*Math.random());

    // A flag for drawing activity
    var drawing = false;

    var clients = {};
    var cursors = {};

    var socket = io.connect(url);

    socket.on('moving', function (data) {

        if(! (data.id in clients)){
            // a new user has come online. create a cursor for them
            cursors[data.id] = $('<div class="cursor">').appendTo('#cursors');
        }

        // Move the mouse pointer
        cursors[data.id].css({
            'left' : data.x,
            'top' : data.y
        });

        // Is the user drawing?
        if(data.drawing && clients[data.id]){

            // Draw a line on the canvas. clients[data.id] holds
            // the previous position of this user's mouse pointer

            drawLine(clients[data.id].x, clients[data.id].y, data.x, data.y);
        }

        // Saving the current client state
        clients[data.id] = data;
        clients[data.id].updated = $.now();
    });

    var prev = {};

    canvas.on('mousedown',function(e){
        e.preventDefault();
        drawing = true;
        prev.x = e.pageX;
        prev.y = e.pageY;

        // Hide the instructions
        instructions.fadeOut();
    });

    doc.bind('mouseup mouseleave',function(){
        drawing = false;
    });

    var lastEmit = $.now();

    doc.on('mousemove',function(e){
        if($.now() - lastEmit > 30){
            socket.emit('mousemove',{
                'x': e.pageX,
                'y': e.pageY,
                'drawing': drawing,
                'id': id
            });
            lastEmit = $.now();
        }

        // Draw a line for the current user's movement, as it is
        // not received in the socket.on('moving') event above

        if(drawing){

            drawLine(prev.x, prev.y, e.pageX, e.pageY);

            prev.x = e.pageX;
            prev.y = e.pageY;
        }
    });

    // Remove inactive clients after 10 seconds of inactivity
    setInterval(function(){

        for(ident in clients){
            if($.now() - clients[ident].updated > 10000){

                // Last update was more than 10 seconds ago.
                // This user has probably closed the page

                cursors[ident].remove();
                delete clients[ident];
                delete cursors[ident];
            }
        }

    },10000);

    function drawLine(fromx, fromy, tox, toy){
        ctx.moveTo(fromx, fromy);
        ctx.lineTo(tox, toy);
        ctx.stroke();
    }

});

The basic idea is that we use socket.emit() to send a message to the node.js server on every mouse movement. This can generate a large number of packets, so we are rate-limiting it to one packet every 30 ms (the $.now() function is defined by jQuery and returns the number of milliseconds since the epoch).

The mousemove event is not called on every pixel of the movement, but we are using a trick to draw solid lines instead of separate dots - when drawing on the canvas, we are using the lineTo method, so that the distance between the mouse coordinates are joined with a straight line.

Now let's take a look at the server!

Server Side

After reading through the client side code you might be worried that the code on the server is even longer. But you will be mistaken. The code on the server side is much shorter and simpler. What it does is serve files when people access the url of the app in their browsers, and relay socket.io messages. Both of these tasks are aided by libraries so are as simple as possible.

app.js

// Including libraries

var app = require('http').createServer(handler),
    io = require('socket.io').listen(app),
    static = require('node-static'); // for serving files

// This will make all the files in the current folder
// accessible from the web
var fileServer = new static.Server('./');

// This is the port for our web server.
// you will need to go to http://localhost:8080 to see it
app.listen(8080);

// If the URL of the socket server is opened in a browser
function handler (request, response) {

    request.addListener('end', function () {
        fileServer.serve(request, response); // this will return the correct file
    });
}

// Delete this row if you want to see debug messages
io.set('log level', 1);

// Listen for incoming connections from clients
io.sockets.on('connection', function (socket) {

    // Start listening for mouse move events
    socket.on('mousemove', function (data) {

        // This line sends the event (broadcasts it)
        // to everyone except the originating client.
        socket.broadcast.emit('moving', data);
    });
});

With this our drawing app is complete!

Done!

Drawing is much more fun when you see other people doing it at the same time. Feel free to play with the example and improve it! Some ideas: it would be awesome to have different brushes, erasers, colors and shapes, or even country flags displayed next to the cursors. Go wild!

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles