Note About This Book: Advanced Lingo For Games was written by Gary Rosenzweig in 2000 for users of Macromedia Director 7. It is presented here for free on an as-is basis, with no updating. Most of the information and code here can be used in the most recent version of Director. The book has been reproduced from the final editing files archived in 2000, and not the final proof galleys. So some minor differences between this version and the printed version my exist. The entire contents of this book are Copyright 2000, Gary Rosenzweig. No part may be reproduced or copied without written permission. The text here is provided for individual use only.
Want to thank me for making this book available for free? Just buy Special Edition Using Macromedia Director MX and we'll call it even!

Advanced Lingo For Games
by Gary Rosenzweig


Chapter 6 Section 3

Making the Game

This game consists of two behaviors, one for the frame and one for the sprite. The frame behavior is the main one, keeping track of the game state, while the sprite behavior simply registers clicks and perform the animation.

Frame Behavior

The frame behavior uses five parameters and four properties. The parameters are used to determine the sprite range, the empty spot at the start of the game, and the frame that the movie jumps to when the game is over.


-- parameters
property pFirstSprite, pLastSprite
property pOpenSpot, pGameOverFrame
property pSlideSound

The properties hold the calculated value of the width and height of the pieces. A property will also hold the location of the upper-left puzzle piece as a landmark for the location of the other pieces.

The fourth property is one that will become quite common throughout the rest of this book. We name it "pMode" because it determines what mode the game is in. In this case, we will have two modes. One mode will be #normal for when the game is waiting for user input. The other will be #animate for when the puzzle pieces are moving. During the #animate mode, we will not allow the user to click on a puzzle piece.


-- properties
property pPieceWidth, pPieceHeight
property pUpperLeftLoc, pMode

The on getPropertyDescriptionList handler has its defaults set for a 5[ts]5, or 24-piece, puzzle. The 25th slot is the open one. Figure 6.3 shows the Parameters dialog box for this behavior.

Figure 6.3
The Parameters dialog box for the "Sliding Puzzle Frame Behavior."

