Monday, May 31, 2010

Refactoring a Fab.js Stack

‹prev | My Chain | next›

Yesterday, I did not quite finish with my fab.js cleanup. Previously, I had refactored the code in my (fab) game to something along the lines of:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( add_player )
( player_from_querystring )
The init_comet and player_from_querystring (fab) apps got the refactoring love. The broadcast_new and add_player apps are the leftovers that did not get refactored, but they are quite important to the game. They add the new player to the list of players and broadcast the new player to that list of players respectively.

The trouble I ended with last night was getting them both to play nicely with each other and getting the init_comet app to pass along the player information to broadcast_new. It turns out that the latter is fairly easy to resolve. Previously, init_comet only sent comet initialization back to the client, ending with something like:
         ({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
It did not send along the player information because the browser did not need to know it (the browser had added the new player in the first place). The trick is to pass along the player information, but to have broadcast_new use it and not pass it along to the browser. In init_comet, I pass along the player information by passing the object encapsulating it back downstream:
         ({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
(obj);
Then, in broadcast_new, I add a condition to broadcast the new player to the existing players, but send back the comet_initialization to the new player's browser:
function broadcast_new (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
broadcast(comet_walk_player(JSON.stringify(obj.body)));
}

else {
out(obj);
}
return listener;
});
};
}
The add_player app turns out to be a bit trickier. So tricky in fact that I have to eliminate it. It had added the player along with its comet channel to a list of players. The problem I ran into was the comet channel. The comet channel is further upstream than the init_comet binary app:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( add_player )
( player_from_querystring )
This means that any subsequent communication over the channel needed to go through the init_comet channel. Initializing the comet channel (sending new headers and new opening HTML tags) each time a new player is added turns out to be a brilliant way to close all previous comet channels. So clearly I need something as close downstream as possible to handle this.

For now, I add the code that previously went in the add_player app into broadcast_new, making the add-player stack a bit smaller:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( player_from_querystring )
Smaller and with a broadcast_new app with too many responsibilities. Still, I am in a better place than when I started. The init_comet and player_from_querystring apps are now tested and all of my untested code is working (in that I can play the game again) and in one place for future refactoring.

And that is a good place to stop for the night.

Day #120

Sunday, May 30, 2010

Cleaning Up After the Clean-Up

‹prev | My Chain | next›

Up tonight, cleaning up a bit of the mess I have left myself in my (fab) game. Over the past two nights I have refactored the fab.js code initializing a new player in the game. The refactoring was driven entirely by testing (yay!), which means that things got much simpler. It also means I left a few things out.

In this case, there are three things that I omitted: adding the new player to the players known by the backend, broadcasting the new player to players already in the game, and telling the new player about all the existing players in the room. Previously, my app chain looked like:
  ( /^\/comet_view/ )
( init_comet )
( player_from_querystring )
The init_comet is the fab app that has gotten the love (and lost features), so I need to work around it. Adding the player to the fab.js is the easiest part, I can add another (fab) app to accomplish what I need:
  ( /^\/comet_view/ )
( init_comet )
( add_player )
( player_from_querystring )
At some point I'll drive the add_player app via a test (or, at the very least, test it after the fact). For now, I just move the code from the old init_comet app that did not make the refactored cut into add_player:
function add_player (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
out(obj);
if (obj && obj.body) {
var new_id = obj.body.id;
puts("adding: " + new_id);
players[new_id] = {status: obj.body, listener: out};

idle_watch(new_id);
}
return listener;
});
};
}
Nothing too fancy in this binary fab.js app: the listener pulls the player back from upstream (player_from_querystring in this case), adds it to the players list and establishes an idle player watch. I can verify that all is well so far by running the game, adding a flew players and watching for the "adding: " + new_id output:
cstrom@whitefall:~/repos/my_fab_game$ node game.js
adding: foo
...
Now comes the slightly trickier part: broadcasting to all players in the players list. Ignoring, for the time being the content of the app, I would like another middleware (binary in fab.js parlance) app that sits in the stack broadcasting the news of the new player. Something like:
  ( /^\/comet_view/ )
( broadcast_new )
( init_comet )
( add_player )
( player_from_querystring )
Unfortunately, that will not work. The broadcast_new app no longer has access to the player data—init_comet does not return player data, just comet data for the browser. Putting the broadcast_new app further upstream won't help either—broadcasting to all clients before all comet sessions are established is a good way to break things.

At this point, I have to call it a night. Hopefully a fresh perspective tomorrow will provide an answer on how to best approach.

Day #119

Saturday, May 29, 2010

The Right Way to Test Fab.js Binary Apps

‹prev | My Chain | next›

Bleh.

I had a harder than expected time testing my fab.js binary app last night. I ultimately got my test passing and testing what it is supposed to test. But it wasn't pretty.

Recall that I am trying to test a binary (middleware) fab app that sits between an upstream app that returns a player object and downstream (the web client). It stores player information and initiates a comet connection back to the web client:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... more of these body-scripts to push past Chrome's
// buffer
}
return listener;
});
};
}
My first test was meant to test that the first chunk returned downstream contained "<html><body>\n". The test that I ended with last night:
  var upstream = function() { this({body:{id:42}}); },
downstream = init_comet (upstream);

function
bodyRespondsWithStartHtml() {
var out = this,
already_tested = false;

downstream.call(function listener(obj) {
if (!already_tested) out(obj.body === "<html><body>\n");
already_tested = true;
return listener;
});
}
There are a couple of problems there. First, I should not name the return value of init_comet as downstream. I am in the downstream context, but the return value of a binary fab app is a unary app. So I rename it unary:
  var upstream = function() { this({body:{id:42}}); },
unary = init_comet (upstream);

function
bodyRespondsWithStartHtml() {
var out = this,
already_tested = false;

unary.call(function listener(obj) {
if (!already_tested) out(obj.body === "<html><body>\n");
already_tested = true;
return listener;
});
}
The next problem is that already_tested binary. I knew that it was crazy to use that last night, but, for the life of me, I could not figure out how to do without it. I probably could have used a pomodoro break or something because it does not take long for me to figure out the right way to do it tonight:
  var upstream = function() { this({body:{id:42}}); },
unary = init_comet (upstream);

