It’s very lively isn’t it!

I’ve got a confession to make… I’ve always had a soft spot for Smalltalk. It all started in the mid-90’s  when I read the book Smalltalk-80: Bits of History, Words Of Advice This told the story of the porting of the Smalltalk virtual machine onto a number of different platforms, allowing them to run the base Smalltalk-80 image released by Xerox Parc. The idea of having a single image containing the whole smalltalk environment which could be ported to many different machines just by implementing a small virtual machine was very impressive. At the time it was even more cutting edge because the machine supported windows and a mouse, with the windowing mainly achieved by the use of a single BitBlt operation acting on a bit mapped display which it used for doing all of the windowing operations.
 
The Smalltalk language itself impressed me greatly. The language is pretty simple with little syntax and is conceptually simple as every operation is regarded as a message send to an object which can be as simple as an integer or as complicated as compiler. The power comes from the libraries which come as part of the VM image.
 
A couple of months ago I was listening to the talks at the 2008 lang.NET symposium when one of the smalltalk names I recognised cropped up. This was a presentation of the Lively Kernel, a javascript implementation of the Morphic user interface that has been available for some time on Squeak smalltalk. The implementation is entirely in javascript, but uses SVG for rendering the graphics elements. Events raised on the SVG elements are converted into events that are processed by the javascript code which can then use the browser’s DOM to adjust the SVG elements.
Unfortunately, the system only runs on a few browsers which have good enough SVG support to make the system worth using. It is also, at the current time, rather slow.
 
In my experience there is only one good way to learn about a graphics system. This is to write the game of pong. This game requires animation type graphics (which one would like to make flicker free), event handling and timer events so it touches most parts of a window library.
 
Armed with build 2.0.0.14 of Firefox and the Venkman javascript debugger add-on I set off.
 
The first thing to note is that the lively kernel includes a version of prototype, a javascript library for aiding the construction of large code bases in javascript. In particular, the prototype library allows javascript entities to be defined with a notion of inheritance.
 
Object.subclass(‘Animal’,
  {
    initialize: function(name)
    {
     this.name = name;
    },
 
    getName : function()
    {
      return this.name;
    },
 
    makeNoise: function()
    {
       alert("Generic animals can’t make a noise");
    }
  });
 
Animal.subclass("Bear",
  {
    initialize: function($super, name)
    {
       $super(name);
    },
 
    makeNoise: function()
    {
      alert(this.getName() + ": growl");
    }
  });
 
rupert = new Bear("Rupert");
 
In the above code fragment, the prototype library takes special notice of an initial parameter named $super, and redefines the name within the scope of the function body to mean the function defined in the parent class. Hence, the initialize function on Bear calls into the initialize function on Animal, so that rupert.makeNoise
will alert with the string "rupert: growl". We can use the same idea to define a subclass of the standard Morph class, which can play the game of pong.
The WindowMorph allows the area of the screen to have a title.
 
