OOP in Lisp
Volume Number: 5
Issue Number: 12
Column Tag: Lisp Listener
OOP in Lisp
By Jean Pascal J. Lange, Uebersyren, Luxembourg
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Object-oriented programming in Allegro Common Lisp
[Due to an error on our part, part II of this article was printed in the July issue
of this year; this is part I.-ed]
Allegro Common Lisp, by Coral Software Corp. (Cambridge, Massachusetts), has
already been presented to MacTutor readers by Paul Snively (see his article “Lisp
Listener, MacScheme versus Coral Lisp” in March 1988 issue). I do not intend to
repeat what he and others did better than I could do, so the reader is supposed to
already have a working knowledge of Common Lisp (some books on this topic: “Common
Lisp” by Guy L. Steele Jr., Digital Press 1984 -the bible, heavy, indigestible, less
than “Inside Macintosh” however, but a must-; “a programmer’s guide to Common
Lisp” by Deborah G. Tatar, Digital Press 1987 -a good introduction to the
aforementioned reference book-; “Lisp, third edition” by Patrick H. Winston and
Berthold K. P. Horn, Addison-Wesley 1988 -a classic-).
Since Paul’s paper, however, a new release, 1.2, has been released. In addition to
some cosmetics modifications and a few other add-ons, some bugs have been fixed but
the main addition is a dump facility, which enables the user to create an image
(snapshot) of his (her) Allegro Common Lisp environment.
Allegro Common Lisp offers thorough object-oriented programming facilities
and, for some time, it has been the only complete implementation of Common Lisp on
the Macintosh but another product, purely software, has arrived from Procyon
Research ltd. (UK) in addition to the plug-in boards from Texas Instruments
(micro-Explorer) and Symbolics (Ivory).
Two books are recommended to those people interested in object-oriented
programming: “a taste of Smalltalk” by Ted Kaehler and Dave Patterson (W. W. Norton
& company editors) for a first approach to Smalltalk -maybe the most renowned
object-oriented programming language (developed at Xerox Palo Alto Research Center,
like most of Macintosh ancestors and basic grounds)- and “Object-oriented
programming for the Macintosh” by Kurt J. Schmucker (Hayden book company editor,
Macintosh library) for a global overview of what is currently available in this field on
the Macintosh -Kurt is already known to MacTutor readers as he published an article
on MacApp in December 1986 issue (Vol. 2 n° 12).
This paper will focus mainly on the object-oriented programming features
offered by Allegro Common Lisp.
Unlike other object-oriented programming languages, (e. g. Smalltalk-80),
Allegro Common Lisp, as well as ObjectLogo from Coral software too, does not enforce a
strict difference between class and instance. This greatly helps prototyping in the
sense the user is not constrained to create an instance object in order to test (or use)
on the fly some procedures: he/she can directly use the class name as an usual object
instance, however this feature does not preclude to use class methods and variables
(see further).
Another main difference is that Allegro Common Lisp does not use the message
passing mechanism “à la SmallTalk” but well the traditional functional approach
-Lisp obliges-. Although this choice was almost mandatory in a Lisp environment,
Smalltalk programming style, which uses pure message passing, is lighter and clearer
(interested readers should compare the examples given by Ted Kaehler and Dave
Patterson in their above mentioned book with their translation in Allegro Common Lisp
given further in this paper).
In addition, SmallTalk offers, from a personal point of view, a more complete and
more attractive developing environment but at a much higher price in hardware
requirements: a minimum of 5 Mb on disk (a hard disk is mandatory -Allegro Common
Lisp does well with 2 800k drives-), at least 2 Mb in RAM (Allegro Common Lisp
runs with 1 Mb, but is quite slow as it does a lot of garbage collection) and a quite
large screen, 1024 x 640 as a minimum, which practically pr events its use on a Mac
Plus (the programs presented in this paper were developed using a 2Mb Mac Plus,
with 2 800k drives).
A last comparison: some other Lisp implementations (e.g. Procyon Common Lisp
already mentioned, Le_Lisp -from ACT informatique, Paris, France-) offer a true file
compiler generating double-clickable applications (including a run-time
environment, not requiring the presence of the interpreter), although no actual
SmallTalk implementation offers such a feature.
Everywhere in the following programs, a useful spelling convention has been
used: in place of using the underscore character (_) to build self-speaking identifiers
names, an upper-case character signals the beginning of a new “word”, so moveTower
stands for move_tower. Unfortunately (?), Common Lisp does not keep the case
(unless you tell it, which might be confusing as for the functions and variables defined
by the language, one should keep using only upper-case names).
Traditional towers of Hanoï
A first example (listing 1) is the Allegro Common Lisp version of the “towers of
Hanoï” game, given in its simplest way, the moves are printed in the Listener window
(to which, by default, Allegro Common Lisp writes all the text it outputs -including
error messages- or it is summoned to print). It does not include any object-oriented
programming features at all and could be translated straightforward in any language
providing recursion (like Pascal, Logo,...). Listing 2 shows the result of “evaluating”
listing 1 and running some example.
Object-minded towers of Hanoï
The second example (listing 3) follows as closely as possible the version given in
Kaehler and Patterson’s book (chapter 4) already mentioned. Here, object-oriented
programming finally appears.
Some comments on the syntax: Allegro Common Lisp creates a new entity (either
a class or an object) with the defObject function which takes one or more arguments:
the first one is the name, the eventual next ones the parents -one or many, as multiple
inheritance is supported-, if no parent is supplied, the new object will be a descendant
of the root object, the ancestor common to every object, the Adam or Eve of the object
kind.
Other ways to define a new entity are the functions kindOf which takes one or
many arguments and oneOf to create a new instance object.
A method is created with the defObFun function whose first argument is a list of
two items (the method name and the owner class name) and the second a mandatory
method argument list -if no actual argument is required, it will be the empty list or
nil-.
In order to avoid warning messages at compile time, it is necessary to declare the
object variables (instance as well as class variables) as well as the parents classes
and every other class referred to in the methods: if one does not want to insert a
declare function at the beginning of every method body using an object variable, a
proclaim declaration is inserted before any method definition, using an
object-variable declaration specification.
Allegro Common Lisp sends messages (equivalent for “calls procedures” in
traditional programming languages) using the ask function: the object to which the
message is sent is the first argument, the message itself is enclosed, with its eventual
arguments, in a list as the second argument (several messages may be sent to the same
object, in sequence).
The exist method always requires the init-list argument, even if one does not
intend to pass it any actual argument at all.
Listing 4 shows how an instance object (aTowerOfHanoi) of the class
towerOfHanoi is created and how the game is started: every times the Allegro Common
Lisp function oneOf is executed, it creates a new object of the class given as argument
-one may pass more than one class as argument, this mechanism being defined as
“multiple inheritance”-, gives the just created object access to every function defined
for that class (or classes) and runs the function exist if it can find a function with this
name defined for that class.
In the present example, such a function exists: it only furnishes the new object
just created a variable called stacks -this variable is by no way any further defined
nor precised in exist, but will be later on by the Hanoi method-.
Then the newly created object is required to execute the Hanoi method (in
object-oriented programming terminology, method stands for function code).
The Hanoi method asks for the number of disks to be moved, sets up all the
internal stuff -among others thing, it precises completely what the object variable
stacks is- and then runs the moveTower method with the number of disks just given.
In this example, all disks are supposed to be moved from the first pin to the third
one, using the second as a temporary repository.
Note the moveDisk method has completely been freed from the implementation
details, in this case the way the disks are represented on the various pegs, this further
step towards data independence is provided by object-oriented programming and would
be much more difficult to implement with traditional languages.
The Hanoi method has been improved respect to the original SmallTalk code as it
loops on howMany until it gets an integer value, in order to avoid errors later on.
Note also, how an object variable, stacks, can be used as any other Lisp symbol
and passed as an argument to a usual function, addFirst or getAndRemoveFirst, by
noway tight to some object.
Some annoying restriction (is-it a bug or just a feature?): Allegro Common Lisp
does not like to know about a “global” function and an object method sharing the same
name. Moreover, the order in which they are defined is relevant: if the “global”
function like Hanoi or moveDisk (see listing 1) is defined first, trying to define an
object (class or instance) method having the same name as for towerOfHanoi (see
listing 3) produces a “fatal” error message (listing 5), defining first the method and
then the function just gives a “continuable” error (listing 6).
Animated towers of Hanoï
The previous example did not depart very much from conventional programming
style and did not take profit at all from Macintosh’s graphics capabilities.
The third example illustrates quite well the way object-oriented programming
allows to enrich a given program, just adding new features without having to rebuild
from scratch the whole code.
The whole game has been divided in two distinct parts: the game itself (the class
animatedTowerOfHanoi), which does not draw anything but the general frame in which
it will take part, and the actual disks animation (the class HanoiDisk).
A overall sketch of all the classes presented in this paper, and their hierarchical
relationship, is presented on table 1, where classes are named within
rounded-rectangles; the thin arrows indicate the pointed class uses the origin one and
the bold arrows show the hierarchical dependencies.
Table 1.
Before going any further, some additional tool has to be build: the class rectangle
which will allow to create and manipulate rectangles (“abstract” objects) and
rectangular images.
Listing 7 shows a possible implementation of this class: it is a straightforward
-and incomplete- translation of the equivalent Smalltalk-80 class, as Allegro Common
Lisp does not furnish any but offers some facility under the form of records “à la
Pascal”, which are intended to be used only in conjunction with ToolBox low-level
calls.
A rectangle record is defined as the traditional QuickDraw 4-tuple
(top-left-bottom-right) or as the variant pair (topLeft-bottomRight), or any valid
combination of these two (top-left-bottomRight or topLeft-bottom-right). These
various ways to define a rectangle record is the cause of the rather complicated code of
the exist method -by the way, the interested reader is invited to refine the way
conflicting coordinates are managed, giving a warning message, and no more an error
message, if there is just some redundancy in the coordinates, e.g. ‘top 10 passed along
with ‘topLeft #@(10 10)-. So, a rectangle object can be defined using one of these
four “complete” possibilities, plus two other ones: origin-corner and origin-extent,
which are converted to the usual one; the eventually missing coordinates are defaulted
to 0. Some more definition possibilities might be added, e.g. center-width-height etc
All but three methods modify only the internal representation of the concerned
rectangle. These three methods, border, erase and invertRect, cope with visible
rectangular images and take an optional argument, window, telling in which window
the drawing has to take place, by default, if no actual argument is furnished, the front
window. At the early beginning of this listing, the require functions tell Allegro
Common Lisp to assure the records and quickdraw modules are present (have been
loaded) before compiling, evaluating or loading the rest of the file.
Listing 8 represents the class animatedTowerOfHanoi, the animated version of the
game: two new functions have been added: setupDisks which draws the box where the
animation will take place, initializes the class variables, creates and draws the disks
and howMany which outputs the number of disks actually used in this animation. All
the methods defined in the parent class towerOfHanoi, but moveTower (plus the two
functions addFirst and getAndRemoveFirst) have been redefined, but exist refers to its
parent function, prefixing its name with usual (the Smalltalk counterpart is super).
setUpDisks first calls the HanoiDisk class method whichTowers in order to initialize
the variables of that class (see listing 9); these variables will be shared by every
instance object of that class and thus allow all the objects pertaining to a given class to
share common data in addition to the common methods. The use of the function self (in
setUpDisks) illustrates how an object can be lexically bound in order to be passed to
some other objects (here an animatedTowerOfHanoi is passed to the HanoiDisk class).
Unlike Smalltalk, Allegro Common Lisp does not force the programmer to define
the class and instance variables all together at the same time. Moreover, it allows some
of them to share the same name and uses the following conflict resolution strategy: it
first looks in the instance variables for such a name, if not found, it searches the class
variables for that name and finally starts the search among the ancestors of the
current class, starting from the parent(s) class(es).
Usually, the instance variables are created inside the exist function which is
executed automatically whenever a new instance is created (using the oneOf function):
this ensures that every instance will have at its disposal such a variable. It is
advisable to create class variables (using the have function) inside a function to be
called only by that class (in this example, whichTowers ).
A syntactic remark: in addition to the usual “end-of-line” comment (beginning
with a semi-colon and terminated by a carriage return, i.e. the end of line), Allegro
Common Lisp allows to enclose comments anywhere inside the code surrounding them
by #| and |#, which is very useful for debugging purposes or when some comment
takes many line.
In this case, the format function call printing out the disk movements has been
commented in order to keep the drawing readable: one may draw in the Listener
window, in which Allegro Common Lisp communicates with the programmer, but
drawing is not scrollable, so the produced output interferes very quickly with the
drawing and it is advisable to keep written output as low as possible (a further
example will alleviate this restriction, which has been kept in order to follow as
strictly as possible the original SmallTalk coding).
HanoiDisk class code is given at listing 9. Have a look to the class method
whichTowers which initializes the class variables shared among all the class instances
(the HanoiDisks): it just looks like any other, only the way it is used determines it is a
class method.
Listing 10 features animatedTowerOfHanoi+ which just slightly modifies the
animatedTowerOfHanoi class, of which it is a direct descendant, in order to avoid the
trouble just mentioned: it creates a window in which all the graphics will take place,
so the text output will not interfere any more (but in speed) with the graphics.
Just two methods have been redefined, in addition to exist, which just calls its
parent method with usual-exist, Hanoi (in order to create the window in which all the
drawings will take place) and setupDisks.(in order to use HanoiDisk+s in place of
HanoiDisks). The text output produced by an animatedTowerOfHanoi+ game is the same
produced by an instance of towerOfHanoi (see listing 4).
Listing 11 shows the only modification brought to HanoiDisk+ respect to its
direct ancestor HanoiDisk from whom it keeps every method but widthPole which
treats the graphic positioning of the disks in the window, in addition to exist just
calling usual-exist.
Figures 1 to 3 give some snapshots of the animated game, just after the
setUpDisks, during the game and when all disks have been moved (the heuristic
version produces similar graphical effects, but eventually for the position of the
destination pole which is 2 or 3 depending on the number of disks moved).
Figure 1.
Figure 2.
Figure 3.
Heuristic towers of Hanoï
Kaehler and Patterson propose an heuristic version of this game; although it is
less readable than the recursive counterpart, it offers an interesting feature, from a
syntactical point of view: the passing of block of code as argument to a function. The
blocks, a Smalltalk peculiar syntactic construction, represent a deferred sequence of
actions, to which one (or several) argument(s) may be passed, and return a value
which is an object that can execute these actions when summoned to do so (from Adele
Goldberg and David Robson: Smalltalk-80, the language and its implementation,
Addison-Wesley).
Listings 12 (class TowerByRules) and 13 (class HanoiDiskRules) show how
blocks have been translated in Allegro Common Lisp, using a lambda expression (sort
of unnamed function): see towerByRules decide method which calls towerByRules
topsOtherThan and HanoiDiskRules hasLegalMove calling towerByRules
polesOtherThan. In addition, note how topsOtherThan and polesOtherThan use the
“block” passed as argument calling funCall. Once more, one takes profit of the
inheritance mechanism and uses the previously defined method, using the usual-
prefix: e.g. in towerByRules’ exist, HanoiDiskRules’ exist, widthPole and moveUpon.
If one wants to eliminate the defect already mentioned for animatedTowerOfHanoi
and HanoiDisk classes and affecting also the just defined classes (i. e. the practical
impossibility to write and draw in the same window) and use the whole screen, one can
just define a new towerByRules++ class (see listing 14), direct descendant of
towerByRules, redefining just the two procedures setting up the scene, Hanoi and
setUpDisks. At the contrary, the situation is not as easy with the new HanoiDiskRules+
class for which all the methods are to be redefined, just by the fact that the direct
ancestor is no more HanoiDisk but well HanoiDisk+ (see listing 15). This situation,
quite harmless in this example, might be very annoying in real life situations for it
might oblige to duplicate whole pieces of code with very little modifications.
Multiple inheritance
Fortunately, Allegro Common Lisp provides the multiple inheritance facility: the
new HanoiDiskRules+ class is created as the descendant of HanoiDiskRules and
HanoiDisk+, in that order (see listing 16). Now, only the exist method has to be
redefined in the new class by calling only its super-method usual-exist.
This mechanism works due to the fact that when looking for a method, the parents
list is browsed before going to look into the grand-parents methods, in expert- systems
terminology, this is called breadth-first search.
In the present case, when widthPole is summoned, the method found in
HanoiDiskRules is fired first, calling usual-widthPole which is looked for and found in
the second parent (HanoiDisk+) methods in place of looking for such a method in
HanoiDiskRules parent (i. e. HanoiDisk). This sequence of actions explains why the
order in which the parents are listed is of uttermost importance: should the parent
classes have been permuted, widthPole would have been found in HanoiDisk+ class and
the instance variable previousPole would have been left undefined. The length of the
resulting code is considerably shorter (3 lines of code in place of 36, roughly a
10-fold factor), reducing the risk of errors.
The multiple inheritance facility exists also in Smalltalk-80, but is not used at
all in the whole system which limits itself to pure inheritance (every class has only
one direct parent) and is scarcely documented: almost nothing in the blue book
(Smalltalk-80, the language and its implementation by Adele Goldberg and David
Robson, Addison-Wesley: the Smalltalk-80 bible, as unreadable as Inside Macintosh or
almost), a few hints in “a little Smalltalk” by Timothy Budd (Addison-Wesley editor)
and some more in “Smalltalk-80” by Alain Mével and Thierry Guéguen (Eyrolles
editor, in french!).
Bugs and features
Even if Allegro Common Lisp is among the best Macintosh programming languages
-and the only valid Common Lisp implementation until very recently-, some bugs (or
features) are present, most of which being really of minor importance (but the very
poor foreign keyboards support).
The function machine-instance output returns “machine-instance unspecified”,
although software-type returns the hardware used (e. g. Macintosh Plus).
The calls (room) and (room nil) produce the same result although the standard
states the first one should output an intermediate amount of information between the
minimum produced by (room nil) and the maximum given by (room t).
An erroneous call to append, like (append ‘(a . b) ‘d), outputs an irrelevant
error message (in that case: B is not a valid argument to CAR).
The support of foreign keyboards is particularly weak and depends heavily on the
system version and the “nationality” of the keyboard used: e.g. the keyboard
equivalents, command-. and command-/ respectively, of the abort and continue
commands are unusable on those keyboards where the dot and slash characters figure at
the upper-case keyboard floor; many commands of the FRED editor (Fred Resembles
Emacs Deliberately ) are unusable as they heavily use control-keys (clover key),
command-keys (shift-clover key) as well as meta-keys (option-key) -some kind of
extension of the command key, largely found on Lisp machines, such as Symbolics, TI’s
Explorers, .-, e.g. meta-” becomes meta-a on a french keyboard or meta-% on an
Italian one.
step does not allow nor to read nor to use object-oriented programming. Most of
the *nx-* variables are in fact compiled functions and I did not succeed to use them
at all; moreover, they are not on-line documented.
When opening a source file brought from a large screen Mac to another one with a
reduced screen (e.g. MacPlus), the edition window may be well beyond the actual
screen limits. To recuperate it, without having to go back to the source Mac, one has
two solutions: either, with the aid of the function windows, one gets the object number
of the window beyond the screen limits and then passing two messages, one changes the
offending coordinates, either, one goes back to the listener (at that time, the offending
window becomes the second one in the front to back order) and one gives a pair of
messages to the hidden window (see listing 17).
Interested readers can write me: Jean-Pascal J. Lange, BP 120, CH-6988
Ponte-Tresa, Switzerland or by fax: int+39-332/78 90 98 (not forgetting to put my
name on the front page, please).
Listing 1
; towers of Hanoï game
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: (Hanoi #disks tower1 tower2 tower3)
e.g.: (Hanoi 3 “A” “B” “C”)
|#
(deFun Hanoi (nDisks fromPin toPin usingPin)
(cond ((> nDisks 0)
(Hanoi (1- nDisks) fromPin usingPin toPin)
(moveDisk fromPin toPin)
(Hanoi (1- nDisks) usingPin toPin fromPin) ) ))
(deFun moveDisk (fromPin toPin)
(format t “~&~D -> ~D” fromPin toPin) )
Listing 2
Welcome to Allegro CL Version 1.2!
?
HANOI
MOVEDISK
? (Hanoi 3 “A” “B” “C”)
A -> B
A -> C
B -> C
A -> B
C -> A
C -> B
A -> B
NIL
?
Listing 3
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 4, pp. 44 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: (setf aTower (oneOf towerOfHanoi))
(ask aTower (Hanoi))
|#
(defObject towerOfHanoi)
(proclaim ‘(object-variable stacks) )
(defObFun (exist towerOfHanoi) (init-list)
(declare (ignore init-list))
(usual-exist)
(have ‘stacks) )
(defObFun (Hanoi towerOfHanoi) ()
(let ((height nil))
(do ()
((integerP height))
(format t “~&Please type the number of disks in the tower: “)
(setq height (read)) )
(format t “~&tower of Hanoï for ~D disk~:P.” height)
(setq stacks (make-array 3 :initial-element nil) )
(do ((each height (1- each)))
((zerop each))
(addFirst stacks 0 (code-char (+ (char-code #\A) (1- each)))) )
(moveTower height 1 3 2) ) )
(defObFun (moveDisk towerOfHanoi) (fromPin toPin)
(let ((disk (getAndRemoveFirst stacks (1- fromPin))))
(addFirst stacks (1- toPin) disk)
(format t “~&~D -> ~D ~A” fromPin toPin disk) ) )
(defObFun (moveTower towerOfHanoi) (nDisks fromPin toPin usingPin)
(cond ((> nDisks 0)
(moveTower (1- nDisks) fromPin usingPin toPin)
(moveDisk fromPin toPin)
(moveTower (1- nDisks) usingPin toPin fromPin) ) ) )
(deFun addFirst (array index item)
(setf (aref array index)
(cons item (aref array index)) ) )
(deFun getAndRemoveFirst (array index)
(let ((first (car (aref array index))))
(setf (aref array index)
(cdr (aref array index)) )
first ) )
Listing 4
Welcome to Allegro CL Version 1.2!
?
TOWEROFHANOI
NIL
EXIST
HANOI
MOVEDISK
MOVETOWER
ADDFIRST
GETANDREMOVEFIRST
? (setf toh (oneOf towerOfHanoi))
#
? (ask toh (Hanoi))
Please type the number of disks in the tower: 3
tower of Hanoï for 3 disks.
1 -> 3 A
1 -> 2 B
3 -> 2 A
1 -> 3 C
2 -> 1 A
2 -> 3 B
1 -> 3 A
NIL
?
Listing 5
Welcome to Allegro CL Version 1.2!
?
HANOI
MOVEDISK
?
TOWEROFHANOI
NIL
EXIST
> Error: Cannot object-bind global function HANOI
> While executing: FHAVE
> Type Command-/ to continue, Command-. to abort.
1 > Continuing...
? (setf toh (oneOf towerOfHanoi))
#
? (ask toh (Hanoi))
> Error: TOH is not a valid argument to NIL .
> While executing: HANOI
> Type Command-/ to continue, Command-. to abort.
1 >
Aborted
?
Listing 6
Welcome to Allegro CL Version 1.2!
?
TOWEROFHANOI
NIL
EXIST
HANOI
MOVEDISK
MOVETOWER
ADDFIRST
GETANDREMOVEFIRST
?
> Continuable Error: Attempt to globally define object function HANOI
> While executing: FSET-GLOBALLY
> If Continued: Bind HANOI in the root object
> Type Command-/ to continue, Command-. to abort.
1 > Continuing...
HANOI
> Continuable Error: Attempt to globally define object function
MOVEDISK
> While executing: FSET-GLOBALLY
> If Continued: Bind MOVEDISK in the root object
> Type Command-/ to continue, Command-. to abort.
1 > Continuing...
MOVEDISK
? (Hanoi 3 “A” “B” “C”)
A -> B
A -> C
B -> C
A -> B
C -> A
C -> B
A -> B
NIL
?
Listing 7
; rectangle class
; from Smalltalk-80, the language and its implementation.
; Adele Goldberg and David Robson. Addison-Wesley, pp. 344-349
; implemented in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(eval-when
(compile eval load)
(require ‘quickDraw)
(require ‘ records) )
(proclaim ‘(object-variable wptr)) ; from * window* class
(defObject rectangle)
(proclaim ‘(object-variable rect) )
(defObFun (exist rectangle) (init-list)
(usual-exist)
(have ‘rect (make-record :rect))
(if init-list
(let ((top (getf init-list ‘top))
(left (getf init-list ‘left))
(topLeft (getf init-list ‘topLeft))
(bottom (getf init-list ‘bottom))
(right (getf init-list ‘right))
(bottomRight (getf init-list ‘bottomRight)) )
(if topLeft
(cond (top
(error “Conflicting coordinates: ~
top (~A) and topLeft (~A)”
top (point-string topLeft) ) )
(left
(error “Conflicting coordinates: ~
left (~A) and topLeft (~A)”
left (point-string topLeft) ) )
(t (rSet rect rect.topLeft topLeft)) )
(progn
(if top (rSet rect rect.top top))
(if left (rSet rect rect.left left)) ) )
(if bottomRight
(cond (bottom
(error “Conflicting coordinates: ~
bottom (~A) and bottomRight (~A)”
bottom (point-string bottomRight) ) )
(right
(error “Conflicting coordinates: ~
right (~A) and bottomRight (~A)”
right (point-string bottomRight) ) )
(t (rSet rect rect.bottomRight bottomRight)) )
(progn
(if bottom (rSet rect rect.bottom bottom))
(if right (rSet rect rect.right right)) ) ) ) ) )
(defObFun (leftRightTopBottom rectangle) (left right top bottom)
(oneOf rectangle
‘top top ‘left left ‘bottom bottom ‘right right ) )
(defObFun (originCorner rectangle) (origin corner)
(oneOf rectangle ‘topLeft origin ‘bottomRight corner) )
(defObFun (originExtent rectangle) (origin extent)
(oneOf rectangle ‘topLeft origin
‘bottomRight (add-points origin extent) ) )
(defObFun (origin rectangle) ()
(rRef rect rect.topLeft) )
(defObFun (corner rectangle) ()
(rRef rect rect.bottomRight) )
(defObFun (center rectangle) ()
(let ((origin (origin)))
(add-points origin (/ (subtract-points (corner) origin) 2)) ) )
(defObFun (extent rectangle) ()
(subtract-points (corner) (origin)) )
(defObFun (setOrigin rectangle) (origin)
(rSet rect rect.topLeft origin) )
(defObFun (setCorner rectangle) (corner)
(rSet rect rect.bottomRight corner) )
(defObFun (setCenter rectangle) (aPoint)
; move the rectangle so it is centered on the point,
; but keep the width and height unchanged
(let ((extent (extent)))
(setOrigin (add-points (origin)
(subtract-points aPoint (center)) ))
(setCorner (add-points (origin) extent)) ) )
(defObFun (border rectangle)
(width &optional (window (front- window)))
(let* ((rect rect)
(oldPenState (ask window (pen-state))) )
(with-port (ask window wptr)
(ask window (pen-normal)
(set-pen-size (make-point width width))
(frame-rect rect)
(set-pen-state oldPenState) ) )
(dispose-record oldPenState) ) )
(defObFun (erase rectangle) (&optional(window (front- window)))
(let ((rectangle rect))
(ask window (erase-rect rectangle)) ) )
(defObFun (invertRect rectangle)
(&optional (window (front- window)))
(let ((rectangle rect))
(ask window (invert-rect rectangle)) ) )
Listing 8
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 5, pp. 65 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: after loaded towerOfHanoi, rectangle and HanoiDisk
classes, load this file, then
(setf aTower (oneOf animatedTowerOfHanoi))
(ask aTower (Hanoi))
|#
(proclaim ‘(object-variable towerOfHanoi) ) ; towerOfHanoi class
(defObject animatedTowerOfHanoi towerOfHanoi)
(proclaim ‘(object-variable HanoiDisk) ) ; HanoiDisk class
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable stacks) ) ; towerOfHanoi object variable
(proclaim ‘(object-variable howMany mockDisks) ) ; new object
variables
(defObFun (exist animatedTowerOfHanoi) (init-list)
(declare (ignore init-list))
(usual-exist nil)
#| An object of this class represents the game. It inherits
variable stacks from class TowerOfHanoi.
The new instance variables are:
howMany: the number of disks,
mockDisks: an array of fake disks (when a disk asks what disk it
can move on top of, and the pole is empty, we return
a mock disk; it has nearly infinite width). |#
(have ‘howMany)
(have ‘mockDisks) )
; the game
(defObFun (Hanoi animatedTowerOfHanoi) ()
; asks user how many disks, set up game and move disks until
; we are done
(do ()
((integerp howMany))
(format t “~&Please type the number of disks in the tower: “)
(setq howMany (read)) )

(setUpDisks) ; create the disks and stacks
(moveTower (howMany) 1 3 2)
; so on next run, howMany will be re-initialized
(setq howMany nil) )
(defObFun (setUpDisks animatedTowerOfHanoi) ()
; Creates the disks and set up the poles.
; Tells all disks what game they are in and set disk thickness and
gap.
(let ((self (self)))
(ask HanoiDisk ( whichTowers self)) )
(let ((displayBox
(ask rectangle (originCorner #@(20 80) #@(380 300))) ))
(ask displayBox (erase))
(ask displayBox (border 2)) )
; poles are an array of three stacks. Each stack is a list.
(setq stacks (make-array 3 :initial-element nil))
(let ((disk)
(size (howMany)) )
(doTimes (i (howMany))
(setq disk (oneOf HanoiDisk)) ; create a disk
(ask disk (widthPole size 1))
; don’t forget: first element of array is at index 0 !!!
(addFirst stacks 0 disk) ; push it onto a stack
(ask disk (invert)) ; show on the screen
(setq size (1- size)) ) )

; When pole has no disk, one of these mock disks acts as a
; bottom disk. A moving disk will ask a mock disk its width and
pole number.
(setq mockDisks (make-array 3 :initial-element nil))
(let ((disk))
(doTimes (index 3)
(setq disk (oneOf HanoiDisk))
; don’t forget: a doTimes-loop index starts at 0 !!!
(ask disk (widthPole 1000 (1+ index)))
; don’t forget: first element array is at index 0 !!!
(setf (aRef mockDisks index) disk) ) ) )
(defObFun (moveDisk animatedTowerOfHanoi) (fromPin toPin)
; move disk from a pin to another pin.
; Print the results in the listener window.

; don’t forget: the first element array is at index 0 !!!
(let ((supportDisk (if (aRef stacks (1- toPin))
(car (aRef stacks (1- toPin)))
(aRef mockDisks (1- toPin)) ))
(disk (getAndRemoveFirst stacks (1- fromPin))) )
(addFirst stacks (1- toPin) disk)
; inform the disk and show move
(ask disk (moveUpon supportDisk))
#|(format t “~&~D -> ~D: ~A” fromPin toPin (ask disk (name)))|# )
(sleep 0.3) )
(defObFun (howMany animatedTowerOfHanoi) ()
; returns the number of disks
howMany )
Listing 9
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 5, pp. 65 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(defObject HanoiDisk)
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable name width pole diskRectangle
theTowers thickness diskGap ) )
(defObFun (exist HanoiDisk) (init-list)
(declare (ignore init-list))
(usual-exist)
; disk in game is represented by object of class HanoiDisk.
; It has
; name: name of this disk (a character),
; width: size of the disk (1 is the smallest disk width),
; pole: number telling which pole the disk is on,
; diskRectangle: rectangle on screen that disk occupies.
(have ‘name)
(have ‘width)
(have ‘pole)
(have ‘diskRectangle (oneOf rectangle)) )
; access
(defObFun (pole HanoiDisk) () ; return pole this disk is on
pole )
(defObFun (name HanoiDisk) () ; return name of this disk
name )
(defObFun ( whichTowers HanoiDisk) (aTowerOfHanoi)
; There are three variables shared across the whole class:
; TheTowers: the object that represents whole game and
; holds the stacks of disks,
; Thickness: the thickness of a disk in screen dots,
; DiskGap: number of screen dots between disks in a stack.
; install the object representing the towers
(have ‘theTowers aTowerOfHanoi)
(have ‘thickness 14) ; thickness of a disk in screen dots
(have ‘diskGap 2) ) ; distance between disks
(defObFun (widthPole HanoiDisk) (size whichPole)
; set the values for this disk
(setq width size)
(setq pole whichPole)
; compute the center of the disk on the screen
(let ((where))
(cond ((not (>= size 1000))
(setq name ; a normal disk
(code-char (+ (char-code #\A) (1- size))))
(let ((y (- 289 (* (- (ask theTowers (howMany)) size)
(+ thickness diskGap) ))))
(setq where (make-point 100 y)) ) )
(t (setq name ‘m) ; a mock disk
(setq where (make-point (* 100 whichPole)
(+ 289 thickness diskGap) ) ) ) )
; create rectangle, specify its size and locate its center
(let ((extent (make-point (* size 14) thickness)))
(setq diskRectangle
(ask rectangle (originExtent #@(0 0) extent)) ) )
; locate the rectangle center
(ask diskRectangle (setCenter where)) ) )
(defObFun (center HanoiDisk) ()
; returns a point that is the current center of this disk
(ask diskRectangle (center)) )
(defObFun (moveUpon HanoiDisk) ( destination)
; this disk just moved. Record the new pole and tell user.
(setq pole (ask destination (pole)))
; remove the old image
(invert)
; reposition
(let ((point (make-point 0 (+ thickness diskGap))))
(ask diskRectangle
(setCenter (subtract-points (ask destination (center))
point )) ) )
; display the new one
(invert) )
(defObFun (invert HanoiDisk) ()
; shows a disk on the screen by turning white to black
; in a rectangular region
(ask diskRectangle (invertRect)) )
Listing 10
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 5, pp. 65 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: after having loaded the towerOfHanoi, rectangle, HanoiDisk,
HanoiDisk+ and animatedTowerOfHanoi classes, load this file,
then (setf aTower (oneOf animatedTowerOfHanoi+))
(ask aTower (Hanoi))
|#
; animatedTowerOfHanoi class
(proclaim ‘(object-variable animatedTowerOfHanoi) )
(defObject animatedTowerOfHanoi+ animatedTowerOfHanoi)
(proclaim ‘(object-variable HanoiDisk+) ) ; HanoiDisk+ class
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable stacks) ) ;defined in towerOfHanoi
(proclaim ‘(object-variable howMany mockDisks) )
(defObFun (exist animatedTowerOfHanoi+) (init-list)
(usual-exist init-list) )
; the game
(defObFun (Hanoi animatedTowerOfHanoi+) ()
; asks the user how many disks, set up the game
; and move disks until we are done
(do ()
((integerp howMany))
(format t “~&Please type the number of disks in the tower: “)
(setq howMany (read)) )
(oneOf * window*
: window-title “animated towers of Hanoï”
: window-position #@(20 100)
: window-size #@(360 220)
: window-type :single-edge-box )
(setUpDisks) ; create the disks and stacks
(moveTower (howMany) 1 3 2)
(setq howMany nil) )
(defObFun (setUpDisks animatedTowerOfHanoi+) ()
; Creates the disks and set up the poles.
; Tells all disks what game they are in and set disk thickness and
gap.
(let ((self (self)))
(ask HanoiDisk+ ( whichTowers self)) )
(let ((displayBox
(ask rectangle
(originCorner #@(0 0)
(ask (front- window) ( window-size)) ) ) ))
(ask displayBox (erase))
(ask displayBox (border 2)) )
; poles are an array of three stacks. Each stack is a list.
(setq stacks (make-array 3 :initial-element nil))
(let ((disk)
(size (howMany)) )
(doTimes (i (howMany))
(setq disk (oneOf HanoiDisk+)) ; create a disk
(ask disk (widthPole size 1))
; don’t forget: first element of array is at index 0 !!!
(addFirst stacks 0 disk) ; push it onto a stack
(ask disk (invert)) ; show on the screen
(setq size (1- size)) ) )

; When a pole has no disk, one of these mock disks acts as a
; bottom disk. A moving disk will ask a mock disk its width and
pole number.
(setq mockDisks (make-array 3 :initial-element nil))
(let ((disk))
(doTimes (index 3)
(setq disk (oneOf HanoiDisk+))
; don’t forget: a doTimes-loop index starts at 0 !!!
(ask disk (widthPole 1000 (1+ index)))
; don’t forget: first element of array is at index 0 !!!
(setf (aRef mockDisks index) disk) ) ) )
Listing 11:
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 5, pp. 65 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(proclaim ‘(object-variable HanoiDisk) ) ; HanoiDisk class
(defObject HanoiDisk+ HanoiDisk)
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable name width pole diskRectangle
theTowers thickness diskGap ) )
(defObFun (exist HanoiDisk+) (init-list)
(usual-exist init-list) )
; access
(defObFun (widthPole HanoiDisk+) (size whichPole)
; set the values for this disk
(setq width size)
(setq pole whichPole)
; compute the center of the disk on the screen
(let* ((where)
( window-size (ask (front- window) ( window-size)))
( window-height (point-v window-size))
( window-width (point-h window-size))
(x0 (floor window-width 6))
(y0 (- window-height 11))
(h-distance (floor window-width 3)) )
(cond ((not (>= size 1000))
(setq name ; a normal disk
(code-char (+ (char-code #\A) (1- size))))
(let ((y (- y0 (* (- (ask theTowers (howMany)) size)
(+ thickness diskGap) ))))
(setq where (make-point x0 y)) ) )
(t (setq name ‘m) ; a mock disk
(setq where (make-point (- (* h-distance whichPole) x0)
(+ y0 thickness diskGap) ) ) ) )
; create rectangle, specify its size and locate its center
(let ((extent (make-point (* size 14) thickness)))
(setq diskRectangle
(ask rectangle (originExtent #@(0 0) extent)) ) )
; locate the rectangle center
(ask diskRectangle (setCenter where)) ) )
Listing 12:
; Ted Kaehler and Dave Patterson: a taste of SmallTalk
; W. W. Norton ed., chapter 6, pp. 83 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: after having loaded the towerOfHanoi, rectangle, HanoiDisk,
animatedTowerOfHanoi and HanoiDiskRules classes,
load this file, then (setf aTower (oneOf towerByRules))
(ask aTower (Hanoi))
|#
; animatedTowerOfHanoi class
(proclaim ‘(object-variable animatedTowerOfHanoi) )
(defObject towerByRules animatedTowerOfHanoi)
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable HanoiDiskRules) ) ; HanoiDiskRules class
(proclaim ‘(object-variable stacks) ) ; defined in towerOfHanoi
; defined in animatedTowerOfHanoi
(proclaim ‘(object-variable howMany mockDisks) )
(proclaim ‘(object-variable pole) ) ; defined in HanoiDiskRules
(proclaim ‘(object-variable oldDisk currentDisk destinationDisk) )
(defObFun (exist towerByRules) (init-list)
#| An object of this class represents the game. It holds an array of
stacks that hold disks. It also keeps track of which disk just moved
and which disk should move next.
The new instance variables are
oldDisk the disk that was moved last time,
currentDisk we are considering moving this disk,
destinationDisk and putting it on top of this disk.|#
(have ‘oldDisk)
(have ‘ currentDisk)
(have ‘ destinationDisk)
; to get the instance variables stacks from class TowerOfHanoi and
; howMany and mockDisks from class AnimatedTowerOfHanoi
(usual-exist init-list) ) ; exist
; initialize
(defObFun (Hanoi towerByRules) ()
; asks the user how many disks, set up the game and move disks
until
; we are done
(do ()
((integerp howMany))
(format t “~&Please type the number of disks in the tower: “)
(setq howMany (read)) )
(setUpDisks) ; create the disks and stacks

(loop ; iterate until all disks are on one tower again.
(let* (( currentDisk (decide))
; decide which to move and also set destinationDisk
( currentPole (ask currentDisk (pole)))
( destinationPole (ask destinationDisk (pole))) )
(removeFirst stacks (1- currentPole))
(addFirst stacks (1- destinationPole) currentDisk)
; tell the disk where it is now
(let (( destinationDisk destinationDisk))
(ask currentDisk (moveUpon destinationDisk)))
(setq oldDisk currentDisk) ) ; get ready for next move
(when (allOnOneTower) (return)) ) ; test if done
; so on next run, howMany will be re-initialized
(setq howMany nil) ) ; Hanoi
(defObFun (setUpDisks towerByRules) ()
; Creates the disks and set up the poles.
; Tells all disks what game they are in and set disk thickness and
gap.
(let ((self (self)))
(ask HanoiDiskRules ( whichTowers self)) )
(let ((displayBox
(ask rectangle (originCorner #@(20 80) #@(380 300))) ))
(ask displayBox (erase))
(ask displayBox (border 2)) )
; poles are an array of three stacks. Each stack is a list.
(setq stacks (make-array 3 :initial-element nil))
(let ((disk)
(size (howMany)) )
(doTimes (i (howMany))
(setq disk (oneOf HanoiDiskRules)) ; create a disk
(ask disk (widthPole size 1))
; don’t forget: first element of array is at index 0 !!!
(addFirst stacks 0 disk) ; push it onto a stack
(ask disk (invert)) ; show on the screen
(setq size (1- size)) ) )

; When pole has no disk, one of these mock disks acts as a
; bottom disk. A moving disk will ask a mock disk its width and
pole number.
(setq mockDisks (make-array 3 :initial-element nil))
(let ((disk))
(doTimes (index 3)
(setq disk (oneOf HanoiDiskRules))
; don’t forget: a doTimes-loop index starts at 0 !!!
(ask disk (widthPole 1000 (1+ index)))
(setf (aRef mockDisks index) disk) ) )
; on first move, look for another disk (a real one) to move
; don’t forget: the first element of array is at index 0 !!!
(setq oldDisk (aRef mockDisks 2)) ) ; setUpDisks
; moves
(defObFun (allOnOneTower towerByRules) ()
; return true if all of the disks are on one tower
(doTimes (index (length stacks) nil)
(if (= (length (aRef stacks index))
(howMany) )
(return t) ) ) ) ; allOnOneTower
(defObFun (decide towerByRules) ()
; use last disk moved (oldDisk) to find a new disk to move
; ( currentDisk) and disk to put it top of ( destinationDisk).
(topsOtherThan
oldDisk
#’(lambda (movingDisk)
(cond ((ask movingDisk (hasLegalMove))
; remember the disk upon which to move
(setq destinationDisk (ask movingDisk (bestMove)))
; return the disk that moves
movingDisk )) ) ) ) ; decide
(defObFun (polesOtherThan towerByRules) (thisDisk aBlock)
; evaluate block of code using the top disk on each of other
; two poles. If pole is empty, use mock disk for that pole.
(doTimes (aPole 3)
; Want a pole other than the pole of thisDisk
; don’t forget: a doTimes-loop index starts at 0 !!!
(if (not (= (1+ aPole) (ask thisDisk (pole))))
(let
((result
(if (null (aRef stacks aPole))
; if the pole is empty, use a mock disk
(funCall aBlock (aRef mockDisks aPole)) ; execute the
block
; else use the top disk
(funCall aBlock ; execute the block
(first (aRef stacks aPole)) ) )) )
(when result (return result)) ) ) ) ) ; polesOtherThan
(defObFun (topsOtherThan towerByRules) (thisDisk aBlock)
; evaluate the block of code using the top disk on each of the
other
; two poles. If a pole is empty, ignore it. This is for actual
disks.
(doTimes (aPole 3)
; If pole does not have thisDisk and is not empty, then
; execute aBlock (don’t forget: a doTimes-loop index starts at 0)
(if (and (not (= (1+ aPole) (ask thisDisk (pole))))
(not (null (aRef stacks aPole))) )
(let ((result (funcall aBlock ; execute the block
(first (aRef stacks aPole)) )))
(when result (return result)) ) ) ) ) ; topsOtherThan
(deFun removeFirst (array index)
; removeFirst is the procedure for pop.
(setf (aRef array index) (cdr (aRef array index))) ) ; removeFirst
Listing 13:
; Ted Kaehler and Dave Patterson a taste of SmallTalk
; W. W. Norton ed., chapter 6, pp. 83 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(proclaim ‘(object-variable HanoiDisk) ) ; HanoiDisk class
(defObject HanoiDiskRules HanoiDisk)
(proclaim ‘(object-variable width theTowers) ) ; from HanoiDisk
(proclaim ‘(object-variable previousPole) )
(defObFun (exist HanoiDiskRules) (init-list)
; previousPole number of pole this disk was on previously.
(have ‘previousPole)
; to get instance variables name, width, pole and rectangle
; from class HanoiDisk
(usual-exist init-list) ) ; exist
; access
(defObFun (width HanoiDiskRules) ()
; return the size of this disk
width ) ; width
(defObFun (widthPole HanoiDiskRules) (size whichPole)
; invoke widthPole in the superclass
(usual-widthPole size whichPole)
(setq previousPole 1) ) ; widthPole
; moving
(defObFun (bestMove HanoiDiskRules) ()
; If self can move two places, which is best? Return the top
; disk of the pole that this disk has not been on recently.
(let ((self (self))
(secondBest) )
(cond ((ask TheTowers
(polesOtherThan
self
#’(lambda ( targetDisk)
(cond ((< (ask self (width))
(ask targetDisk (width)) )
(setq secondBest targetDisk)
(if (not (= (ask targetDisk (pole))
(ask self previousPole) ))
targetDisk ) )) ) ) ))
; as a last resort, return a pole it was on recently
(t secondBest ) ) ) ) ; bestMove
(defObFun (hasLegalMove HanoiDiskRules) ()
; do either of other two poles have a top disk large enough
; for this disk to rest on?
(let ((self (self)))
(ask TheTowers
(polesOtherThan
self
; when a pole has no disk,
; targetDisk is a mock disk with infinite width
#’(lambda ( targetDisk)
(< (ask self (width))
(ask targetDisk (width)) ) ) ) ) ) ) ; hasLegalMove
(defObFun (moveUpon HanoiDiskRules) ( destination)
; this disk just moved. Record the new pole and tell user.
(setq previousPole (pole))
; run the version of moveUpon defined in class HanoiDisk
(usual-moveUpon destination) ) ; moveUpon
Listing 14:
; Ted Kaehler and Dave Patterson a taste of SmallTalk
; W. W. Norton ed., chapter 6, pp. 83 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
#|
use: after having loaded the towerOfHanoi, rectangle, HanoiDisk,
animatedTowerOfHanoi, HanoiDiskRules and HanoiDiskRules+ classes,
load this file, then (setf aTower (oneOf towerByRules++))
(ask aTower (Hanoi))
|#
(proclaim ‘(object-variable towerByRules) ) ; towerByRules class
(defObject towerByRules++ towerByRules)
(proclaim ‘(object-variable rectangle) ) ; rectangle class
(proclaim ‘(object-variable HanoiDiskRules+) ) ; HanoiDiskRules+
class
(proclaim ‘(object-variable stacks) ) ; defined in towerOfHanoi
; defined in animatedTowerOfHanoi
(proclaim ‘(object-variable howMany mockDisks) )
(proclaim ‘(object-variable pole) ) ; defined in HanoiDiskRules+
(proclaim ‘(object-variable oldDisk currentDisk destinationDisk) )
(defObFun (exist towerByRules++) (init-list)
(usual-exist init-list) ) ; exist
; initialize
(defObFun (Hanoi towerByRules++) ()
; asks user how many disks, set up game and move disks until
; we are done
(do ()
((integerp howMany))
(format t “~&Please type the number of disks in the tower: “)
(setq howMany (read)) )
(oneOf * window*
: window-title “animated towers of Hanoï”
: window-position #@(0 0)
: window-size (make-point *screen-width* *screen-height*)
: window-type :single-edge-box )
(setUpDisks) ; create the disks and stacks

(loop ; iterate until all disks are on one tower again.
(let* (( currentDisk (decide))
; decide which to move and also set destinationDisk
( currentPole (ask currentDisk (pole)))
( destinationPole (ask destinationDisk (pole))) )
(removeFirst stacks (1- currentPole))
(addFirst stacks (1- destinationPole) currentDisk)
; tell the disk where it is now
(let (( destinationDisk destinationDisk))
(ask currentDisk (moveUpon destinationDisk)))
(setq oldDisk currentDisk) ) ; get ready for next move
(when (allOnOneTower) (return)) ) ; test if done
(setq howMany nil) ) ; Hanoi
(defObFun (setUpDisks towerByRules++) ()
; Creates the disks and set up the poles.
; Tells all disks what game they are in and set disk thickness and
gap.
(let ((self (self)))
(ask HanoiDiskRules+ ( whichTowers self)) )
; poles are an array of three stacks. Each stack is a list.
(setq stacks (make-array 3 :initial-element nil))
(let ((disk)
(size (howMany)) )
(doTimes (i (howMany))
(setq disk (oneOf HanoiDiskRules+)) ; create a disk
(ask disk (widthPole size 1))
; don’t forget: first element of array is at index 0 !!!
(addFirst stacks 0 disk) ; push it onto a stack
(ask disk (invert)) ; show on the screen
(setq size (1- size)) ) )

; When pole has no disk, one of these mock disks acts as a
; bottom disk. A moving disk will ask a mock disk its width and
pole number.
(setq mockDisks (make-array 3 :initial-element nil))
(let ((disk))
(doTimes (index 3)
(setq disk (oneOf HanoiDiskRules+))
; don’t forget: a doTimes-loop index starts at 0 !!!
(ask disk (widthPole 1000 (1+ index)))
(setf (aRef mockDisks index) disk) ) )
; on first move, look for another disk (a real one) to move
; don’t forget: first element of an array is at index 0 !!!
(setq oldDisk (aRef mockDisks 2)) ) ; setUpDisks
Listing 15:
; Ted Kaehler and Dave Patterson a taste of SmallTalk
; W. W. Norton ed., chapter 6, pp. 83 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(proclaim ‘(object-variable HanoiDisk+) ) ; HanoiDisk+ class
(defObject HanoiDiskRules+ HanoiDisk+)
(proclaim ‘(object-variable width theTowers) ) ; from HanoiDisk
(proclaim ‘(object-variable previousPole) )
(defObFun (exist HanoiDiskRules+) (init-list)
; previousPole number of the pole this disk was on previously.
(have ‘previousPole)
; to get instance variables name, width, pole and rectangle
; from class HanoiDisk
(usual-exist init-list) ) ; exist
; access
(defObFun (width HanoiDiskRules+) ()
; return the size of this disk
width ) ; width
(defObFun (widthPole HanoiDiskRules+) (size whichPole)
; invoke widthPole in the superclass
(usual-widthPole size whichPole)
(setq previousPole 1) ) ; widthPole
; moving
(defObFun (bestMove HanoiDiskRules+) ()
; If self can move two places, which is best? Return the top
; disk of the pole that this disk has not been on recently.
(let ((self (self))
(secondBest) )
(cond ((ask TheTowers
(polesOtherThan
self
#’(lambda ( targetDisk)
(cond ((< (ask self (width))
(ask targetDisk (width)) )
(setq secondBest targetDisk)
(if (not (= (ask targetDisk (pole))
(ask self previousPole) ))
targetDisk ) )) ) ) ))
; as a last resort, return a pole it was on recently
(t secondBest ) ) ) ) ; bestMove
(defObFun (hasLegalMove HanoiDiskRules+) ()
; do either of other two poles have a top disk large enough
; for this disk to rest on?
(let ((self (self)))
(ask TheTowers
(polesOtherThan
self
; when a pole has no disk,
; targetDisk is a mock disk with infinite width
#’(lambda ( targetDisk)
(< (ask self (width))
(ask targetDisk (width)) ) ) ) ) ) ) ; hasLegalMove
(defObFun (moveUpon HanoiDiskRules+) ( destination)
; this disk just moved. Record the new pole and tell user.
(setq previousPole (pole))
; run the version of moveUpon defined in class HanoiDisk
(usual-moveUpon destination) ) ; moveUpon
Listing 16:
; Ted Kaehler and Dave Patterson a taste of SmallTalk
; W. W. Norton ed., chapter 6, pp. 83 ff.
; translated in Allegro Common Lisp by Jean-Pascal J. LANGE.
; © Copyright 1988 Jean-Pascal J. LANGE.
(proclaim ‘(object-variable HanoiDisk+) ) ; HanoiDisk+ class
(proclaim ‘(object-variable HanoiDiskRules) ) ; HanoiDiskRules class
(defObject HanoiDiskRules+ HanoiDiskRules HanoiDisk+)
(defObFun (exist HanoiDiskRules+) (init-list)
(usual-exist init-list) ) ; exist
Listing 17:
Welcome to Allegro CL Version 1.2!
? ( windows)
(#
#
#)
? (ask (license-to-object 230) (set- window-size 512 304))
19923456
? (ask (license-to-object 230) (set- window-position 0 38))
2490368
? (ask (second ( windows)) (set- window-size 512
304)(set- window-position 0 38))
2490368
?