return [
function
bodyRespondsWithStartHtml() {
var out = this;

unary.call(function (obj) {
out(obj.body === "<html><body>\n");
return function listener() {return listener;};
});
}
That is much better. When I call the unary app returned from my init_comet fab app, I set the this variable inside the unary to that anonymous function (this is what javascript's call() function does). This is just normal fab.js stuff—it leverages call like crazy to make app chaining work (which is how jQuery does it as well). The unary app then replies back downstream via this anonymous function, at which point I can test it.

The last line is a bit trickier (and what I could not figure out last night):
        return function listener() {return listener;};
What is the point of returning a function that does nothing other than returning itself? The answer is that I am not sending a single chunk back downstream. After sending the initial HTML document, I send several more chunks:
out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... more of these body-scripts to push past Chrome's
// buffer
The only reason I do this is to make sure that Chrome starts responding to comet responses. It is not really necessary behavior—it only handles yet another browser idiosyncrasy. Still, it is worth knowing how to test. Sending multiple chunks like that is perfectly normal fab.js stuff. After sending the first chunk downstream, the return value is called with more HTML, the return value of that is again called with more HTML, and so on. If I wanted to tell downstream that I was done with it, I would have called it with no arguments. As it is, the last call in that stack is more output:
         // ... more of these body-scripts to push past Chrome's
// buffer
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
This signals to downstream to expect more output, which is how I get the fab.js backend to communicate player movement later (aka comet calls). At any rate, it is this call stack that necessitates the function that returns itself in my test:
        return function listener() {return listener;};
Each return value is called by the init_comet app. For this unit test, I do not care about anything else in that stack, which is why my test ignores it.

Armed with that knowledge, I can now do all sorts of testing of binary fab.js apps. For instance, I can test that the second chunk is Chrome comet padding:
    function
bodyPadsCometSoChromeWontIgnoreMe() {
var out = this;
unary.call(function(obj) {
return function second_listener(obj) {
out(/123456789/.test(obj.body));
return function listener() {return listener;};
};
});
}
I ignore the response to the anonymous function that I supply to the unary. I do my test in the second listener, then ignore the remaining responses. With my test harness, I now have two passing tests:
cstrom@whitefall:~/repos/my_fab_game$ node test.js
Running 2 tests...
bodyRespondsWithStartHtml: true
bodyPadsCometSoChromeWontIgnoreMe: true
Done. 2 passed, 0 failed.
I am really getting the hang of this stuff. I have some refactoring on tap for tomorrow, then I would like to play with Cucumber's newly found integration on the v8 javascript engine.

Day #118

Friday, May 28, 2010

Testing a Binary Fab.js App (Almost)

‹prev | My Chain | next›

I think I have a handle on testing simple upstream fab.js apps. Tonight I will see if that ability transfers to testing binary (aka middleware) apps.

I have a middleware app that translates a new player response into a comet response:
  ( /^\/comet_view/ )
( init_comet )
( player_from_querystring )
The actual app looks like a normal, albeit large fab.js binary app:
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

// Send a bunch of output to initialize the comet session in some browsers
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... many more of these lines to push pass Chrome's buffer

// Tell all other players about the new one
for (var id in players) {
downstream = downstream({body: comet_walk_player(JSON.stringify(players[id].status))});
}

// Add the new player to the player list
var new_id = obj.body.id;
puts("adding: " + new_id);
players[new_id] = {status: obj.body, listener: out};

// Establish an inactivity watch for the user
idle_watch(new_id);

// tell the user the game is aware of it
broadcast(comet_walk_player(JSON.stringify(obj.body)));
}
return listener;
});
};
}
Actually, wow. There is a lot going on there. I obviously didn't TDD that app. Let's see if I can drive a series of smaller apps. I break the init_comet app out of my main game.js application and into lib/init_comet. Since this is a binary app, I start with a skeleton binary app along with the initial comet reply:
var puts = require( "sys" ).puts;

function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

// Send a bunch of output to initialize the comet session in some browsers
var downstream = out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

({body: "<script type=\"text/javascript\">\"123456789 123456789 123456789 123456789 123456789 12345\";</script>\n"})
// ... many more of these lines to push pass Chrome's buffer
}
});
}
}
Next, I write my test. For now, I want to see if I can test that the middleware returns an HTML document back downstream:
exports.tests = ( function() {
var upstream = function() { this({body:{id:42}}); },
downstream = init_comet (upstream);

return [
function
bodyRespondsWithStartHtml() {
var out = this;
downstream.call(function(obj) {
out(obj.body == "<html><body>\n");
});
}
];
})();
I create a dummy upstream app. For now, it only needs to reply with a body. I then get the result of calling my init_comet app with that upstream app. If I have done this right, init_comet will call the supplied callback. I then send the result of my test back to the test harness with out. Unfortunately, when I run this, I get:
cstrom@whitefall:~/repos/my_fab_game$ node test.js
Running 1 tests...
bodyRespondsWithStartHtml: true

TypeError: undefined is not a function
at CALL_NON_FUNCTION (native)
at /home/cstrom/repos/my_fab_game/lib/init_comet.js:50:21
at listener (/home/cstrom/repos/my_fab_game/lib/init_comet.js:9:26)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:42:31)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:7:16)
at Function.bodyRespondsWithStartHtml (/home/cstrom/repos/my_fab_game/lib/init_comet.js:49:13)
at /home/cstrom/repos/my_fab_game/test.js:14:10
at Array.forEach (native)
at test (/home/cstrom/repos/my_fab_game/test.js:13:15)
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test.js:36:1)
Ah. Not too bad. The test passes, but that backtrace... Ew.

The init_comet app calls the downstream app with "<html><body>\n" and then calls the result of that with some of that comet buffer breaker data. I should simply need to return a function to be called:
exports.tests = ( function() {
var upstream = function() { this({body:{id:42}}); },
downstream = init_comet (upstream);

return [
function
bodyRespondsWithStartHtml() {
var out = this;
downstream.call(function(obj) {
out(obj.body == "<html><body>\n");
return function() {};
});
}
];
})();
Again, however, I get the same error:
cstrom@whitefall:~/repos/my_fab_game$ node test.js
Running 1 tests...
bodyRespondsWithStartHtml: true
Done. 1 passed, 0 failed.
TypeError: undefined is not a function
at CALL_NON_FUNCTION (native)
at listener (/home/cstrom/repos/my_fab_game/lib/init_comet.js:12:125)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:30:31)
at Function.<anonymous> (/home/cstrom/repos/my_fab_game/lib/init_comet.js:7:16)
at Function.bodyRespondsWithStartHtml (/home/cstrom/repos/my_fab_game/lib/init_comet.js:39:13)
at /home/cstrom/repos/my_fab_game/test.js:14:10
at Array.forEach (native)
at test (/home/cstrom/repos/my_fab_game/test.js:13:15)
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test.js:36:1)
at Module._compile (node.js:639:23)
I finally get this passing by returning the same listener:
    function
bodyRespondsWithStartHtml() {
var out = this,
already_tested = false;

downstream.call(function listener(obj) {
if (!already_tested) out(obj.body === "<html><body>\n");
already_tested = true;
return listener;
});
}
I must confess that I do not understand why returning the listener as opposed to an anonymous function worked. Something to investigate tomorrow.

Day #117

Thursday, May 27, 2010

Fab.js Testing

‹prev | My Chain | next›

Last night, I wrote my first legitimate fab.js test. It was a hard fought battle to figure out exactly how to do it, but ultimately I got it right. Tonight I am going to do it even better.

