This is a multi-part series. The first in the series is here.
Tower of Hanoi — With Rule Disks (and no stack)
The first passes at the Tower of Hanoi algorithm were all done with the algorithm being a recursive call on the stack. This is ‘true’ as an algorithm, but comes off as a bit unnatural for humans. But it also has a problem in that the algorithm can not be ‘suspended’ in mid activity unless the language allows a feature (called a continuation) that can suspend the stack itself. For a multi-threaded language, this inability to suspend may not be a problem because you could create one thread for the algorithm and another thread to listen for whenever a new (interesting) change in state occurs. This is how the RubyShoes version works: the algorithm is in a new thread separate from the GUI thread (otherwise things behave badly). Similarly, the Smalltalk-80 GUI is being drawn in a different thread than the main execution thread. But in many circumstances even this multi-thread version would not work: say you have a client-server version (e.g. Rails) or want to pause/suspend the execution of the 100-tall tower of hanoi. And finally, if you only have a single-threaded language like Flex, things just don’t work at all for intermediate renderings unless you can make the algorithm not be dependent on the call stack.
Tower of Hanoi — With Rule Disks (and no stack) – Flex
I think it is easiest to work up from the Disk perspective, although see the book for a different flow. The main changes to HanoiDisk are to have it be able to figure out whether it has any legal moves, and what the bestMove (next move) would be. To do each of these, the disk needs to communicate back with the towers. In the Smalltalk code this was done through a Class Variable, but it is far simpler and more scalable to do this through instance variables: each Disk needs to know what tower it belongs to.
Compared to the Smalltalk version of this, the main difference is we are creating Array objects instead of passing a function into an iterator. Again this seems the more natural for Flex… but… it is starting to get annoying looking and has real performance impact, so later we should try to do an iterator + function-based version.
AnimatedRulesTowerOfHanoi (No Animation)
The change to the Tower code for Flex is two parts:
Provide methods that support the rules that the smarter HanoiDiskRules has
Drive the algorithm in a way to enable Flex to render the intermediary results
We can do these in two steps, with the current step focused on just the algorithm changes
package{publicclassAnimatedRulesTowerOfHanoiextendsAnimatedTowerOfHanoi{protectedvarmy_oldDisk:HanoiDisk;protectedvarmy_currentDisk:HanoiDisk;protectedvarmy_destinationDisk:HanoiDisk;protectedoverridefunctionmoveTower(height:int,fromPin:*,toPin:*,usingPin:*):void{//Now that the disks know all the rules... we can ignore all the arguments!while(!doNextMove_IsDone){};}protectedfunctiondoNextMove_IsDone():Boolean{my_currentDisk=this.decide();(my_stacks[my_currentDisk.pole-1]asArray).pop();(my_stacks[my_destinationDisk.pole-1]asArray).push(my_currentDisk);my_currentDisk.moveUpon(my_destinationDisk);my_oldDisk=my_currentDisk;my_view.noteChange();returnisAllOnOneTower();}protectedfunctionisAllOnOneTower():Boolean{for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(eachStack.length==my_height)returntrue;}returnfalse;}publicfunctionselectTopsOtherThan(disk:HanoiDisk):Array{varresult:Array=newArray();for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(eachStack.length==0)continue;vartopDisk:HanoiDisk=eachStack[eachStack.length-1];if(topDisk!==disk){result.push(topDisk);}}returnresult;}publicfunctionselectPolesOtherThan(disk:HanoiDisk):Array{varresult:Array=newArray();for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(i==disk.pole-1)continue;if(eachStack.length==0){result.push(my_mockDisks[i]);}else{vartopDisk:HanoiDisk=eachStack[eachStack.length-1];result.push(topDisk);}}returnresult;}protectedfunctiondecide():HanoiDisk{vartops:Array=selectTopsOtherThan(my_oldDisk);for(vari:int=0;i<tops.length;i++){varmovingDisk:HanoiDiskRules=tops[i];if(movingDisk.hasLegalMove()){my_destinationDisk=movingDisk.bestMove();returnmovingDisk;}}//This should never happenreturnnull;}protectedoverridefunctioncreateDisk():HanoiDisk{varresult:HanoiDiskRules=newHanoiDiskRules();result.setupTowers(this);returnresult;}}}//package
As mentioned in HanoiDiskRules section, the main annoyance of this particular implementation of the new algorithm compared to Smalltalk is having to create intermediate array objects just to communicate between the Disk and the Tower.
But things should work again. Except we will still get only one rendering because the whole algorithm is executed in one call within a “while” loop.
Tower of Hanoi — With Rule Disks and Correct Animation – Flex
To make things work in Flex now, all we do is have to unwrap the immediacy [single call stack] of the while loop. Instead of calling each ‘doNextMove’ immediately, we will wait for an event. That event could be anything, like clicking a button or the server sending a response. Each time we get an event, we will do the next step. To be easy to present and view, we will make the events be driven by a simple timer. Each time the timer fires an event, we will do the next step. Repeating this until we are done.
The only method changes are to ‘moveTower’ and to ‘handleTimer’. Everything else works just the way it was.
package{importflash.events.TimerEvent;importflash.utils.Timer;publicclassAnimatedRulesTowerOfHanoiextendsAnimatedTowerOfHanoi{protectedvarmy_oldDisk:HanoiDisk;protectedvarmy_currentDisk:HanoiDisk;protectedvarmy_destinationDisk:HanoiDisk;protectedvarmy_timer:Timer;protectedoverridefunctionmoveTower(height:int,fromPin:*,toPin:*,usingPin:*):void{//Now that the disks know all the rules... we can ignore all the arguments!my_timer=newTimer(300)my_timer.addEventListener(TimerEvent.TIMER,handleTimer);my_timer.start();}publicfunctionhandleTimer(evt:TimerEvent):void{varisDone:Boolean=doNextMove_IsDone();if(isDone){my_timer.stop();my_timer=null;}}protectedfunctiondoNextMove_IsDone():Boolean{my_currentDisk=this.decide();(my_stacks[my_currentDisk.pole-1]asArray).pop();(my_stacks[my_destinationDisk.pole-1]asArray).push(my_currentDisk);my_currentDisk.moveUpon(my_destinationDisk);my_oldDisk=my_currentDisk;my_view.noteChange();returnisAllOnOneTower();}protectedfunctionisAllOnOneTower():Boolean{for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(eachStack.length==my_height)returntrue;}returnfalse;}publicfunctionselectTopsOtherThan(disk:HanoiDisk):Array{varresult:Array=newArray();for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(eachStack.length==0)continue;vartopDisk:HanoiDisk=eachStack[eachStack.length-1];if(topDisk!==disk){result.push(topDisk);}}returnresult;}publicfunctionselectPolesOtherThan(disk:HanoiDisk):Array{varresult:Array=newArray();for(vari:int=0;i<my_stacks.length;i++){vareachStack:Array=my_stacks[i];if(i==disk.pole-1)continue;if(eachStack.length==0){result.push(my_mockDisks[i]);}else{vartopDisk:HanoiDisk=eachStack[eachStack.length-1];result.push(topDisk);}}returnresult;}protectedfunctiondecide():HanoiDisk{vartops:Array=selectTopsOtherThan(my_oldDisk);for(vari:int=0;i<tops.length;i++){varmovingDisk:HanoiDiskRules=tops[i];if(movingDisk.hasLegalMove()){my_destinationDisk=movingDisk.bestMove();returnmovingDisk;}}//This should never happenreturnnull;}protectedoverridefunctioncreateDisk():HanoiDisk{varresult:HanoiDiskRules=newHanoiDiskRules();result.setupTowers(this);returnresult;}}}//package