Monday, June 7, 2010

Keep-alive in Fab.js

‹prev | My Chain | next›

Up tonight, I need to fix a problem that seems to have popped up recently with my (fab) game in Chrome. Specifically, the comet <iframe> seems to be closing after only a minute. I am reasonably sure that this started in a recent Chrome build, but regardless of the cause, I need to make sure that the <iframe> stays open.

Happily, this is pretty easy to do in my fab.js backend:
function keepalive(id) {
if (players[id]) {
puts("keepalive: " + id);
players[id].listener({body: '<script type="text/javascript">"12345789 "</script>' + "\n"});
setTimeout(function(){keepalive(id);}, 30*1000);
}
}
If a player's ID is in the list of players, call the downstream (closer to the browser) listener with a NOP body. Then, wait another 30 seconds and keepalive again. I then call the keepalive function for the first time when the new player is added in the broadcast_new app:
function broadcast_new (app) {
return function () {
var out = this;

var downstream;
return app.call( function listener(obj) {
if (obj && obj.body && obj.body.id) {
for (var id in players) {
out({body: comet_walk_player(JSON.stringify(players[id].status))});
}

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

idle_watch(new_id);

setTimeout(function(){keepalive(new_id);}, 30*1000);

puts("broadcasting about: " + new_id);
broadcast(comet_walk_player(JSON.stringify(obj.body)));
}
else {
downstream = out(obj);
}
return listener;
});
};
}
That is a good place to call keepalive for the first time because I am sure that the new player in the game is already in the players list. (I do make a TODO to clean-up that app—it's getting a tad large) That keeps the comet connection open seemingly indefinitely—or at least until the idle_watch kicks the player out of the room.

Minor Bug Fix


Much to my chagrin, I found that chat was no longer working in my (fab) game when I tried to demo it at Bohconf today. I take a little time to investigate that and fix it as well. The fix was a simple matter of removing code that seemed to serve no purpose:
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);
}
};
It is beyond me why, when sending a chat event, I would need to stop the player walking through the room, walk them to the current x-y coordinate, then notify the fab.js backend that the player moved to the coordinates of an event that does not have coordinates. Craziness. What's worse, I was quite pleased with myself when I did it, though didn't say why. Tracer bullets do not prevent all mistakes. Ah well, removing those lines fixes chat and all's well that ends well.

Up tomorrow, my code could use a bit of refactoring, but I'm itching to play with web sockets. Which urge will win out?


Day #127

Sunday, June 6, 2010

Overflow Events in Raphaël.js

‹prev | My Chain | next›

Way back when I was first experimenting with raphaël.js, I ran into a problem with capturing events on the room itself (the paper in Raphaël terms). My eventual solution was to listen on the paper's container. My son quickly discovered during our tag games in my (fab) game that players can hide in the container.

His cheating ends today!

The event code is decorated before notifying subscribers. That seems a good place to start:
Room.prototype.decorate_event = function(evt) {
return {
type: evt.type,
x: evt.pageX - $(this.container).offset().left,
y: evt.pageY - $(this.container).offset().top
};
};
That seems easy enough. I simply need to check if the player is trying to sneak out of bounds—either by moving less than zero or greater than the paper's width. Less than zero is easy and I ought to be able to grab the width of the paper from its attrs property:
Room.prototype.decorate_event = function(evt) {
var x = evt.pageX - $(this.container).offset().left;

if (x < 0)
x = 0;
else if (x > this.paper.attrs.width)
x = this.paper.attrs.width;

var y = evt.pageY - $(this.container).offset().top;
if (y < 0)
y = 0;
else if (y > this.paper.attrs.width)
y = this.paper.attrs.width;


return {
type: evt.type,
x: x,
y: y
};
};
Unfortunately, there doesn't seem to be an attrs property on the paper like other raphaël objects. So I have to settle for using the API which give access to the dimensions directly via width and height properties:
Room.prototype.decorate_event = function(evt) {
var x = evt.pageX - $(this.container).offset().left;

if (x < 0)
x = 0;
else if (x > this.paper.width)
x = this.paper.width;

var y = evt.pageY - $(this.container).offset().top;
if (y < 0)
y = 0;
else if (y > this.paper.width)
y = this.paper.width;

return {
type: evt.type,
x: x,
y: y
};
};
With that, the decorated event that is sent to subscribers (like the player) are limited to be within the bounding box. That's enough for today—it was a quite day :)

