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 4 Section 3

Making the Game

In the last chapter, we had one behavior that was used on each sprite in the game. In addition, we had a simple frame behavior that simply looped on the frame.

In this chapter, we'll do the opposite. The frame behavior will actually be the main piece of code, controlling the game play. The sprites, in turn, will have a simple behavior on them that just passes mouse clicks to the frame behavior.

This all-powerful frame behavior starts off as any other behavior, with a list of properties. The first six are properties that are set when the behavior is assigned to a frame in the Score. The rest are listed here with descriptions.


-- Settable Properties
property pSpriteOffset, pDisplayDelay, pGameOverFrame
property pClickSound, pMatchSound, pNoMatchSound

property pNumberOfCards -- number of cards in the game
property pBoard -- holds a list of what cards are where
property pCard1 -- the first card in a pair clicked on
property pCard2 -- the second card in a pair clicked on
property pCardTimer -- the time that the second card was clicked

The on getPropertyDescription handler returns the information you want displayed in the behavior inspector.


on getPropertyDescription me
  text = "Used on a frame script to make a matching game."&RETURN
  put "Will shuffle a list of cards from the 'Cards' cast library"&RETURN after text
  put "and will assign them to certain sprites based on the sprite offset."&RETURN after text
  put "Will allow the user to click on a pair of sprites and"&RETURN after text
  put "then remove them if they refer to the same card."&RETURN after text
  return text
end

The on getPropertyDescriptionList behavior is much like the one from the previous chapter. One difference is the way we set a range for the "pDisplayDelay" property. Instead of just allowing the author to type in a number for this property, we are going to have a small slider in the behavior Settings dialog box. You do this by including a #range property, with a list that has a #min and a #max value. The result is the slider seen in Figure 4.3.

Figure 4.3
The Parameters dialog box for the Matching Game Frame Behavior, which includes a slider for one of the value.

