
Example: Towers of Hanoi
This rendition of Towers of Hanoi is a simple game to code and makes introductory use of 3D shapes. Recreating it could serve as a great introduction to turtleSpaces and Logo coding in general.
In the game, you attempt to transfer the disks on post 1 to post 3 ending in the same order the disks started in (smallest to largest, starting from the top). To do this, you can transfer disks between posts, but disks can only be placed on top of disks smaller than they are.
This example uses lists to store the disks on each post, and the queue and dequeue primitives to ‘transfer’ the disks from one ‘post’ (list) to another.
The graphical representation of the game is optional and can be added after first creating the game logic, using text output. A potential project would be to first create the game logic, using the game rules (and for the instructor, this example) as a guide. In its most basic form, it can be created using conditionals (if), lists (make, queue, dequeue). loops (label, go), and input/output (print, question) primitives.
Then, add the graphical representation, once the game is working correctly. The example here uses voxels, cylinders and typeset text. Finally, add some camera movements.
The example follows, and is commented:
SETABOUT |Towers of Hanoi (run myrtle:hanoi)|
CREATORS [melody]
NEWTURTLE "myrtle
TO hanoi
;This is a simple recreation of the classic
;Towers of Hanoi game. It has fairly basic logic
;and would nake a great introduction to game creation
;using turtleSpaces
;as a project, we would create the text-game
;first and then add the graphical representation
;later (as a 'stretch goal')
reset
hideturtle
penup
;reset and set up workspace
make "moves 0
;initialize the moves counter
;*** OPTIONAL ***
make "camerastep 1
;define camera 'step'
cam:orbitdown 90
;position camera
;*** END OPTIONAL ***
print |*** TOWERS OF HANOI ***|
print |Move the disks from rod 1 to rod 3, keeping the same order|
print |(largest at the bottom to smallest at the top)|
cursordown
playsound "doodoo
;instructions
label "start
question |Number of Disks (3-9)?|
make "disks answer
if not numberp :disks [go "start]
if or :disks < 3 :disks > 9 [go "start]
;select number of disks to play with
;and validate choice
if :disks > 6 [print |Wow! You're brave! Good luck!| cursordown]
;message of encouragement for hard levels
;*** OPTIONAL ***
control "snappy {"raise 2 * :disks}
control "snappy {"pullin 140 - (8 * :disks)}
make "snappypos snappy:position
make "snappyvec snappy:vectors
;position camera based on number of disks
;and store the camera position.
;this can be omitted if text-only game
;*** END OPTIONAL ***
make "rod1 reverse range {1 :disks}
make "rod2 []
make "rod3 []
;define the rod lists, creating the disks
;on the first rod
label "select
;labels can be returned to using 'go'
(show :rod1 :rod2 :rod3)
;show the rod lists
(show |Moves elapsed:| :moves)
;and the move count
;*** OPTIONAL ***
drawdisks
;draw the graphical representation
;(via the optional drawdisks user procedure)
;*** END OPTIONAL ***
question |Rod?|
make "selection word "rod answer
if not namep :selection [print |Invalid selection!| go "select]
if (count thing :selection) = 0 [print |Invalid rod!| go "select]
;select the source rod and check for validity
label "dest
question |Destination?|
make "destination word "rod answer
if not namep :destination [print |Invalid selection!| go "dest]
if (last thing :destination) < (last thing :selection) [
print |Invalid move!|
go "select
]
;select the destination rod and check for validity
make "disk dequeue :selection
queue :disk :destination
;move the disk, by 'dequeuing' or removing the last
;item from the selected rod into a variable,
;and then queuing the item (adding it as the last
;item) to the destination rod
inc "moves
;increment the moves counter
;*** OPTIONAL ***
;the following orbits the camera around the game
;to spice it up a little:
inc "camerastep
;increment the camerastep container
cam:repeat 45 [orbitright 1]
;orbit the camera 45 degrees right
if divp 3 :camerastep [cam:repeat 45 [orbitright 1]]
;if camerastep divisible by 3 then orbit another 45
;so we're not looking at the side of the game
;*** END OPTIONAL ***
if :rod3 = reverse range {1 :disks} [
drawdisks
;update the graphics (OPTIONAL)
control "snappy {
"setposition :snappypos
"setvectors :snappyvec
}
;restore the camera position (OPTIONAL)
print |You win! Great job. Try more disks!|
(print |Moves taken:| :moves)
playsound "applause
finish
]
;check the third rod for a completed tower
;and if complete, displays 'win' message and quits
go "select
;return (loop back) to select label
END
TO drawdisks
;the graphical representation of the towers is
;optional, and makes a good 'stretch goal'
;you can start with just rendering the disks,
;then add a base and rods, and finally
;rod numbers (to make it easier to keep track
;of the rods when playing with camera rotation
;enabled)
norender
;don't update graphics until render is called
clean
;erase graphics
setposition {-50 - 4 * :disks 0 - 4 * :disks 0}
setfillcolor brown
voxeloid 2 * (50 + 4 * :disks) 2 * (4 * :disks) 5
;create base of appropriate size
repeat 3 [
setposition {-100 + (50 * repcount) 0 0}
rollright 180
setfillcolor brown
cylinder 2 5 + :disks * 5 10
rollright 180
raise 5
;create rod of appropriate height
if 0 < count thing word "rod repcount [ ;if there are disks on the rod: foreach "i thing word "rod repcount [ ;for each disk on the rod: setfillcolor :i ;set the color based on the disk size cylinder 2 + (2 * :i) 5 10 * :i ;create the disk raise 5 ] ] ] ;create disks on each rod up 90 settypesize 5 + (:disks / 4) repeat 3 [ setfc item repcount [13 11 14] setposition { (-100 - :disks / 2) + 50 * repcount 0 - 4 * :disks (-14 - (:disks / 3)) - (:disks / 4) } typeset repcount ] ;print numbers under towers rollright 180 repeat 3 [ setfc item repcount [14 11 13] setposition { (100 + :disks / 2) - 50 * repcount 4 * :disks (-14 - (:disks / 3)) - (:disks / 4) } typeset 4 - repcount ] ;print numbers under towers opposite side rollright 180 down 90 ;return the turtle to the proper orientation render ;resume rendering if :moves > 0 [playsound "knock]
;if not the start of the game, make a sound
END
NEWTURTLE "snappy
NEWTURTLE "libby