WindowMorph.subclass(‘PongMorph’, {
 
We define a conventional initialize function to setup the state of the object. The pong game consists of a PanelMorph (a panel) on which we place the two bats and a ball.
 
    initialize: function($super, position) {
 
        pongBoard = new PanelMorph(pt(300,300));
        $super(pongBoard, ‘Pong’, position);
 
        playerx = 20;
        playery = 20;
        playerBat = Morph.makeLine([pt(20,20),pt(20,50)],4,Color.blue);
        playerBatPosition = new Rectangle(20,20,4,30);
        pongBoard.addMorph(playerBat );
 
        computerx = 280;
        computery = 20;
        computerBat = Morph.makeLine([pt(280,20),pt(280,50)],4,Color.blue);
        computerBatPosition = new Rectangle(280,20,4,30);
        pongBoard.addMorph(computerBat);
 
        ballSpeed = 5;
        ballx = 100;
        bally = 100;
        ballxdiff = ballSpeed;
        ballydiff = ballSpeed;
        ballPosition = new Rectangle(ballx,bally,20,20);
        ball = Morph.makeCircle(pt(ballx,bally), 10, 1, Color.blue, Color.blue);
        pongBoard.addMorph(ball);
        boardExtent = pongBoard.getExtent();
        return this;
    },
 
The startSteppingScripts function starts a timer that will call the moveBall function ten times a second.
 
    startSteppingScripts: function() {
        this.startStepping(100, "moveBall");
    },
 
The moveBall function checks if the ball is sufficiently close to a bat to cause it to rebound. If not, we can check for a bounce against the walls. Lastly we can check for whether the computer should move its bat, which it will do 70% of the time in the direction of the ball.
    moveBall: function()
    {
        var dist = ballPosition.dist(playerBatPosition);
        if (dist == 0)
        {
          ballxdiff = -ballxdiff;
        }
        var dist = ballPosition.dist(computerBatPosition);
        if (dist == 0)
        {
          ballxdiff = -ballxdiff;
        }
        if (ballx + ballxdiff <= 0 || ballx + ballPosition.width + ballxdiff >= boardExtent.x) 
        {
          ballxdiff = -ballxdiff;
        }
       else
       {
         ballx += ballxdiff;
        }
       if (bally + ballydiff <= 0 || bally + ballPosition.height + ballydiff >= boardExtent.y) 
       {
          ballydiff = -ballydiff;
       }
       else
       {
          bally += ballydiff;
        }
        ball.setPosition(pt(ballx, bally));
        ballPosition.x = ballx;
        ballPosition.y = bally;
        if(Math.random() < 0.7)
        {
           if (bally > computery)
           {
             computery += ballSpeed;
             computerBatPosition.y = computery;
             computerBat.setPosition(pt(computerx, computery));
           }
           if (bally < computery)
           {
             computery -= ballSpeed;
             computerBatPosition.y = computery;
             computerBat.setPosition(pt(computerx, computery));
           }  
        }
    },
We need to say that the Morph handles the mouse down event
 
    handlesMouseDown: function () {
       return true;
    },
 
And when it gets it, we request the keyboard focus so that we can process keys corresponding to the movement of the user bat, in this case, the up and down arrow keys.
 
    onMouseDown : function ($super,evt) {
       this.requestKeyboardFocus(evt.hand);
       $super(evt);
       return true;
    },
 
These functions allow us to accept the keyboard focus.
 
    takesKeyboardFocus: function() {
        return true;
    },
 
    setHasKeyboardFocus: function(newSetting) {
        return newSetting;
    },
 
If the up or down arrow keys are pressed, then we need we move the bat in the appropriate direction. If keyboard key is held down and repeats we can generate so many events that the timer events do not get a chnace to fire, so we do a cycle of the timer action of moving the ball on processing each keyboard event.
 
    onKeyDown: function (event) {
    var key = event.getKeyCode() || event.charCode;
    if (key == Event.KEY_UP)
    {
      if (playery – ballSpeed >= 0)
      {
        playery = playery – ballSpeed;
        playerBat.setPosition(pt(playerx, playery));
      }
    }
    if (key == Event.KEY_DOWN)
    {
      if (playery + playerBatPosition.height + ballSpeed <= boardExtent.y)
      {
        playery = playery + ballSpeed;
        playerBat.setPosition(pt(playerx, playery));
      }
    }
    playerBatPosition.x = playerx;
    playerBatPosition.y = playery;
    event.stop();
    this.moveBall();
    },
 
This prevents the sustem drawing a focus halo after the pong area is selected.
 
    onFocus: function () {},
   
});
Finally we need to make an instance of this class. I did this by putting the following code in the populateWorldWithExamples function in main.js in the lively kernel 0.8 distribution which I ran locally.
 
    var pong = new PongMorph(pt(200,200).extent(300,600));
    world.addMorph(pong);
    pong.startSteppingScripts();
 
 
What did I learn from the experience?
 
That currently the javascript/SVG interpreter combination is not yet fast enough to make develop a painless process. The javascript code is single threaded, so it is hard to do too much work inside timer callbacks without the whole GUI slowing down dramatically. The lively kernel library contains a lot of code for making it easy to do XMLHTTP calls so it would be possible to push some of this work onto the web server.
 
SVG allows the graphics to be quite spectacular.
 
There is an inbuilt model/view/controller setup with changed events being propagated from the model into the views to allow multiple views on the same data.
 
Included in the distribution there are a number of browsers which look a lot like the smalltalk object and method browsers. With these, and extra reflection facilities supplied by the prototype library, it is easy to see the javascript code for a given method on a given class. Unfortunately the methods are not sorted into protocols so it is really hard to see which ones fit together. I had a lot of problems dealing with the keyboard focus and still haven’t got it working properly.
 
The use of javascript is neat, but the debugging experience isn’t the same as using real smalltalk. I often had to resort to editing the lively kernel javascipt source to include a debugger call to allow me to single step the code in Venkman. In Smalltalk I’d be able to embed a break into a method without the whole system stopping while I debugged but the single threaded nature of the browser stops this.
 
There are methods for serialising the Morph objects back to the web server from which they came. I couldn’t find enough documentation on this to experiment much with this part of the system.
 
Writing and developing in javascript is certainly one way to go. Another is to do what other projects are doing and regard javascript as the target language for a compiler and framework written in another high level language. In other words to use javascript as the assembly language of the net rather than the implementation language for large projects. It will be interesting to see how the lively kernel goes in the future.
 
Advertisements
This entry was posted in Computers and Internet. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s