I refactor my test thusly:
exports.tests = ( function() {
var head = { url: { search : "?player=foo&x=1&y=2" } };

return [
function
bodyRespondsWithPlayer() {
var player;
function downstream (obj) { if (obj) player = obj.body; }

var upstream_listener = player_from_querystring.call(downstream);
upstream_listener(head);

this(player.id == "foo");
}
I think this is about as good as this test can be. Getting it here has given me a clearer understanding of what fab.js does.

Starting from the top, the downstream function behaves like a downstream (closer to the web client) fab.js app. It is meant to be called by the upstream app with a response object. For the purposes of my test, I expect that the response object will encapsulate the the player in the response body so I squirrel it away in a variable:
      var player;
function downstream (obj) { if (obj) player = obj.body; }
At this point things get interesting. The app that I am testing, player_from_querystring is a unary fab.js app. That means that it is the end point of the current request. Fab.js calls it a unary app based on the arity—it takes a single argument, the downstream app. This can be seen in the call to player_from_querystring:
      var upstream_listener = player_from_querystring.call(downstream);
What's interesting here is that I need to call() the player_from_querystring() function. I need to call() because this is how fab.js does it. Why it does it is part of what makes fabs.js so interesting.

The built-in javascript function call() evaluates the function being called (here, my player_from_querystring function/app). What is different about calling call() rather than invoking the function directly is what happens to the this variable inside the function. Normally, the this variable would refer to the function itself. But using call() sets the this keyword to the first argument of call. In this case, inside player_from_querystring the this variable will point to my downstream app.

Why does fab.js do that? Because upstream apps need to send responses back downstream. They cannot do so without a reference to the downstream app, to which they have convenient access to in the this keyword. Nice.

The fun on that line does not stop there, however. I assign the result of the call to a variable I name upstream_listener. Not all fab apps return a listener, but they do if they need to act on input from downstream. For my player_from_querystring app, I need to act on input from all the way downstream—I need to parse the query string supplied by the browser. So my player_from_querystring app will return a listener for that information and will expect to be called with a player query string (e.g. "?player=foo&x=1&y=2"). To simulate this in my test, I call the listener directly:
      var upstream_listener = player_from_querystring.call(downstream);
upstream_listener(head);
With that information, my player_from_querystring app replies to the downstream app with a player object. That gets stored in the player variable, which I can then test:
      this(player.id == "foo");
And yeah, I am testing fab.js, so the this variable in the test harness (copied directly from fab.js source) is part of the testing framework.

With that, I have a nice, small unit test and a much firmer grasp on what fab.js is doing under the covers.

For completeness, the fab.js app that passes this test:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0} });
if ( app ) app();
}
else {
out();
}
};
}


Day #116

Wednesday, May 26, 2010

Testing with Fabjs (and a little commonjs thrown in)

‹prev | My Chain | next›

First up tonight, a bit of messing about with commonjs. It is a part of node.js, which means that it is a part of fab.js. That does not mean that I know much about.

Take export and require, for instance. I think I get what they do—allow library functions to be imported from a separate file into my current code. Seems straight forward, but I have not had need for it myself. Now that I am testing my fab apps, I think I will need it. Last time around, I broke the player_from_querystring method out into its own fab app in lib/player_from_querystring.js. In there, I had two exports statements:
function player_from_querystring() {
// the actual fab app here
}

exports.tests = ( function() {
// returns a list of functions that can be tested
})();

exports.app = player_from_querystring;
I am exporting two things: tests and app. The former goes into my test harness. The latter goes into my my (fab) game:
var puts = require( "sys" ).puts,
player_from_querystring = require('./lib/player_from_querystring').app;

// Other fab code

( /^\/comet_view/ )
( init_comet )
( player_from_querystring )
I could have exported the player_from_querystring directly:
exports.player_from_querystring = player_from_querystring;
And then imported thusly:
    player_from_querystring = require('./lib/player_from_querystring').player_from_querystring;
I prefer the fab.js convention of exporting app. I want to import a (fab) app, so it feels right.

With that out of the way, I am onto trying to test my player_with_querystring method. I got a rudimentary test (that this is a unary app) in place on Monday. Now, let's see if I can verify that, given a query string with player information, my unary app produces a player object.

This may not be a good first choice for experimentation. It is rather complicated in that it reads from the downstream (closer to the client) app to parse the query string:
function player_from_querystring() {
var out = this;
return function(head) {
if (head && head.url && head.url.search) {
var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0} });
if ( app ) app();
}
else {
out();
}
};
}
So how to simulate that in the test harness? Well, I am definitely going to need a fake "head" (the request headers):
exports.tests = ( function() {
var head = { url: { search : "?player=foo&x=1&y=2" } };
return [

function
bodyRespondsWithCorrectPayload() {
// Test goes here
}
];
})();
But how to get that in there? If I execute player_from_querystring, it is going to return that downstream listener for the header. So I need to call player_from_querystring with something that can receive the response. In fab, it is the downstream app that receives the response. I create a fake downstream app to capture the player returned:
      var player;
function downstream (obj) {
if (obj) player = obj.body;
}
I am cheating a bit by manipulating player in that downstream app. There is almost certainly a cleaner way to do this, but... I am just trying to get this to work. With that, I want to call player_from_querystring with that downstream function:
      var listener = player_from_querystring.call(downstream);
That listener is what player_from_querystring returns to get the request headers (and the request body if there is one). This is where I need to pass in my head with the query string:
listener(head);
Last, but not least, I need to call the test harness with a boolean value indicating test pass / failure. I try comparing Javascript objects but fail. Instead I try comparing the id attribute, which should be "foo":
      out(player.id == "foo");
Altogether my test now reads:
exports.tests = ( function() {
var head = { url: { search : "?player=foo&x=1&y=2" } };

return [
function
statusReturnsUnaryApp() {
this( player_from_querystring().length === 1 );
},

function
bodyRespondsWithCorrectPayload() {
var out = this;
var player;
function downstream (obj) {
if (obj) player = obj.body;
}
var listener = player_from_querystring.call(downstream);
listener(head);
out(player.id == "foo");
}

];
})();
And, when I run the tests:
cstrom@whitefall:~/repos/my_fab_game$ node test.js 
Running 2 tests...
statusReturnsUnaryApp: true
bodyRespondsWithCorrectPayload: true
Done. 2 passed, 0 failed.
Yay!

It is going to be a while before I am TDDing fab.js applications. But this is definite progress.

Day #115

Tuesday, May 25, 2010

Retrospective: Week Fifteen

‹prev | My Chain | next›

In an ongoing effort to make this chain as valuable as possible to myself (and possibly others), I perform a weekly retrospective of how the past week went (notes from last week). I do this on Tuesdays to avoid conflicts with B'more on Rails, which usually holds events then.

This week (like the week before), I continue working on my (fab) game—a game inspired by the fab.js framework that I am writing for my kids.

