HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Graphical game application

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gamegraphicalapplication

Problem

I'm writing my first JavaScript application (no 3rd party libraries used) and looking for some high-level guidance on how to better organize the code into separate classes & files.

Here is a simplified/contrived example to illustrate my current organizational layout/problems:

game.js:

/* Lots of constants, enums, etc defined here.
   Can't define them inside Game function as
   they are used by Graphics and ui_player classes too.
*/

function Game(ui) {

    /* Lots of things defined here which are exclusive to Game class. */

    var graphics = new Graphics();
    graphics.load();
    ui.setCanvas(graphics.canvas);

    this.start = function() {
        mainLoop();
    };

    function mainLoop() {
        /* ... */
        ui.player.updateAbc(abc);
        /* ... */
        paint();
    }

    function paint() {
        graphics.renderAll()
    }

    /* ... */
}


graphics.js:

function Graphics() {

    /* Some constants defined here that are exclusive to Graphics class. */

    var canvas = this.canvas = ...;
    var ctx = canvas.getContext("2d");

    /* ... */

    this.renderAll = function() {
        ctx.drawImage(...);
    };

    /* ... */
}


ui.js:

/* Lots of constants related to UI classes here. */

function ui() {
    var div = this.div = ...;
    var canvas;            

    /* ... */

    var player = this.player = new ui_player();
    div.appendChild(player.div);

    this.setCanvas(c) {
        if (canvas)
            div.removeChild(canvas);
        canvas = c;
        div.appendChild(canvas);
    };

    /* ... */
}


ui_player.js:

function ui_player() {
     var div = this.div = ...;

     /* ... */

     this.updateAbc = function(abc) {
         div.innerHTML = abc;
     };
 }


main.js:

ui = new ui();
 game = new Game(ui);
 game.start();


What I dislike about this:

  • I feel that my scopes are clumsy.



  • I dislike the fact that I have constants in global scope because they are shared across multiple c

Solution

Using a closure

You can avoid putting constants in the global scope by wrapping everything in another closure, i.e. the code produced after the compiling process should look something like this:

(function() {
/* all your definitions here */
function ui() {
    // code here
}

function Game(ui) {
    // code here
}

function Graphics(ui) {
    // code here
}
}).call(this);


Thereby all the functions have access to your constants without cluttering the global namespace.

Supporting RequireJS/AMD

RequireJS is a library that aims to give you the possibility to import modules, something JavaScript lacks by default. It does so using 'dependency injection', i.e. passing all the things code requires to execute as parameters to a wrapping function. (You're already doing this in your code - kudos! Even though you seem not to like it, definitely stick to it!) This is considered a best practice in many programming languages - including JavaScript, duh - as it allows for nice modularization of code and great testability by replacing other modules that you don't want to test by mock implementations that might always return the same input, thereby you are able to test just parts of your code and locate errors more quickly. This is called unit testing - something you will never want to miss again.

By supporting RequireJS natively you will save your users from a lot headaches figuring out how exactly to get references to your code.

Doing so is quite easy, you check if the define function exists and use it or release everything to the global scope if it doesn't:

var lib = {
    ui: ui,
    Game: Game,
    Graphics: Graphics
}

if (typeof define === "function") {
    define("mylib", [], function() { return lib; });
} else {
    window.mylib = lib;
}


EDIT: When I wrote this I thought you wrote that you were creating your first library - not application. That being the case you can ignore the last paragraph, the first in this section still applies, though.

Building your library as a set of AMD modules

You can leverage the power of RequireJS/AMD for your own application. Just use any directory structure you like and use dependency injection to seperate them into files nicely. The RequireJS optimizer can then put them in a single file, you would still need RequireJS, though. Meet almond! It is only about 1kb minified+gzipped and defines a subset of the functionality of RequireJS that suffices to still use AMD modules inside a single file, which is ultimately what you want: a single file you can include in their HTML or load using Require. Now, unfortunately, this introduces another step in going from your source to a distributable JS file.

Using a sophisticated build tool

Because nobody wants to do 10 steps manually each time you change your code and want a new build, sophisticated build tools were created that invoke the compiler(s), do code linting, etc. I personally recommend Grunt. It is IMHO the best and most flexible option out there and big libraries, e.g. jQuery, use it as well.

Don't reinvent the wheel

There already are great libraries that can do some of the heavy lifting for you. While for a first application it is a good idea to do things yourself to learn how they work internally, once you start working on production code you want to rely on existing libraries, because

  • that leaves you more time to work on your app and make it better



  • they make your code more error proof:



  • many libraries have been around for quite a long time and hardly have bugs



  • bugs that are found will most likely be fixed quickly (and by other people, see (1))



  • many libraries will make some design decisions for you. While you might think that cuts your freedom as a programmer (it certainly does), it also has a great advantage: Other people have spent a great deal on working out a good concept that will probably make your app easier to maintain in the end. So just choose one you like and go with it...



  • let's face it: people who dedicate their whole time to working on a library probably have better implementations of stuff than you, so that might make your app faster



Additional Points

I will add further points here with edits, but wanted to give you access to existing sections asap ;)

Code Snippets

(function() {
/* all your definitions here */
function ui() {
    // code here
}

function Game(ui) {
    // code here
}

function Graphics(ui) {
    // code here
}
}).call(this);
var lib = {
    ui: ui,
    Game: Game,
    Graphics: Graphics
}

if (typeof define === "function") {
    define("mylib", [], function() { return lib; });
} else {
    window.mylib = lib;
}

Context

StackExchange Code Review Q#39761, answer score: 5

Revisions (0)

No revisions yet.