Articles Archive
Articles Search
Director Wiki
 

Towers of Hanoi game

February 20, 2000
by Pat McClellan

I want to create that old Towers game where you have 3 sticks (or towers) with 3 or more rings on the first of them. The objective is to move all of them to another stick. The trick is you can put only a smaller ring on bigger, not vice versa. Any ideas?

Laszlo Nathay
Hungary

Dear Laszlo,

Creating a whole game is more extensive than I can generally handle in a column, but this one intrigued me. It occurs to me that this whole game can be done with a single behavior. The first thing I do when considering a program like this is to figure out where and when all the processing must happen. Let's start by making a list of "the rules".

My main observation from this is that there's really nothing that happens outside of the user's interaction with the sprites; nothing animating in the background, etc.

There are several approaches to creating a game like this. One method would involve a single "object" or frame script which would watch and control all the sprites and user interaction. This is a common approach and a good one. However, I tend to favor an approach involving sprite behaviors. This shifts the burden of managing the sprites to the sprites themselves. Each one "knows" what it can and cannot do. They'll need to share some information with each other (in the form of a property list stored in a global variable) -- but otherwise, each one operates independent of a centralized manager. One approach is not better than the other -- though this can certainly inspire some heated debate among Lingo-philes. For me, it's more a matter of style.

Here's a demo of what we'll be discussing. Play with it a bit to observe how it works.

A sample movie is available for download in Mac or PC format. This is a D7 movie.

I can't think of many games which don't use lists. Lists are extremely powerful and once you get the syntax of them down, they're easy to use too. Let's start thinking of our "towers" as lists, named t1, t2, and t3. If all 5 pieces are on tower one, our lists could look like this:

t1 = [5,4,3,2,1]
t2 = []
t3 = []

The numbers in the list represent the size of the pieces in the list, not the sprite numbers. This is the status of the lists when the game starts. The only piece that is moveable is piece #1 -- the last item in the list -- corresponding to the top piece on the tower. If the player moves the top piece (size 1) to the second tower, our lists would look like this:

t1 = [5,4,3,2]
t2 = [1]
t3 = []

Now which pieces should be moveable? Pieces 1 and 2 -- because they're at the top of their towers; that translates to the last position in their respective lists. This gives us the key to knowing which pieces should be moveable and which are locked in place.

What we've seen so far with these tower lists is essentially the "occupancy" of that tower. But we also need to keep track of some other tower info; specifically, we need to know the base location of each of the towers. I'm just talking about the locH and locV of the bottom of each tower. For example, in the demo movie, tower1's baseLoc is point(60,200). You may know that lists can hold just about anything, including other lists. So let's keep things tidy by putting the baseLoc info into our t1 list (and t2 and t3).

t1List = [#baseLoc: point(60,200), #occupants: []]
t2List = [#baseLoc: point(180,200), #occupants: []]
t3List = [#baseLoc: point(300,200), #occupants: []]
  

Then, we can put all three of these lists into a property list called gTowerList.

gTowerList = [#t1: t1List, #t2: t2List, #t3: t3List]

That's how gTowerList will look when the game initializes. Actually, we'll stash a couple of other items in there too.

gTowerList = [#t1: t1List, #t2: t2List, #t3: t3List, ¬
  #spriteHeight: 20, #moves: 0]

#spriteHeight is simply the height of the pieces that move. We'll need that for accurately placing the piece when the user drops it. The other property, #moves is where we'll keep track of how many moves the player took to complete the game.

This one behavior, which I'll call the TowerSprites Behavior is dropped on each of our pieces, and it's going to have a lot of handlers in it. So rather than going through each line of code here, I'll simply summarize the sequence. I'd recommend that you download the demo movie now and follow along in the Lingo if you'd like.

on beginSprite me sets up some common properties (pSprite, pLoc) and checks to see if gTowerList already exists. The first sprite that initializes will see no existing gTowerList, so it will call the initTower handler. on initTower me sets up the gTowerList as is shown above. It also starts the timer for the game. The last line of the beginSprite handler calls another handler, initTowerSprite.

on initTowerSprite me looks at gTowerList and extracts the baseLoc for each tower. It assumes that #t1 is the starting tower (pStartTower) and sets pCurrentTower = pStartTower. (You'll soon understand what these properties are used for -- just hang in there for now.) Next, we get down to the 2 most important things in this handler. A call is made to reportToList, which causes this sprite to add itself to the occupants list of the pCurrentTower. It is important that the sprites add themselves to this list in a particular order (from largest to smallest). To make sure that this happens, you should put the sprites (the pieces) in the score so that the biggest pieces are in the lower numbered sprite channels. (See the demo movie.) Lastly, a call is made to broadcastMoveable me. This handler does what we were talking about earlier: it looks at the three tower lists and finds the size of the pieces that are last on each list (at the top of the tower). Then if the size matches this sprite's pSize, it makes the sprite moveable. (NOTE: this is why it's important not to have two pieces with the same size.)

Now, we're up to the point where we can start the game. gTowerList looks like this:

[#t1: [#baseLoc: point(60, 200), #occupants: [5, 4, 3, 2, 1]], #t2: [#baseLoc: point(180, 200), #occupants: []], #t3: [#baseLoc: point(300, 200), #occupants: []], #spriteHeight: 20, #moves: 0]

If you don't understand everything in this list now, go back and figure it out. Otherwise, you won't be able to follow what's coming up. Note that only the sprite whose size is 1 is set to be moveable (which you can see by looking at the last item in each of the occupants lists).

At this point, nothing happens until the player drags and drops a piece. That dropping event will trigger the mouseUp handler. The mouseUp handler calles the reportToList handler -- and does nothing else.

on reportToList me checks to see if the piece has been dropped on a tower (using the locV from the baseLoc properties in gTowerList). If the piece is in the vertical column of one of the towers, a variable (whichTower) is set to the correct tower. If the piece is not over a tower, then whichTower is set to the piece's current tower. Now that the tower has been determined, we have to try to add the piece to that tower's list.

on tryToAdd me, whichTower tests to see if the piece is smaller than the existing pieces in that tower's list. If so, the piece is added to the list and the #moves property is incremented by one. On the other hand, if the piece is larger than the top piece in that tower, then the piece is returned to its previous tower location -- and no move is charged to the player. After the correct tower assignment is determined, we need to determine which vertical tier this piece will fall into. The variable myTier is just the position of this piece in that tower's list. Finally, we'll need to set the location so that the piece can neatly lock into place.

on setLoc me, whichTower, myTier calculates the new position starting with the baseLoc of the tower, then subtracting (myTier * gTowerList.spriteHeight). I told you we'd need that #spriteHeight value eventually! After the piece is locked into place, we need to check to see if the player has won. There are three conditions which constitute a win:

  1. pSize = 1 -- because the last move of the game will always be the smallest piece);
  2. whichTower <> pStartTower -- because the rules say that you have to move all the pieces to a different tower;
  3. myTier = 5 -- because that means that all of the other pieces must be on this tower as well.

I've set the behavior up so that it goes to a frame called "win" if all three of these conditions are met. If not, we call the broadcastMoveable me handler and the game continues.

That's it for the TowerSprite Behavior. The only other thing is that I set up a simple little behavior to display the timer and moves info in the win frame. I hope this has inspired some of you to explore the power of lists and behaviors.

Patrick McClellan is Director Online's co-founder. Pat is Vice President, Managing Director for Jack Morton Worldwide, a global experiential marketing company. He is responsible for the San Francisco office, which helps major technology clients to develop marketing communications programs to reach enterprise and consumer audiences.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.