on getPropertyDescriptionList me
  list = [:]

  -- pSpriteOffset is used to determine how far from
  -- the top of the Score the first card is
  -- so, if the first card starts in channel 11,
  -- the offset would be 10 (10 away from channel 1).
  addProp list, #pSpriteOffset,[cc]
    [#comment: "Card Sprite Offset",[cc]
     #format: #integer,[cc]
     #default: 0]

  -- pDisplayDelay is how many ticks the pair of
  -- cards will be kept on the screen for the player
  -- to see before they are removed or turned back over
  addProp list, #pDisplayDelay, [cc]
    [#comment: "Display Delay",[cc]
     #format: #integer,[cc]
     #default: 60,[cc]
     #range: [#min: 0, #max: 240]]

  -- when a sprite is clicked, what sound is played?
  addProp list, #pClickSound,[cc]
    [#comment: "Click Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- when a sprite is matched, what sound is played?
  addProp list, #pMatchSound,[cc]
    [#comment: "Match Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- when a sprite is not matched, what sound is played?
  addProp list, #pNoMatchSound,[cc]
    [#comment: "No Match Sound",[cc]
     #format: #string,[cc]
     #default: ""]

  -- when all sprites are matched, which frame should the movie go to?
  addProp list, #pGameOverFrame,[cc]
    [#comment: "Game Over Frame",[cc]
     #format: #marker,[cc]
     #default: #next]
  return list
end

Instead of using the #string format for the sound parameters, you could use the #sound format. This presents a pop-up menu of all the sound members in the movie. However, this would also force you to use assign a sound always. Using the #string format is not as convenient, but it does allow you to leave the entry blank, indicating that you do not want to use a sound.

The on beginSprite handler is the one that needs to shuffle the cards to provide a random layout each time the game is played. It starts by getting the number of members in the "Cards" cast library{1}. It then adds each card twice to a list{2}. Then, a random item from this first list is taken and put into a new list{3}, the "pBoard" property. When all the items from the first list have been randomly shuffled into the second, the game is ready to begin.


-- shuffles the cards to build the pBoard list
-- initializes the game properties
on beginSprite me

  -- refers to the "Cards" cast library to see how many cards there are
  pNumberOfCards = the number of members of castLib "Cards"{1}

  -- build a list with each card in the list twice
  list = []
  repeat with i = 1 to pNumberOfCards{2}
    add list, i
    add list, i
  end repeat

  -- fill the pBoard list up randomly with items from
  -- the previously created list
  pBoard = []
  repeat while list.count > 0
    r = random(list.count)
    add pBoard, list[r]{3}
    deleteAt list, r
  end repeat

  -- initialize the game properties
  pCard1 = 0
  pCard2 = 0
end

Each time a sprite is clicked, it sends a "turnCard" message to the frame behavior. This is then handled by the "on turnCard" handler below. The sprite number is also passed to this handler.

With the sprite number, and the "pSpriteOffset" property, the code determines the card number. It records this in either "pCard1" or "pCard2".

In addition, the card is turned over{4}. This simply means that the sprite on the Stage is replaced with the appropriate bitmap from the "Cards" cast.

No decision is made in this handler as to whether a match has been made. Instead, the "pCardTimer" property is set when the second card is turned over{5}. This is monitored by the on exitFrame handler, and the comparison occurs after a specified amount of time passes.

In the meantime, if the player tries to turn another card over, then the time delay ends and the comparison is made immediately. You can see this bit of code in the final else portion of the handler:{6}


-- called by the sprites when the user clicks
-- the spriteNumber parameter is the sprite number clicked
on turnCard me, spriteNumber
  -- play sound, if there is one
  if pClickSound <> "" then puppetSound 1, pClickSound

  -- determine the card number
  cardNumber = spriteNumber - pSpriteOffset

  if pCard1 = 0 then -- first card clicked
    -- record this card
    pCard1 = cardNumber
    -- turn it over
    sprite(spriteNumber).member = member(pBoard[cardNumber],"Cards"){4}

  else if pCard2 = 0 then -- second card clicked
    -- ignore if it is the same card
    if cardNumber = pCard1 then exit
    -- record this card
    pCard2 = cardNumber
    -- turn it over
    sprite(spriteNumber).member = member(pBoard[cardNumber],"Cards")
    -- set the timer
    pCardTimer = the ticks{5}

  else -- two cards are already turned over{6}
    -- this happens if the user clicks very quickly
    -- force a look at the two cards
    returnCards(me)
    -- make sure the card was not clicked on twice
    if sprite(spriteNumber).memberNum = 0 then exit
    -- record new card as the first card
    pCard1 = cardNumber
    -- turn it over
    sprite(spriteNumber).member = member(pBoard[cardNumber],"Cards")
  end if
end

The "on returnCards" handler does the comparison. This handler is also responsible for dealing with the results--either turning the cards back over{7}, or removing them{8}. If a successful match is made, it calls the "on checkAllMatched" handler to see if the game is over.


-- looks at the two cards turned over and compares them
on returnCards me
  if pBoard[pCard1] = pBoard[pCard2] then -- they are a match
    -- play sound, if there is one
    if pMatchSound <> "" then puppetSound 2, pMatchSound
    -- remove both sprites{8}
    sprite(pCard1+pSpriteOffset).memberNum = 0
    sprite(pCard2+pSpriteOffset).memberNum = 0
    -- check for game over
    if checkAllMatched(me) then
      go to frame pGameOverFrame
    end if
  else -- no match
    -- play sound, if there is one
    if pNoMatchSound <> "" then puppetSound 2, pNoMatchSound
    -- turn both cards back{7}
    sprite(pCard1+pSpriteOffset).member = member("Card Back")
    sprite(pCard2+pSpriteOffset).member = member("Card Back")
  end if

  -- reset the game properties
  pCard1 = 0
  pCard2 = 0
end

The on exitFrame handler plays an important part in this game. It checks for the situation in which two cards are turned over. If that is true, then it checks to see if it's a certain amount of time past the time set in "pCardTimer". If this is also true, then it calls "on returnCards" to compare them.

This behavior is also responsible for the critical go to the frame command. This keeps the game looping on the current frame.


on exitFrame me
  if pCard1 <> 0 and pCard2 <> 0 then -- two cards are turned
    if the ticks > pCardTimer + pDisplayDelay then -- time has expired
      -- check the cards to see if there is a match
      returnCards(me)
    end if
  end if

  -- loop on the frame
  go to the frame
end

The last handler checks to see if the game is over. It simply looks at all the sprites where a card should be and sees if there is one there. If it finds any cards{9}, then the game is not over, and a FALSE is returned. Otherwise, a TRUE is returned.


-- check to see if the game is over
on checkAllMatched me
  -- loop through all cards
  repeat with i = 1 to pNumberOfCards
    -- determine the card's sprite
    spriteNumber = i + pSpriteOffset
    -- if it is still a card, then the game is not over
    if sprite(i).memberNum <> 0 then return FALSE{9}
  end repeat

  -- all cards missing, so game over
  return TRUE
end

In addition to this one long behavior for the frame script channel, there needs to be a behavior on each of the individual card sprites. This behavior is just one line of code inside an on mouseUp handler.


on mouseUp me
  -- simply tell the frame script that this sprite was clicked
  sendSprite(0,#turnCard,me.spriteNum)
end

When you want to send a message to the frame script, use sendSprite with a sprite number of 0. In Director 7, you can actually use any sprite number to send a message to the frame script, so long as the sprite does not already have a behavior that handles the same message. Strangely enough, if you were to ask the frame behavior for the value of me.spriteNum, however, it would return -5.

The sendSprite command here sends its message to the frame script channel. To do this, a sprite number of 0 is used as the first parameter. The command also sends an extra parameter, the sprite number.