WHAT WENT WELL

  • I chatted (while playing tag) with my son in my (fab) game. It is rather nice being able to share this with the kids.
  • Was able to implement an idle timeout in fab.js and broadcast player removal to all attached clients via comet.
  • Got raphaël.js animation shadows working in my (fab) game. Then did it even better (possibly even the right way).
  • Effectively used tracer bullets to drive the chat system in my (fab) game.
  • Finally started in with testing my fab.js code. I have been enjoying playing with the code, but the prospect of using it for something real means that I must know how to test it.

OBSTACLES / THINGS THAT WENT NOT SO WELL

  • Testing in node.js / fab.js. It is really this hard?
  • The kids are now asking to help edit EEE Cooks, which was the original purpose of my second chain.

WHAT I'LL TRY NEXT WEEK

  • More testing. I plan to keep moving forward with the test harness that I copied from fab.js for another day or so. Then I will likely move onto other node.js testing frameworks and possibly even Cucumber.
  • Still have a few TODOs in my (fab) game:
    • constant speed animations with raphaël.js. By default in raphaël.js, it takes a fixed amount of time to move—regardless of the distance
    • graphical, animated avatars
    • collision detection, ideally with an API with which the kids can play


Day #114

Monday, May 24, 2010

First Pass at Testing My Own Fab.js App

‹prev | My Chain | next›

Stubbornly, I continue tonight trying to get started with testing my fab.js application. I found out last night that there is no easy way to run the fab.js test suite (and a @fabjs tweet this morning confirmed it). Still, I need to be able to test if I am going to seriously use fab.js.

Last night was not a complete waste. I did learn a thing or two about fab.js test strategies. I think that I will start with them tonight. I create my own test.js test suite and pull in the commonjs assert and puts (print):
#!/usr/bin/env node

var assert = require("assert")
, puts = require( "sys" ).puts;
I then manually copy in the contents of fab.js's utils/test.js and pull in a function from my (fab) game to test:
#!/usr/bin/env node

var assert = require("assert")
, puts = require( "sys" ).puts;

// utils/test.js from fab.js

var target_module = require('./lib/player_from_querystring');
test(target_module);
In lib/player_from_querystring.js, I paste the contents of the function that I wrote by hand last week to add a new player to the fab.js game and export it (using the commonjs exports):
function player_from_querystring() {
var out = this;
return function(head) {
if (head.url.search) {
var search = head.url.search.substring(1);
var q = require('querystring').parse(search);
var app = out({ body: {id: q.player, x: q.x || 0, y: q.y || 0} });
if ( app ) app();
}
else {
out();
}
};
}