Up tomorrow: first I'm gonna thrash my son in tag. After that, I'll be poking around the fab.js / comet connection. I have introduced a bug somewhere that needs sorting out.

Day #126

Saturday, June 5, 2010

Constant Speed Raphaël.js Animations

‹prev | My Chain | next›

Today, I would like to make player movements in my (fab) game a bit more consistent. Raphaël.js allows you to set the duration for an animation, but not the speed. This means that it takes a player 3 seconds to walk from the top left all the way to the bottom right and it takes a player 3 seconds to walk 2 pixels to the left. That's just annoying.

The maximum distance a player can walk in my (fab) game is from top-left (0,0) to bottom-right (500,500). According to Pythagoras:



Or:



I am not going to bother calculating the square root, however. Instead, I will calculate the sum of the squares and compare that to the sum of the squares of the maximum walking distance that can be traveled (from 0,0 to 500,500). The percentage is then multiplied against the maximum walk time (3 seconds):
Player.max_walk = 500*500 + 500*500;
Player.time_to_max_walk = 3 * 1000;


Player.prototype.walk_to = function(x, y) {
var p = "M"+ this.x + " " + this.y +
"L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance_squared = x_diff * x_diff + y_diff * y_diff;
var time = Player.time_to_max_walk * ( distance_squared / Player.max_walk );

this.avatar.animateAlong(p, time);


this.x = x;
this.y = y;
};
Nice, right?

Well no. The movement is completely wrong. Long distance motion seems OK, but short distance moves way too fast. Sigh. Maybe I do need the distance rather than the sum of the squares. Let's see how the percentages compares:e
(x,y)x2 + y2Percentage of 102 + 102Distance from (0,0)Percentage of Distance to (10,10)
(1,1) 2 1% 1.41 10%
(5,5) 50 25% 7.07 50%
(10,10) 200 100% 14.14 100%
Well, that explains it. The (5,5) point should be half-way to (10,10), but it is only 25% of the way along the sum of the squares.

Sadly, I will have to incur the overhead of calculating the square root each time the player moves. Yeah, yeah, it is not much overhead. At the risk of over analyzing here, I am not sure if this counts as premature optimization as the square root would have been an extra code. I think I will give myself a pass on this one, especially since I legitimately did not think it was needed.

So I switch to using the actual distance when calculating the time:
Player.max_walk = Math.sqrt(500*500 + 500*500);
Player.time_to_max_walk = 5 * 1000;

Player.prototype.walk_to = function(x, y) {
var p = "M"+ this.x + " " + this.y +
"L" + x + " " + y;

var x_diff = x - this.x;
var y_diff = y - this.y;
var distance = Math.sqrt(x_diff * x_diff + y_diff * y_diff);
var time = Player.time_to_max_walk * ( distance / Player.max_walk );

this.avatar.animateAlong(p, time);

this.x = x;
this.y = y;
};
With that, my player moves around the room at a nice, constant speed:



Up next: something small. I think tomorrow may be a busy one for me.

Day #125

Friday, June 4, 2010

Size and Animation in Raphaël.js

‹prev | My Chain | next›

After a bit of fun with Cucumber and javascript (by way of v8), I am back on raphaël.js tonight. My goal is twofold: to make the look of my (fab) game slightly less craptacular and to begin thinking about an animation callback that my kids can code against.

To improve the look of the game, I would like to make the players a bit bigger (in anticipation of eventually replacing them with animated graphics). Currently the players look like this:



To make the player bigger, I simply need increase the radius of the raphaël.js element from 3 to 10:
  var c = this.paper.circle(player.x, player.y, 10);
