Bookmark this on Hatena Bookmark
Hatena Bookmark - Ruby on enchant.js: Packaging Succession with HotRuby Insertion Classes
Share on Facebook
Post to Google Buzz
Bookmark this on Yahoo Bookmark
Bookmark this on Livedoor Clip
Share on FriendFeed

Our story about using Ruby on enchant.js with HotRuby is nearing its climax. Up until now we’ve focused on whether enchant.js can be called up from Ruby, but this time I tried to actually package it with class succession.

Corrections to HotRuby.protype.classes were among the main additions to the code. Up until now we did anything and everything with Sprites, but here we’ve added Scene and Entity classes.

Entity is the parent class for Sprite, and I prepared a Label for other classes using Entity.

"Sprite" : {
"initialize" : function(recver, args) {
var sprite = new Sprite(args[0],args[1]);
recver.__instanceVars.entity = sprite;
HotRuby.prototype.classes.extendRubyClass("Sprite","Entity");
},
"image" : function(recver,args) {
return HotRuby.prototype.classes.
newNativeObject("String",
recver.__instanceVars.entity.imageName);
},
"image=" : function(recver,args) {
recver.__instanceVars.entity.imageName=args[0].__native;
return recver.__instanceVars.entity.image=
game.assets[args[0].__native];
}
},
"Label" : {
"initialize" : function(recver, args) {
var label = new Label(args[0].__native);
recver.__instanceVars.entity = label;
HotRuby.prototype.classes.extendRubyClass("Label","Entity");
},
"text" : function(recver,args) {
return HotRuby.prototype.classes.
newNativeObject("String",
recver.__instanceVars.entity.text);
},
"text=" : function(recver,args) {
return recver.__instanceVars.entity.text=args[0].__native;
}
},
"Entity" : {
"initialize" : function(recver, args) {
var obj = HotRuby.prototype.classes["Entity"];
'x y scaleX scaleY rotation frame'.split(' ').forEach(function(prop) {
obj[prop] = function(recver,args){
return recver.__instanceVars.entity[prop];};
obj[prop+"="] = function(recver,args){
recver.__instanceVars[prop]=args[0];
return recver.__instanceVars.entity[prop]=args[0];};
obj["@"+prop+"="] = function(recver,args){
recver.__instanceVars[prop]=args[0];
return recver.__instanceVars.entity[prop]=args[0];};
obj[prop+"+="] = function(recver,args){
return recver.__instanceVars.entity[prop]+=args[0];};
obj[prop+"-="] = function(recver,args){
return recver.__instanceVars.entity[prop]-=args[0];};
obj[prop+"*="] = function(recver,args){
return recver.__instanceVars.entity[prop]*=args[0];};
obj[prop+"/="] = function(recver,args){
return recver.__instanceVars.entity[prop]/=args[0];};
obj[prop+"++"] = function(recver,args){
return recver.__instanceVars.entity[prop]++;};
obj[prop+"--"] = function(recver,args){
return recver.__instanceVars.entity[prop]--;};
obj[prop+"<"] = function(recver,args){
return recver.__instanceVars.entity[prop] < args[0] ? this.trueObj : this.falseObj;};
obj[prop+"<="] = function(recver,args){
return recver.__instanceVars.entity[prop] <= args[0] ? this.trueObj : this.falseObj;};
obj[prop+">"] = function(recver,args){
return recver.__instanceVars.entity[prop] > args[0] ? this.trueObj : this.falseObj;};
obj[prop+">="] = function(recver,args){
return recver.__instanceVars.entity[prop] >= args[0] ? this.trueObj : this.falseObj;};
obj[prop+"=="] = function(recver,args){
return recver.__instanceVars.entity[prop] == args[0] ? this.trueObj : this.falseObj;};
});
},
"setupEventListener" : function(recver,args) {
var classObj = ruby.__proto__.classes[recver.__className];
var hr = this;
'enterframe touchstart touchend touchmove'.split(
' ').forEach(function(eventName){
if(classObj[eventName]){
var func = function() {
var hr = arguments.callee.hr;
var proc = arguments.callee.proc;
hr.runOpcode(
proc.__opcode,
proc.__parentStackFrame.classObj,
proc.__parentStackFrame.methodName,
recver,
hr.nativeAryToRubyObjectAry(arguments),
proc.__parentStackFrame,
true);
};
func.hr = hr;
func.proc={};
func.proc.__parentStackFrame=ruby.topSF;
func.proc.__opcode= classObj[eventName];
recver.__instanceVars.entity.addEventListener(
eventName,func);
}
});
},
"addEventListener" : function(recver,args) {
var func = function() {
var hr = arguments.callee.hr;
var proc = arguments.callee.proc;
hr.runOpcode(
proc.__opcode,
proc.__parentStackFrame.classObj,
proc.__parentStackFrame.methodName,
recver,
hr.nativeAryToRubyObjectAry(arguments),
proc.__parentStackFrame,
true);
};
func.hr = this;
func.proc = args[1];
recver.__instanceVars.entity.addEventListener(
args[0].__native,
func);
},
},

As you can see, up until now most of the Sprite contents was divided up as Entity shared contents, and only image (Sprite-native parameter) is treated as a Sprite class.

Also, Label (which like Sprite has the superclass Entity) only provides elements distinct to the “text” Label.

HotRuby class succession is realized with functions like what you see below:

extendRubyClass:function(subClass,superClass,recver,args){
//Ruby class succession
var subClass = HotRuby.prototype.classes[subClass];
var superClass = HotRuby.prototype.classes[superClass];
superClass["initialize"](recver,args); //Calls up parent class constructor
//Class construction rewriting only happens once
if(subClass[superClass+"_done"]){
return;
}
subClass[superClass+"_done"]=true; //Class construction initialization ending flag
for(var i in superClass){ //Copies parent class members (succession)
if(!subClass[i]){
subClass[i]=superClass[i];
}
}
},

HotRuby’s internal construction is such that all included classes are all stored as associative arrays in HotRuby.prototype.classes.

For succession, all you need to do is copy the elements of the super class. At this point, it’s still a fairly whatever-works sort of construction, so it doesn’t work when not calling up the superclass constructor (in normal object orientation the super class constructor is not called up unless explicitly stated).

In any event, here I was able to package Label. The Ruby code looks something like this:

class Bear < Sprite
def initialize w,h,image
super(w,h)
self.image = Game.assets(image)
self.y=50
self.setupEventListener();
end
def enterframe
self.x+=1
end
end
bear = Bear.new(32,32,"chara1.gif")
label = Label.new("Hello enchant.js");
Game.rootScene.addChild(bear)
Game.rootScene.addChild(label)

The “Hello enchant.js” displayed in the opening screenshot is based on work of the Label class.

Of course, you can set both Label and Sprite to subclass event handlers.

class Button < Label
def initialize x,y,text
super text
self.x = x
self.y = y
self.setupEventListener();
end
def touchend
self.text="Thank you!"
end
end
button = Button.new(100,100,"Tap Here");
Game.rootScene.addChild(button)

If you click the “Tap Here” label displayed on the screen, the display should change to read “Thank you” courtesy of the program you just wrote.

Seems like we’ve picked up enough of the essentials to create a game. You can probably create a simple game with this info alone.

If we prepare the Group and Map classes, we should be able to put together most any enchant.js game.

The Group class can be packaged with comparative ease, but he Map class looks a bit tricky. Won’t know until we try though, will we? It might be fairly easy with Ruby’s two dimensional arrays.

Let’s take a breath and move on to the next challenge!