exports.app = player_from_querystring;
To use that in the test framework, this library also needs to export a list of tests. For now, I just want to test that this is a unary app (which it is by virtue of receiving no arguments:
exports.tests = ( function() {
var app = player_from_querystring( );

return [

function
statusReturnsUnaryApp() {
this( app.length === 1 );
}
];
});
When I run the test framework, however, I get:
cstrom@whitefall:~/repos/my_fab_game$ node ./test.js 
Running 0 tests...
TypeError: Object function () {
var app = player_from_querystring( );

return [

function
statusReturnsUnaryApp() {
this( app.length === 1 );
}
];
} has no method 'forEach'
at test (/home/cstrom/repos/my_fab_game/test.js:13:15)
at Object.<anonymous> (/home/cstrom/repos/my_fab_game/test.js:33:1)
at Module._compile (node.js:639:23)
at node.js:667:20
at fs:52:23
at node.js:748:9
Hrm... I thought the tests being exported returned a list of things to test. What gives?

Ah, I am assigning exports.tests to a function, not the return value of that function. For the lack of an empty parens, my test fail. Adding the parens evaluates that function, returning the list of tests:
exports.tests = ( function() {
var app = player_from_querystring( );

return [
function
statusReturnsUnaryApp() {
this( app.length === 1 );
}
];
})();
With that, I get my first fab.js test to pass:
cstrom@whitefall:~/repos/my_fab_game$ node ./test.js 
Running 1 tests...
statusReturnsUnaryApp: true
Done. 1 passed, 0 failed.
It is not much. But it is a start. Up tomorrow, a retrospective (and B'more on Rails opensource hack night!). After that, perhaps more work with this or moving onto testing some of this with Cucumber.

Day #113

Sunday, May 23, 2010

I Can't Test Fab.js

‹prev | My Chain | next›

Up tonight, I would like to start working through testing my (fab) game. So far, I have not done any, and feel like I'm pushing my luck with keeping this together. I know that Cucumber now runs (at least as a proof of concept) under the v8 javascript engine. Before I go there, I would like to be able to at least run the fab.js test suite. Or at lest one test.

Way back, a reader provided a clue as to how to run a single test. This is a good thing because none of the built-in test like scripts seem to do anything:
cstrom@whitefall:~/repos/fab$ node utils/test.js
<no output>
cstrom@whitefall:~/repos/fab$ node utils/build.js
<no output>
cstrom@whitefall:~/repos/fab$ node builds/all.js
<no output>
What's worse is that the "core" test fails completely:
cstrom@whitefall:~/repos/fab$ node builds/core.js 
Error: Cannot find module '/home/cstrom/repos/fab/apps/fab.Function'
at loadModule (node.js:492:15)
at require (node.js:618:12)
at /home/cstrom/repos/fab/utils/build.js:10:15
at Array.forEach (native)
at /home/cstrom/repos/fab/utils/build.js:6:8
at Object.<anonymous> (/home/cstrom/repos/fab/builds/core.js:12:41)
at Module._compile (node.js:639:23)
at node.js:667:20
at fs:52:23
at node.js:748:9
Bah!

I can get that "working", in the sense that it does not crash, by adding a "defaults" object namespace to some of the apps in core.js:
var apps = exports.apps = [
"fab"
, "fab.body"
, "fab.defaults"
, "fab.defaults.Function"
, "fab.identity"
, "fab.defaults.Number"
, "fab.path"
, "fab.defaults.RegExp"
, "fab.status"
];

exports.app = require( "../utils/build" )( apps ).app;
But that does me little good as running that file with node now returns right away without any output as well.

Let's see if I can get a single test to run. Thanks to the comment from istruble, I know that I can create a my_test.js file in the root directory of the fab.js source with contents like:
var test = require("./utils/test"),
target_module = require('./apps/fab.status');
test(target_module);
And indeed that does work:
cstrom@whitefall:~/repos/fab$ node my_test.js
Running 3 tests...
statusReturnsUnaryApp: true
statusRespondsWithCorrectPayload: true
statusClosesConnection: true
Done. 3 passed, 0 failed.
I fiddle with that for a bit longer to see if I can get it to load in more tests automatically, but the overall approach feels very manual. I am forced to call it a night at this point. I think tomorrow I will move on to try Cucumber.

Day #112

Saturday, May 22, 2010

Hitting the Raphaël.js Target

‹prev | My Chain | next›

Last night, I teased out a chat system in my (fab) game. I left a tracer bullet, in the form of a javascript alert(), in place. Tonight, I need to finish that off.

The tracer bullet was left in the PlayerList, which is where the fab.js backend interfaces with clients via comet. In this case, the backend makes a call to player_say method:
PlayerList.prototype.player_say = function(attrs) {
alert(attrs.id + " says " + attrs.say);
};
Removing the tracer bullet, the target that I really want to hit is the say method on the Player itself:
PlayerList.prototype.player_say = function(attrs) {
this.get_player(attrs.id).say(attrs.say);
};
I define the say method as:
Player.prototype.say = function(message) {
this.balloon = this.drawable.paper.text(this.x, this.y - 10, message);
};
With that, I have a speech balloon 10 pixels above the drawable version of the player. I'm not quite done yet because I need that speech balloon to move along with the drawable version of the player as it moves about the room.

Shadowing the drawable player is the responsibility of the the attach_drawable method, which makes use of the onAnimation method from raphaël.js:
Player.prototype.attach_drawable = function(drawable) {
var self = this;
this.drawable = drawable;
this.label = drawable.paper.text(this.x, this.y + 10, this.id);

drawable.onAnimation(function(){
self.label.attr({x: drawable.attr("cx"), y: drawable.attr("cy") + 10});
});

};
Right now, I am shadowing the player with its name. To shadow with the speech balloon (if present), I add:
Player.prototype.attach_drawable = function(drawable) {
var self = this;
this.drawable = drawable;
this.label = drawable.paper.text(this.x, this.y + 10, this.id);

drawable.onAnimation(function(){
self.label.attr({x: drawable.attr("cx"), y: drawable.attr("cy") + 10});

if (self.balloon) {
self.balloon.attr({x: drawable.attr("cx"), y: drawable.attr("cy") - 10});
}

});
};
I do not want the speech balloon to stick around indefinitely, so, back in the say method, I add a timer to remove it after 10 seconds:
Player.prototype.say = function(message) {
var self = this;

this.balloon = this.drawable.paper.text(this.x, this.y - 10, message);
setTimeout(function(){self.balloon.remove();}, 10*1000);
};
Lastly, I need to remove a previous speech balloon if a new message is sent:
Player.prototype.say = function(message) {
var self = this;

if (this.balloon) this.balloon.remove();

this.balloon = this.drawable.paper.text(this.x, this.y - 10, message);
setTimeout(function(){self.balloon.remove();}, 10*1000);
};
With that, I do believe that my chat system is more or less complete:

video

Up tomorrow: maybe a tweak or two, but I heard tell of Cucumber integration with v8 (the javascript engine on top of which fab.js and node.js are built). I do believe that will prove irresistible for me.

Day #111

Friday, May 21, 2010

Tracer Bullets for Raphaël.js and Fab.js

‹prev | My Chain | next›

The killer feature in my (fab) game (as least as far as my son is concerned) is chatting. Sure I can have multiple people in a room, I could probably get rudimentary collision detection working in it, I can even animate players as they move about the room. None of that matters in my son's eyes. He wants to be able to chat.

Kids.

I have a lot of fairly decoupled pieces at the moment. In the frontend, largely raphaël.js with a sprinkle of jQuery, I have a bunch of Players that roam about a Room. I also have a PlayerList that holds the list of players in a room and, as such, serves as the middleman for comet push communication from the fab.js backend. Speaking of the backend, there are two parts that will factor into chatting: notifying the backend of the message and broadcasting the message to all players.

That is a lot of moving pieces. To make sure I do this right, I am going to use the tried-and-true method of tracer bullets to make sure I know my target before I hit it for real. I start with a simple form at the bottom of my Room, which I add via a jQuery insertAfter:
Room.prototype.draw = function() {
var self = this;

// Initialize the Raphaël.js room
this.paper = Raphael(this.container, 500, 500);
this.paper.clear();
this.paper.
rect(0, 0, 500, 500, 4).
attr({fill: "#fff", stroke: "none"});

// Form to capture chat
$('<div id="chat">' +
'<form id="chat-form" action="#">' +
'<label for="change-message">Chat:</label>' +
'<input type="text" name="message" id="chat-message" />' +
'<input type="submit" name="commit" value="Go" />' +
'</form>' +
'</div>').insertAfter($(self.paper.canvas).parent());


}
I know that this message eventually needs to hit the server, but first, the room needs to tell player that I said something so that my player can tell the fab.js backend. But even before that, I need to know that I can capture the form input. So, my first tracer bullet:
Room.prototype.draw = function() {
var self = this;

// Initialize the Raphaël.js room

// Form to capture chat

// handle submits
$('#chat-form').submit(function(e) {
console.debug($('#chat-message').val());
subscriber.notify(event);
$('#chat-message').val('');
return false;

});
}
Now, when I enter a message and press "Go", I get nice debug output in Chrome's javascript console. Obviously, this submit handler is of little use to me at this point, but that is the whole point of tracer bullets. I make sure that I can hit the target (handling chat messages) before I do it for real.

So let's do it for real. This is being done in my Room, but my current player needs to be told that a message was posted. I could hold a reference to my player in the room and call the Player.say() method directly, but that would pretty heavy handed coupling. Instead I have a pub/sub for events which allows me to send events, like a new chat message, to all interested parties (like my Player):
  $('#chat-form').submit(function(e) {
self.subscribers.forEach(
function(subscriber) {
var event = {
type: "message",
value: $('#chat-message').val()
};
subscriber.notify(event);
$('#chat-message').val('');
}
);

return false;
});
Time for another tracer bullet at this point. My Player currently ignores all notifications other than move:
Player.prototype.notify = function(evt) {
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
}
};
Rather than going for full blown support of the "message" event, I add some more simple debugging to make sure that I am aiming the right way:
Player.prototype.notify = function(evt) {
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
default:
console.debug("[notify] type: " + evt.type + " value: " + evt.value);

}
};
Now, when I send a chat message, I get more nice debug output in Chrome's javascript console. Since my aim is good, I can add the real stuff to handle message notifications:
Player.prototype.notify = function(evt) {
switch(evt.type) {
case "click":
this.stop();
this.walk_to(evt.x, evt.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
break;
case "message":
this.stop();
this.walk_to(this.x, this.y);
this.notify_server('move', {id:this.id, x:evt.x, y:evt.y});
this.notify_server('chat', {id:this.id, say:evt.value});
break;

default:
console.debug("[notify] type: " + evt.type + " value: " + evt.value);
}
};
New in there is notifying the server of a chat event. For that, I need to move down into my fab.js backend where a unary will handle the request:
  ( /chat/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
broadcast(comet_player_say(obj.body));
}
return listener;
};
} )
Nothing too fancy there. It is a simple unary app with a listener to grab the data being posted (the chat message payload). I then use the already-in-place broadcast method to tell all attached clients that someone had something to say. I am not using tracer bullets here because I know these fab.js targets well.