on getPropertyDescriptionList me
  list = [:]

  -- the first puzzle piece channel
  addProp list, #pFirstSprite,[cc]
    [#comment: "First Sprite",[cc]
     #format: #integer,[cc]
     #default: 11]

  -- the last puzzle piece channel
  addProp list, #pLastSprite,[cc]
    [#comment: "Last Sprite",[cc]
     #format: #integer,[cc]
     #default: 34]

  -- the open spot, usually the bottom right corner
  addProp list, #pOpenSpot,[cc]
    [#comment: "Open Spot (point(x,y))",[cc]
     #format: #point,[cc]
     #default: point(5,5)]

  -- the sound to be played during a slide
  addProp list, #pSlideSound,[cc]
    [#comment: "Slide Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- frame to jump to when the puzzle is solved
  addProp list, #pGameOverFrame,[cc]
    [#comment: "Game Over Frame",[cc]
     #format: #marker,[cc]
     #default: #next]

  return list
end

When the frame is entered, the on beginSprite handler calculates the values of "pPieceWidth" and "pPieceHeight" based on the first puzzle piece sprite. All pieces should be the same dimensions. The "pUpperLeftLoc" is also set this way. The "pMode" is set to #normal, and then the "on randomizePieces" handler is called.


-- set properties and randomize puzzle
on beginSprite me
  -- get the piece width and height from the first piece
  pPieceWidth = sprite(pFirstSprite).width
  pPieceHeight = sprite(pFirstSprite).height
  -- get the location of the upper left piece to use as a landmark
  pUpperLeftLoc = sprite(pFirstSprite).loc
  -- start in normal mode, which allows clicks
  pMode = #normal
  -- mix it up
  randomizePieces(me)
end

To randomize the pieces on the Stage, we shuffle their positions. First, a "posList" variable is created. Then, it is filled with the positions of all the sprites. Next, all the sprites are assigned one position from the list.


-- this handler shuffles the piece positions
on randomizePieces me
  -- create a list with all positions
  posList = []
  repeat with s = pFirstSprite to pLastSprite
    add posList, sprite(s).loc
  end repeat
  -- randomly assign positions to sprites
  repeat with s = pFirstSprite to pLastSprite
    r = random(posList.count)
    sprite(s).loc = posList[r]
    deleteAt posList, r
  end repeat
end

When a puzzle piece is clicked, the sprite behavior sends a #clickPuzzle message to the frame behavior. This message includes the sprite's number as a second parameter.

The first thing that the "on clickPuzzle" handler does is make sure that the "pMode" is #normal. Otherwise, it will ignore the click. This happens when there is animation in progress. You'll see where we set the "pMode" to #animate later in this behavior.

The handler then determines the location of the sprite in the puzzle by using its location, the location of the upper-left corner of the puzzle, and the width and height of each piece{1}. This position is stored in the two variables: "x" and "y".

Then, the empty space in the puzzle, represented by the "pOpenSpot" property, is compared to the position of the piece to see if the two are next to each other. If so, then the "on move" handler is called with the horizontal and vertical difference between the two positions{2}. This "on move" handler takes care of swapping the piece for the empty slot.


-- the sprite behaviors call this handler with their sprite number
-- it determines if the piece is next to the empty space
-- and moves it there if it is
on clickPuzzle me, spriteNumber
  -- make sure we are in normal mode
  if pMode <> #normal then exit
  -- determine the x and y position of the piece
  x = (sprite(spriteNumber).locH-pUpperLeftLoc.locH)/pPieceWidth+1{1}
  y = (sprite(spriteNumber).locV-pUpperLeftLoc.locV)/pPieceHeight+1
  -- see if the open spot is to the left, right, above or below
  if pOpenSpot.locV = y then -- move horizontally
    if pOpenSpot.locH = x-1 then -- move left
      move(me,spriteNumber,-1,0)
    else if pOpenSpot.locH = x+1 then -- move right
      move(me,spriteNumber,1,0)
    end if
  else  if pOpenSpot.locH = x then -- move vertically
    if pOpenSpot.locV = y-1 then -- move pup
      move(me,spriteNumber,0,-1){2}
    else if pOpenSpot.locV = y+1 then -- move down
      move(me,spriteNumber,0,1){2}
    end if
  end if
end

The "on move" handler takes the difference between the puzzle piece position and the empty slot as two variables: "dx" and "dy". Variables with these names are often used to indicate the "difference" between two locations.

The new location of the piece is calculated. Then, the new "pOpenSpot" property is calculated. Finally, the sprite is sent the #changeLoc message, which kicks off the animation to slide the piece into the empty slot. The "pMode" for the frame behavior is also changed, so clicks are ignored until the animation is done.


-- move a sprite into the empty spot
on move me, spriteNumber, dx, dy
  -- determine new location from dx, dy
  newloc = sprite(spriteNumber).loc + point(dx*pPieceWidth,dy*pPieceHeight)
  -- change the open spot to be where the piece was
  pOpenSpot = pOpenSpot - point(dx,dy)
  -- tell the sprite to animate to the new location
  sendSprite(spriteNumber,#changeLoc,newloc)
  -- set the mode, so that no clicks are allowed until animation is done
  -- play the sound
  if pSlideSound <> "" then puppetSound pSlideSound
  -- change the mode to no allow clicks until slide is complete
  pMode = #animate
end

When the sprite is done animating, it sends a #resetMode message back to the frame behavior. This does two things. First, it resets the "pMode" to #normal, thus allowing clicks to be recognized again. Second, it calls the "on checkForGameOver" handler to see if the puzzle is complete.


-- when animation is done, sprite will call this to allow more clicks
on resetMode me
  pMode = #normal
  -- also see if all pieces are now in the correct spots
  if checkForGameOver(me) then
    go to frame pGameOverFrame
  end if
end

To determine if the game is over, the "on checkForGameOver" handler loops through all the sprites. The sprites are arranged in the order of the solved puzzle, with the upper-left corner piece first, the one to the right of it second, and so on. First, it checks to see if the sprite is just to the right of the sprite before it{3}. If this is so, then these two sprites are in order. Otherwise, it checks to see if the next sprite is the start of a new row{4}. If so, then the sprites are still in order. However, if there is any deviation from this, then it means that the sprites are out of order on the screen, and a FALSE is returned.


-- loop through all pieces starting with the second one
-- if each piece come either to the right, or starts a new row,
-- then the puzzle is done
on checkForGameOver me
  -- get loc of first sprite
  prevLoc = sprite(pFirstSprite).loc
  repeat with i = pFirstSprite+1 to pLastSprite
    -- get loc of this sprite
    nextLoc = sprite(i).loc
    -- see if they are next to each other
    if nextLoc.locH <> prevLoc.locH + pPieceWidth then{3}
      -- or, see if it is the first of the next row
      if (nextLoc.LocV <> prevLoc.locV + pPieceHeight) or[cc]
         (nextLoc.locH <> pUpperleftLoc.locH) then{4}
        -- neither, so must be out of order
        return FALSE
      end if
    end if
    -- ready to look at next piece
    prevLoc = nextLoc
  end repeat
  -- made it here, so all pieces must be in order
  return TRUE
end

The last function of the frame behavior is the standard looping frame code.


-- loop on the frame
on exitFrame
  go to the frame
end

The Sprite Behavior

The sprite behavior starts off by simply setting the cursor property of the sprite to 280, which is the finger cursor. This is a good way to indicate, without instructions, that the player is to click on the puzzle pieces.

In addition, the sprite behavior also has a "pMode" property. We use the same two values for this "pMode" property as we did in the frame behavior: #normal and #animate.


property pNewLoc, pMode

on beginSprite me
  -- use the finger cursor for this sprite
  sprite(me.spriteNum).cursor = 280
  -- normal mode (do nothing)
  pMode = #normal
end

When the sprite is clicked, the #clickPuzzle message is sent along to the frame behavior to be dealt with.


-- pass mouse clicks along to the frame behavior
on mouseUp me
  sendSprite(0,#clickPuzzle,me.spriteNum)
end

When the frame behavior determines that the sprite needs to change location, it calls the "on changeLoc" handler shown here. A parameter tells the sprite where the destination is. The "pMode" of the sprite behavior is changed to #animate.


-- frame behavior calls this to initiate an animation
on changeLoc me, newloc
  -- record the destination location
  pNewloc = newloc
  -- allow the animation
  pMode = #animate
end

The "on exitFrame" behavior uses the "pMode" property to determine if it's supposed to be doing anything. If it is, the "on exitFrame" behavior then examines its current position with the "pNewLoc" position, and brings it one pixel closer to the destination.{5}

If the destination is reached{6}, a message, #resetMode, is sent back to the frame behavior, and the "pMode" of the sprite behavior changes back to #normal.


on exitFrame me
  -- if the destination does not equal the current position
  if pMode = #animate then
    -- move the location by one pixel in the right direction
    curloc = sprite(me.spriteNum).loc
    if curloc.locH < pNewLoc.locH then
      curloc = curloc + point(1,0){5}
    else if curloc.locH > pNewLoc.locH then
      curloc = curloc - point(1,0){5}
    else if curloc.locV < pNewLoc.locV then
      curloc = curloc + point(0,1){5}
    else if curloc.locV > pNewLoc.locV then
      curloc = curloc - point(0,1){5}
    end if
    sprite(me.spriteNum).loc = curloc
    -- if this ends the animation, tell the frame script
    -- so its pMode can be changed to #normal
    -- and set the sprite's pMode to #normal too
    if curloc = pNewLoc then{6}
      sendSprite(0,#resetMode)
      pMode = #normal
    end if
  end if
end

Note that the position of the sprite is changed by one pixel at a time. The example movie on the CD-ROM is set to 120 frames per second. So a 40[ts]40 piece should slide into a new position in one-third of a second. However, if you feel that you need to make the game work smoothly for slower computers, ones that cannot do 120 frames per second, you might want to move the pieces more than one pixel at a time. You can change to four point values in the previous handler to use any number of pixels you want. However, make sure that it uses a number divisible by the dimensions of the pieces.

For instance, if the pieces are 40 pixels wide and 40 pixels high, then 1, 2, 4, and 8 work well. However, if the pieces move 3 pixels at a time instead, they will overshoot the destination location because 40 is not divisible by 3.