I would also like to draw a border around the circle. This is possible through the stroke properties. It took me a while to figure it out (sadly), but the "stroke" is the "pen" mark as the element is drawn—the circle in this case. To color the player in, I had been filling the circle, but not stroking the outline as it was drawn. Now I can add a few stroke attributes to get my outline:
  c.attr({fill: color, "fill-opacity": 0.5, stroke: '#000', "stroke-width": 2, "stroke-opacity": 1.0});
Lastly, I would like to increase the text size on the player's name. This is done by invoking the attr method on the text label:
  this.label = drawable.paper
.text(this.x, this.y + 20, this.id)
.attr({"font-size": 12});
With that, I have a (slightly) prettier player:



Easy enough. There may just be something to this Raphaël.js stuff after all. To be sure of it, I would like to use it to animate the player as it walks about the room. Will Raphaël.js be able to accommodate this?

The answer, of course, is "yes", but, for a while there, I was not sure if I would be able to pull it off. My initial woes stemmed from the callback parameter to the animateAlong method. I had expected that the callback would be called at every point along the animation. It turns out that it only gets called at the end of the animation. That won't be much help in animating the player.

The answer to this problem is the onAnimation raphaël.js method which does just what I need: gets called at every point along the animation. I am already using it to shadow the player name along with the avatar. I can add a animate_with callback call in there as well:
  var animation_count = 0;