What I am not so sure about is the ultimate broadcast call. For that, I will tell the PlayerList on each connected browser that somebody said something:
function comet_player_say(player_string) {
return comet_wrap('player_list.player_say('+ player_string +')');
}
This brings me to my final bullet point for the night, the player_say method in PlayerList:
PlayerList.prototype.player_say = function(attrs) {
alert(attrs.id + " says " + attrs.say);
};
Here I use good, old fashioned alert() for my tracer bullets. Eventually, I would like what the player said displayed immediately above the player in the room. But, for now, an alert will provide a reasonable facsimile:

video

Nice!

Bit of a whirlwind, but I have basic chatting working. Tomorrow, I will tidy things up a bit and replace the last of my tracer bullets with in-room chat messages.

Day #110

Shadows in Raphaël.js

‹prev | My Chain | next›

Last night I made a bit of a mess out of animating my players and labels as they move about the room in my (fab) game. It works:

video

But it is done by independently animating the label and the player. The label is offset by 10 pixels, but everything else about the label is duplicated in the Player. I have to stop both the Player and the label the same way, I have to animate both the same way, and I have to initially draw both the same way. That's a lot of duplication.

So let's try it another way. Instead of animating the label as an independent object, I will hook it into the Player animation via raphaël.js's onAnimation callback:
Player.prototype.attach_drawable = function(drawable) {
this.drawable = drawable;
var label = drawable.paper.text(this.x, this.y + 10, this.id);
this.label = label;

drawable.onAnimation(function(){
label.attr({x: drawable.attr("cx"), y: drawable.attr("cy") + 10});
});

};
I can then eliminate all other references to label in the code because this callback will draw the label in a new position each increment of the Player (or the drawable representation of it) along its path about the room.

Nice!

The only difficulty I had was choosing the "right" X and Y. I know from playing about with raphaël.js over the past week that cx and cy are the center of my player avatar in the room, so it only make sense to use them when moving calculating where to put the label.

What I could not figure out was how to set the location of the label. I initially tried cx and cy for that as well, but it had no effect. The label stayed in the start position as the Player moved about the room. I had expected x/y to start the label at that position and then draw the label to the right of center, but, in the end, it does exactly what I had hoped for:



Up next: more fab.js as I get chatting underway.

Day #110

Thursday, May 20, 2010

Moving Sets in Raphaël.js

‹prev | My Chain | next›

Things are going fairly well with my (fab) game. My fab.js backend is doing most of what I need it to (keeping track of players, dropping idle players). My frontend is working fairly well in raphaël.js as well. Tonight, I would like to see if I can attached a name to players in the game room (right now, they are just dots).

Raphaël's set() seems like a good option. Currently, the Room is drawing the player with Raphaël's circle() and returning that to be moved as the Player moves about the room:
Room.prototype.draw_player = function(player, color) {
var c = this.paper.circle(player.y, player.x, 3);
c.attr({fill: color, opacity: 0.5});
return c;
};
The set() method groups elements together. So let's try grouping this circle with a text element just below it:
Room.prototype.draw_player = function(player, color) {
var st = this.paper.set();
st.push(
this.paper.circle(player.x, player.y, 3),
this.paper.text(player.x, player.y + 10, player.id)
);
st.attr({fill: color, opacity: 0.5});

return st;
};
Trying that out in the browser it works!:



It works... but only up to a point. When I try to move this set with the same animateAlong() method, nothing happens. Digging through the raphaël.js source code a bit, it seems that only animate() was implemented for sets. I would hazard a guess that it is difficult to animate a bunch of arbitrary objects along a path, which is why this is not part of raphaël.js yet. Still I really would like this feature.

For now, I am going to fall back on a terrible hack. Hopefully I can come up with something better in the future, but...

I revert my set code. Instead, when the Player is handed its "drawable" circle() I add a second object 10 pixels below (y-pixels increase down the page):
Player.prototype.attach_drawable = function(drawable) {
this.drawable = drawable;
this.label = drawable.paper.text(this.x, this.y + 10, this.id);
};
Here, I am using the paper attribute that the cirle() drawable (and any raphaël.js element) has.

With a label now an attribute on Player, I need to animate the label along a path 10 pixels below the path the Player walks:
Player.prototype.walk_to = function(x, y) {
var p = "M"+ this.x + " " + this.y +
"L" + x + " " + y;
this.drawable.animateAlong(p, 3000);

var pl = "M"+ this.x + " " + this.y + 10 +
"L" + x + " " + y + 10;
this.label.animateAlong(p, 3000);


this.x = x;
this.y = y;
};
And, when the Player stops, I need to stop the label as well:
Player.prototype.stop = function () {
this.drawable.stop();
this.label.stop();
this.x = this.drawable.attrs.cx;
this.y = this.drawable.attrs.cy;
};
I am not at all satisfied with this solution but it does work:

video

I will stop there for the night and ruminate on how I might do this better tomorrow. Maybe onAnimation()?

Day #109

Wednesday, May 19, 2010

From Raphaël.js to Fab.js: A Tale of One Player's Journey

‹prev | My Chain | next›

Up tonight, I want to kick players out of my (fab) game. I am not trying to drop unruly players, just unresponsive players.

I start with the client code. When a Player quits, I need to remove the raphaël.js drawable object:
Player.prototype.quit = function() {
this.drawable.remove();
}
("drawable" refers to a raphaël.js circle and remove() is a raphaël.js method).

For good measure, I delete the attributes from the player object:
Player.prototype.quit = function() {
this.drawable.remove();
delete this.x;
delete this.y;
delete this.id;

}
I am not trying to save memory or anything of the sort. I remove these attributes so that the Player no longer quacks like a Player. If anything tries to treat it as such, errors will happen.

Now that a Player can quit a room, I need to expose an interface for fab.js to be able to communicate with browsers to inform them of a departure. The interface for this is the PlayerList in the browser. The PlayerList holds the current player (me) as well as all the other current players. I will handle removing these other players first:
PlayerList.prototype.remove_player = function(id) {
this.get_player(id).quit();
delete this.other_players[id];
};
Actually, that is quite easy. I call the quit() method that I just defined on the Player leaving the room. Then I delete the player from the player list.

