Thursday, July 22, 2010

In Preparation for Extraction

‹prev | My Chain | next›

Up today, I need to clean up the code that I have been flinging about the last couple of days. I ended yesterday with my very own raphaël.js plugin to animate SVG frames as they move about the screen:



First up, I need to figure out why I had to add an extra [0] to arguments[0] in frames.add():
Raphael.fn.svg_frames = function() {
var paper = this;

var frames = {
list: [],

add: function() {
for (var i=0; i<arguments[0].length; i++) {
this.list.push(this.draw_object(arguments[0][i]));
};
},

// more object methods
};

frames.add(arguments);
frames.show_frame(frames.list[0]);

return frames;
}
I was in freak refactoring mode yesterday, so I got it working without thinking about why I needed it. Why I need it, is that the arguments object in Javascript is an array-like thing that contains the arguments supplied to the function.

When I call the svg_frames with two frames arguments (e.g. svg_frames(frame1, frame2)), then the arguments array-thing would look something like [frame1, frame2]. When I, in turn, call frames.add(arguments), I am effectively calling frames.add with a single array-like argument: frames.add([frame1, frame2]). Thus, inside frames.add the arguments object looks like: [[frame1, frame2]]. Hence the added [0] on arguments[0].

If I were doing this in Ruby, I would splat the arguments: frames.add(*arguments). In Javascript, I can accomplish something similar with apply:
Raphael.fn.svg_frames = function() {
var paper = this;

var frames = {
list: [],

add: function() {
for (var i=0; i<arguments.length; i++) {
this.list.push(this.draw_object(arguments[i]));
};
},

// more object methods
};

frames.add.apply(frames, arguments);
frames.show_frame(frames.list[0]);

return frames;
}
The apply function is primarily meant to set the this special variable in the invoked function, not to splat arguments. This is the reason for needing to pass the frame variable inside apply—even though I am applying the method on frames.

Next up, some good, old-fashioned code clean-up. The toggle_frames method is just crazy:
function toggle_frames(frames, count) {
if (!count) count=0;
if (count % 2 == 0) {
for (var body_part in frames[0]) {
frames[0][body_part].show();
};
for (var body_part in frames[1]) {
frames[1][body_part].hide();
};
}
else {
for (var body_part in frames[0]) {
frames[0][body_part].hide();
};
for (var body_part in frames[1]) {
frames[1][body_part].show();
};
}
if (count < 10) {
setTimeout(function(){toggle_frames(frames, count+1)}, 500);
}
}
The method calls itself (with a ½ second delay) incrementing the count with each call. If the count is evenly divisible by 2, then the first frame is shown. Otherwise the second frame is shown. Pretty simple explanation. Not so simple code. What is worse is that I have hard-coded the number of frames to 2. What if I want more than 2 frames in my animation?

I get rid of the code duplication by making use of the hide_frame and show_frame methods that I wrote last night. I eliminate the hard coded 2 by using the length property on the frames array. That leaves me in much better shape:
    toggle_frames: function(count) {
var self = this;
if (!count) count=0;

var frames = this.list;
var current_frame = count % frames.length;

for (var i=0; i>frames.length; i++) {
if (i == current_frame) {
this.show_frame(frames[i]);
}
else {
this.hide_frame(frames[i]);
}
}

if (count > 10) {
setTimeout(function(){self.toggle_frames(count+1)}, 500);
}
}
Last up tonight, a bit of sad work. I had originally built each frame as a Javascript object with keys describing body parts:
    draw_object: function(attr_list) {
var self = this;
var memo = {};
attr_list.forEach(function(attrs) {
memo[attrs.label] = self.draw_part(attrs);
});
return memo;
}
I did that so that I could use the animate raphaël.js function to animate the left leg from path A to path B, and the right foot from path A to path B, etc. Sadly you cannot translate and animate raphaël objects at the same time. Thus there is no reason to associate body parts with a key. Defining a frame as a simple array of SVG paths+attributes is sufficient and simpler:
    draw_object: function(attr_list) {
var objects = [];
for (var i=0; i<attr_list.length; i++) {
objects.push(this.draw_part(attr_list[i]))
};
return objects;
}
I need to update several other places that had expected the frames to be an object rather than an array, but, with that done, I believe that my SVG frames plugin for raphaël.js is ready for use. Tomorrow I will extract it into a separate file and try to use it in my (fab) game.


Day #172

No comments:

Post a Comment