avatar.onAnimation(function(){
self.label.attr({x: avatar.attr("cx"), y: avatar.attr("cy") + 20});

if (++animation_count > 25) {
self.animate_with(this);
animation_count = 0;
}
});
(the animation count is used because I don't think that the animate_with callback need be called every millisecond of animation).

With that, I can create the player in the room with silly animation that changes the fill color randomly:
    var me = new Player(kv[1], {animate_with: function(avatar){
var color = ["#ccc", "#c00", "#0c0"][Math.floor(3*Math.random(3))];
avatar.attr({fill: color});
}
});
That yields a decent proof of concept animation:


That's a good stopping point for today. Up tomorrow: fix a few bugs that seem to have cropped up in the fab.js backend.

Day #124

Thursday, June 3, 2010

Joy for Cucumber on V8

‹prev | My Chain | next›

Last night I enjoyed little luck getting running on the v8 javascript engine. First up tonight, I am going to try a fresh install of all dependent gems. Thanks to rvm, this is trivial:
cstrom@whitefall:~/repos$ rvm gemset create 'javascript'
cstrom@whitefall:~/repos$ echo 'rvm 1.8.7@javascript' > cucumber/.rvmrc
cstrom@whitefall:~/repos$ cd cucumber
cstrom@whitefall:~/repos/cucumber$ gem install gherkin cucumber therubyracer
Now, I have only what I need:
cstrom@whitefall:~/repos/cucumber$ gem list

*** LOCAL GEMS ***

builder (2.1.2)
cucumber (0.7.3)
diff-lcs (1.1.2)
gherkin (1.0.30)
json_pure (1.4.3)
rake (0.8.7)
term-ansicolor (1.0.5)
therubyracer (0.7.1)
trollop (1.16.2)
While I'm at it, I pull new changes to cucumber. I still have the patch mostly lifted from Charles Lowell in place.

Now, when I run the javascript examples... everything passes:
cstrom@whitefall:~/repos/cucumber/examples/javascript$ rake cucumber
(in /home/cstrom/repos/cucumber/examples/javascript)
/home/cstrom/.rvm/rubies/ruby-1.8.7-p249/bin/ruby -I "/home/cstrom/repos/cuke.real/lib:lib" "/home/cstrom/repos/cucumber/bin/cucumber"
Feature: Fibonacci
In order to calculate super fast fibonacci series
As a Javascriptist
I want to use Javascript for that

Scenario Outline: Series # features/fibonacci.feature:6
When I ask Javascript to calculate fibonacci up to #
Then it should give me #

Examples:
| n | series |
| 1 | [] |
| 2 | [1, 1] |
| 3 | [1, 1, 2] |
| 4 | [1, 1, 2, 3] |
...
@another-do-fibonnacci-in-before-hook
Scenario: Single series with Before hook with a tag label # features/fibonacci.feature:41
Then it should give me [1, 1, 2] #

Scenario: Single series by calling a step from within a step # features/fibonacci.feature:45
Then it should give me [1, 1] via calling another step definition #

12 scenarios (12 passed)
21 steps (21 passed)
0m1.420s
So what the hell? I spent a long time trying to get this working last night, so I cannot let that pass without trying to understand. Sadly, I cannot get this to fail again, even after trying:
  • reverting yesterday's cucumber patch
  • reverting to therubyracer version 0.7.0
  • even switching back to my "global" rvm gemset
Bah!

Eventually, I do track this down. It turns out the patch that am using now is not quite the same as the one I used last night. Last night, I had used the following line directly from the patch in Charles Lowell's cucumber fork:
js_function.call(@world.scope, *js_args)
Earlier today I manually patched the Cucumber source by searching and replacing ToString with to_s. That left the above line in this state:
@world.eval("(#{js_function.to_s})(#{js_args.join(',')});")
I am not sure what semantics (Ruby's or Javascript's) the call and eval statements are using in there. At this point, I am just happy to have it working. It would have been nicer if I realized it was working when I first made my "dumb" patch, but c'est la vie.

My complete patch, for what it's worth.

Day #123

Wednesday, June 2, 2010

No Joy for Cucumber on V8

‹prev | My Chain | next›

Up tonight, I'd like to see if I can write some Cucumber scenarios in the v8 Javascript engine. Joseph Wilk recently wrote a blog post on how to do this, so hopefully it will prove straight forward.

I install therubyracer gem as normal:
gem install therubyracer
I also checkout the latest version of cucumber to be sure that I have the recent javascript bindings. The javascript example are located in the examples/javascript directory, along with a Rakefile to run them. Unfortunately, when I do run the examples, I find:
/home/cstrom/.rvm/rubies/ruby-1.8.7-p249/bin/ruby -I "/home/cstrom/repos/cuke.real/lib:lib" "/home/cstrom/repos/cuke.real/bin/cucumber" 
Uncaught Error: undefined method `ToString' for #: /home/cstrom/repos/cuke.real/bin/../lib/cucumber/js_support/js_dsl.js:12 (V8::JavascriptError)
/home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/therubyracer-0.7.0/lib/v8/context.rb:33:in `eval'
/home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/therubyracer-0.7.0/lib/v8/context.rb:38:in `evaluate'
/home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/therubyracer-0.7.0/lib/v8/context.rb:43:in `load'
/home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/therubyracer-0.7.0/lib/v8/context.rb:42:in `open'
/home/cstrom/.rvm/gems/ruby-1.8.7-p249/gems/therubyracer-0.7.0/lib/v8/context.rb:42:in `load'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/js_support/js_language.rb:31:in `send'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/js_support/js_language.rb:31:in `method_missing'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/js_support/js_language.rb:117:in `load_code_file'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/step_mother.rb:84:in `load_code_file'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/step_mother.rb:76:in `load_code_files'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/step_mother.rb:75:in `each'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/step_mother.rb:75:in `load_code_files'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/cli/main.rb:56:in `execute!'
/home/cstrom/repos/cuke.real/bin/../lib/cucumber/cli/main.rb:25:in `execute'
/home/cstrom/repos/cuke.real/bin/cucumber:8
rake aborted!
Command failed with status (1): [/home/cstrom/.rvm/rubies/ruby-1.8.7-p249/b...]

(See full trace by running task with --trace)
I am not sure what the deal is with the capital ToString. Converting it to toString has no effect. Converting it to to_s has some effect, but not enough. Eventually, I happen across an issue opened by Corey Haines on therubyracer's github issue tracker. Nice! That is the same backtrace I am getting and the problem looks to be solved by Charles Lowell himself.

After checking out Charles's fork of Cucumber, I am able to run the Cucumber feature, but the fibonnaci function seems to always return an empty array:
  @fibonacci
Scenario Outline: Series # features/fibonacci.feature:7
When I ask Javascript to calculate fibonacci up to <n> #
Then it should give me <series> #

Examples:
| n | series |
| 1 | [] |
Uncaught Expected <'[]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 2 | [1, 1] |
Uncaught Expected <'[1, 1]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 3 | [1, 1, 2] |
Uncaught Expected <'[1, 1, 2]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 4 | [1, 1, 2, 3] |
Uncaught Expected <'[1, 1, 2, 3]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 6 | [1, 1, 2, 3, 5] |
Uncaught Expected <'[1, 1, 2, 3, 5]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 9 | [1, 1, 2, 3, 5, 8] |
Uncaught Expected <'[1, 1, 2, 3, 5, 8]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
| 100 | [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] |
Uncaught Expected <'[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]'> but got <[]>: features/support/env.js:3 (V8::JavascriptError)
features/fibonacci.feature:9:in `Then it should give me <series>'
This one has me stumped, unfortunately. The problem seems to be with the fibonnaci number passed into the scenario. It ought to be a string and mostly behaves like a string, but, when I throw the parseInt value
var fibonacciSeries = function(fibonacciLimit) {
var result = Array();
var currentfibonacciValue = fibonacci(1);
var i = 2;
throw " " + currentfibonacciValue + " " + fibonacciLimit + " " + typeof(fibonacciLimit) + " " + parseInt("" + fibonacciLimit.toString() + "", 10);
while(currentfibonacciValue < parseInt(fibonacciLimit)) {
result.push(currentfibonacciValue);
throw result.join(",");

currentfibonacciValue = fibonacci(i);
i++;
}
// result = [1,1];
return "[" + result.join(", ") + "]";
}
I always end up with NaN:
 @fibonacci
Scenario Outline: Series # features/fibonacci.feature:7
When I ask Javascript to calculate fibonacci up to <n> #
Then it should give me <series> #

Examples:
| n | series |
| 1 | [] |
Uncaught 1 '1' string NaN: features/lib/fibonacci.js:9 (V8::JavascriptError)
features/fibonacci.feature:8:in `When I ask Javascript to calculate fibonacci up to <n>'
| 2 | [1, 1] |
Uncaught 1 '2' string NaN: features/lib/fibonacci.js:9 (V8::JavascriptError)
features/fibonacci.feature:8:in `When I ask Javascript to calculate fibonacci up to <n>'
...
I am a bit disappointed to have to call it a night here. Tomorrow, I will pick it back up with fresh eyes and possibly look to install the pre-release version of therubyracer in the hopes of resolving this.


Day #122

Tuesday, June 1, 2010

Retrospective: Week Sixteen

‹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

  • Testing fab.js. I have been threatening to do this for weeks. It felt great to finally get to it.
  • Since there is little in the way of documentation on testing fab.js code, I had to figure out much on my own. That turned out to be a fabulous way to become even more familiar with fab.js.
  • Was able to test both a unary and binary (fab) app.
  • I did quite well at figuring out how to test, then going back the next day and figuring out how to do it well. Again, that aided my overall understanding of fab.js immensely.
  • Learned a bit about the commonjs library exports and require statements.

OBSTACLES / THINGS THAT WENT NOT SO WELL

  • I spent the entire week on testing two small (fab) apps and refactoring the aftermath.
  • I was surprised at the gap between how well I thought I understood fab.js and reality. Writing tests really exposed some key weaknesses.
  • For both apps, it took at least two tries to get the testing right.

WHAT I'LL TRY NEXT WEEK

  • Cucumber on the v8 engine. I have no idea how well it will work for me. I'd like to find out.
  • Investigate other Javascript / node.js testing frameworks.
  • Get back to driving the game to a point that the kids can write code for it.


Day #121