If the player quitting the room is me (e.g. I have been idle too long), then I need to quit the room as do all other players. From my perspective, if I leave, I no longer care about the other players. So:
PlayerList.prototype.remove_player = function(id) {
if (this.me.id == id) {
// I am leaving and so is everyone else (as far as I'm concerned)
this.me.quit();
for (var oid in this.other_players) {
this.other_players[oid].quit();
}
}

else {
this.get_player(id).quit();
delete this.other_players[id];
}
};
With that, I am ready to get started in the fab.js backend. First up, I do a little refactoring of the code so that the comet communication method is a bit more general. It had been specific to walking players in the rooms of each client. Now the broadcast() function can send any command to the PlayerList in the connected browsers:
function broadcast(comet_command) {
var num = 0;
for (var id in players) {
var player = players[id];
player.listener({body: comet_command});
num++;
}
puts("broadcasting to "+num+" players");
}
It can send any command, such as the new remove_player() method. To get started with timing out players, I add a simple setTimeout() immediately after a player is added in fab.js:
// ...
puts("adding: " + obj.body.id);
players[obj.body.id] = {status: obj.body, listener: out};

setTimeout(function() {
drop_player(obj.body.id);
}, 5000);


broadcast(comet_walk_player(JSON.stringify(obj.body)));
...
With that, 5 seconds after connecting, the drop_player() method will be called which, in turn, broadcasts to all connected clients that said player should be removed:
function drop_player(id) {
puts("Dropping player \""+ id +"\"");
broadcast(comet_quit_player(id));
}

function comet_quit_player(id) {
return comet_wrap('player_list.remove_player("'+ id +'")');
}
Trying this out, and after moving the player "bob" about the room, I am dropped:
cstrom@whitefall:~/repos/my_fab_game$ node game.js
adding: bob
broadcasting to 1 players
broadcasting to 1 players
updating player status: bob
Dropping player "bob"
broadcasting to 1 players
The last step is to move this into per-player idle watching inside the fab.js. In the idle_watch() function, I clear any existing timeout, then set a new one. If 60 seconds pass, then the Player will be removed:
function idle_watch(id) {
if (players[id].idle_timeout) {
clearTimeout(players[id].idle_timeout);
}

players[id].idle_timeout = setTimeout(function() {
drop_player(id);
}, 60*1000);
}
Calling this method anytime a player updates its status will ensure that active players remain in the room as long as they like:
function update_player_status(status) {
puts("updating player status: " + status.id);
players[status.id].status = status;
idle_watch(status.id);
}
That is a good stopping point for tonight. Up tomorrow, more raphaël.js work, I think. I would like to include player names next to the player icons. Then it'll be onto the next killer feature (in my son's eyes): chatting.

Day #108

Tuesday, May 18, 2010

Retrospective: Week Fourteen

‹prev | My Chain | next›

In an ongoing effort to make this chain as valuable as possible to myself (and possibly others), I perform a weekly retrospective of how the past week went (notes from last week). I do this on Tuesdays to avoid conflicts with B'more on Rails, which usually holds events then.

This week (like the week before), I continue working on my (fab) game—a game inspired by the fab.js framework that I am writing for my kids.

WHAT WENT WELL

  • I played tag with my son in my (fab) game. I was quite excited to have gotten the game to this point already.
  • Figured out how to broadcast to many fab.js clients
  • Started playing with raphaël.js
    • Very nice / easy to pick up framework.
    • Needed to reach under the API covers (to stop animation). That's something of a not-so-well, but the Raphaël code is so well organized that it was easy to accomplish what I wanted.
  • Refactored a fab.js unary into a binary with relative ease, using that to support a backend player list. The code was prettier as well!

OBSTACLES / THINGS THAT WENT NOT SO WELL

  • Still not testing my javascript.
  • Had not separated my Room/Player concerns as well as I hoped. Switching from <canvas> to Raphaël forced changes such that the Room, which had been watching the Player move, now moves the Player and informs the Player where it is. This required a fair number of changes in my code. I wonder if it possible to have coded this such that fewer changes were required—less coupling between the two would be a good thing.
  • I struggled with setting the comet iframe's src more than I should have. I was sure that I needed to muck with the window.location inside the iframe. It took me much longer than it should have to figure out that I could simply set the src attribute directly.

WHAT I'LL TRY NEXT WEEK

  • I am having way too much fun playing around with these Javascript frameworks. That is certainly a good thing, but I do ultimately want to produce robust, accurate, maintainable code. That means testing. I must test!
  • I am not sure if it is possible to detect disconnecting clients in fab.js. Worth some time to investigate.
  • Regardless of the answer to the above, I ought to be able to add client heartbeats to passively remove players from the room.


Day #107

Monday, May 17, 2010

Stopping Animation in Raphaël.js

‹prev | My Chain | next›

Last night I was able to get animation with raphaël.js working in my (fab) game. Tonight I want to stop it.

More to the point, I want to stop the animation of my player walking the room when I click somewhere else in the room. If I am already half way to my destination when I click, I want the animation to halt immediately. The player should then move from that location to the click event. Currently that is not happening:

video

I had already gotten this working with my low-level<canvas> code. I even have a method on Player that stops the walk before restarting to a new destination. In the new raphaël.js branch, it is not doing much:
Player.prototype.stop = function () {
console.debug("try to stop()");
};
That seems like a good place to get started. First up, I think I will try stop(), an undocumented raphaël.js method that halts animations:
Player.prototype.stop = function () {
console.debug("try to stop()");
this.drawable.stop();
};
Recall that drawable is how Player remembers the raphaël element (circle in this case).

That seems to halt the animation, but it is hard to know for sure. A second click event starts the animation right back up starting from the wrong coordinates. Recall also that the walk_to method sets the coordinates of a Player to the destination:
Player.prototype.walk_to = function(x, y) {
var self = this;
var p = "M"+ this.x + " " + this.y +
"L" + x + " " + y;
this.drawable.animateAlong(p, 3000);
this.x = x;
this.y = y;

};
In the case of the second click before the Player reaches the destination, those coordinates are wrong, messing up the next walk_to. What I need is a way to know the location on the raphaël.js element when the animation is stopped. Since I am already reaching under the raphaël.js API, I will go just a bit further to access the center-x / center-y attribute of my drawable element:
 Player.prototype.stop = function () {
console.debug("try to stop()");
this.drawable.stop();
this.x = this.drawable.attrs.cx;
this.y = this.drawable.attrs.cy;

};
I could have used the getBBox method as well, but then I would have to calculate the center-x / center-y value. Why bother when I can reach into attrs for it? True, there is something to be said for staying within the exposed API, but I have already given up on that when I called stop in the first place.

With that, my raphaël.js room can now animate movement even when I change my mind mid-walk:

video

Up tomorrow, it is retrospective time. Then I need to make sure that my raphaël-based frontend is still talking nicely to my fab.js backend.

Day #106

Sunday, May 16, 2010

Animating with Raphaël.js

‹prev | My Chain | next›

Tonight, I continue my exploration of raphaël.js. Last night, I was able to get the basic canvas in place and hook in an event listener into my (fab) game. At that point, I realized that it was going to require some refactoring of the player animation to work with raphaël.js. Now I start that refactoring.

The difference between my <canvas> based animation and the way that raphaël animates is that my <canvas> code only watches/reports as the player itself moves. Raphaël's animateAlong() moves items about on its "paper", then reports on locations. I had hoped my code would prove decoupled enough that I could change only the Room or only the Player. Sadly, both will require changes.

First up, I re-arrange the player list and room so that player list knows about the room, not the other way around:
    var room = new Room($("#room-container")[0]);
player_list = new PlayerList(me, room);
The room no longer needs to watch the players—now the players will have to watch what happens in the room. After updating the constructors of each, I provide a means for the player list to tell each Player about the raphaël.js drawing element:
Player.prototype.attach_drawable = function(drawable) {
this.drawable = drawable;
};
This "drawable" element in the room comes from the circle object drawn by the Room:
Room.prototype.draw_player = function(player, color) {
var c = this.paper.circle(player.y, player.x, 3);
c.attr({fill: color, opacity: 0.5});
return c;
};
(It is the player list's job to attach the two).

With that, I can call the raphaël.js animateAlong() method with an SVG path element to animate the motion of the player in the room:
Player.prototype.walk_to = function(x, y) {
var self = this;
var p = "M"+ this.x + " " + this.y +
"L" + x + " " + y;
this.drawable.animateAlong(p, 3000)
};
With that, I can actually animate my players again:

video

Nice! That was a bit easier than working with <canvas> (which probably helped overcoming some problems I might have encountered). Unfortunately, I do run into one of the problems I had already solved in my <canvas> code—clicking a new destination before the animation has completed fouls up the walk (animations combine).

I am unsure if that can be resolved easily in raphaël.js. I will investigate tomorrow.

Day #105

Saturday, May 15, 2010

Getting Started with Raphaël.js

‹prev | My Chain | next›

Tonight, I will explore drawing my game with the raphaël.js javascript framework. I have no interest in cross-browser support (Webkit and Firefox are sufficient), but I would like to see if drawing things are easier with it and maybe a little less CPU intensive.

So I remove my <canvas> tag, replacing it with a container for my raphaël.js canvas:
   <div id="room-container"></div>
...and pass this into my Room constructor:
    new Room($("#room-container")[0], player_list);
After updating my constructor to expect the container rather than a <canvas> element, my draw routine starts in with the raphaël.js:
Room.prototype.draw = function() {

this.paper = Raphael(this.container, 500, 500);
this.paper.clear();
this.paper.rect(0, 0, 500, 500, 10).attr({fill: "#fff", stroke: "none"});

// calls to draw the players
}
This initializes a raphaël element, clears it, then fills it in with white. Simple enough. Drawing players with raphaël is also fairly straight forward:
Room.prototype.draw_player = function(player, color) {
var c = this.paper.circle(player.y, player.x, 3);
c.attr({fill: color, opacity: 0.5});
};
I just borrow some of the sample code from the very excellent documentation for drawing circles. I now have a nicely rendered room with very little effort:



Unfortunately, now I run into problems. I move players about by clicking on the room and having them follow the click. The problem here is trying to figure out where to attach that click event. When I was using a straight <canvas> element, I could attach my click handler directly to it. That does not work with raphaël, nor can I attach the handler to the "paper"'s node.

In the end I am forced to attach the handler to the containing <div>:
Room.prototype.init_events = function() {
var self = this;
$(this.container).click(function(evt) {
var decorated_event = self.decorate_event(evt);
self.subscribers.forEach(
function(subscriber) { subscriber.notify(decorated_event); }
);
});
};
Although the event is not listening on the real target, it seems to be close enough. My player is again notified of the event and sends movement information to the fab.js backend once more.

I stop here for the night. Tomorrow I will pick back up with animating my players inside raphaël. That seem relatively easy to do, but it will require a fair bit of rejiggering of responsibilities in my code so far.

Day #104

Friday, May 14, 2010

Iframe src and a Weird Object

‹prev | My Chain | next›

OK. Let's see if I can get state remembered properly in my (fab) game. Last night I got the fab.js backend ready to accept iframe query parameters describing a player. The first order of business tonight is to get the iframe actually sending said query parameters.

Easy enough. Currently the iframe resource is requested on page load:
<iframe src="/comet_view"></iframe>
I switch to a no-src iframe:
<iframe id="iframe"></iframe>
I only assign a src once the player is identified and when I do so I pass query parameter information that the server can use to link the player with the connection:
  if (kv[0] == 'player' && kv[1]) {
// There is a player
$('#login').hide();

var me = new Player(kv[1]);
$('#iframe')[0].src = '/comet_view?player=' + me.id + '&x=' + me.x + '&y=' + me.y;

player_list = new PlayerList(me);
new Room($("#canvas")[0], player_list);
}
To associate both position and connections with players, I convert the player list to a hash:
var players = {};
I then add players in the fab.js backend (taking advantage of the query-params-to-object app that I wrote last night):
function init_comet (app) {
return function () {
var out = this;

return app.call( function listener(obj) {
if (obj && obj.body) {
out({ headers: { "content-type": "text/html" },
body: "<html><body>\n" })

( // other comet connection initialization );

puts("adding: " + obj.body.id);
players[obj.body.id] = {status: obj.body, listener: out};

}
return listener;
});
};
}
With that working, I would like to keep track of players movements. In the /move app, I add:
  ( /move/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
broadcast(obj.body);
var status = JSON.parse(obj.body);
update_player_status(status);

}
return listener;
};
} )
Right after I broadcast the new position to all clients, I record it in the fab.js backend with update_player_status. Except I don't. My fab.js server crashes when my player moves:
cstrom@whitefall:~/repos/my_fab_game$ node game.js
HTTPParser.onBody (http:92:23)
Stream.ondata (http:524:30)
IOWatcher.callback (net:307:31)
node.js:748:9
That is not too helpful, but I would speculate that the HTTPParse in the stacktrace indicates that my JSON.parse is failing. With the help of print-stderr debugging, I eventually determine that the obj.body is not a string (although it behaves very much like one). This debugging code:
            puts("typeof"+typeof(obj.body));
puts("id: " + obj.body.id);
for (var prop in obj.body) {
puts("["+prop+"] " + obj.body[prop]);
}
...outputs the following:
typeofobject
id: undefined
[0] 123
[1] 34
[2] 105
[3] 100
[4] 34
[5] 58
[6] 34
[7] 98
[8] 111
...
So obj.body is an object. It is an object but not a player object because it does not respond to id. It also looks like an array... of characters. That is odd. Ultimately, I can cast it into a real string by concatenating it with an empty string:
  ( /move/ )
( function() {
var out = this;
return function listener( obj ) {
if ( !obj ) out();
else if ( obj.body ) {
broadcast(obj.body);
var status = JSON.parse("" + obj.body);
update_player_status(status);
}
return listener;
};
} )
That is a good stopping point for today. Tomorrow, I think I may explore raphaël.js a bit to make the room a bit nicer.

Day #103