Ah, puzzles. The first thing most people love to do with Director is create games, and it is a great tool for this. The thing that gets a lot of people is that a puzzle appears simple, but there are a few layers of programming that must go into this. It isn't especially hard, but you need to plan it out carefully from the beginning. To make the system work there are three main areas we need to look at:
We need to be able to move "groups" of pieces
We need to be able to know if a piece is dropped close to one of the pieces it connects to
We need to test if the puzzle is complete
So where do we start? For this example I am using a simple rectangular puzzle that is broken down on a grid pattern, with a slight tab for overlap. This allows us to just let lingo check where pieces intersect. The most valuable lingo command for something like this is the sendAllSprites command. This command is basically like running a loop that gets each sprite to execute a piece of code, but it happens much faster. The other nice thing is that if the sprite does not have that script attached to it, the command is ignored. This way we can have one behavior on all the pieces and each can tell all the others to check and do things.
When we start off the puzzle will be in one piece. Since everything is in place we can do a number of things. The beginSprite handler can record the location of the piece, its locZ (height in 3d space), and give it a group number. The group is how we know when a sprite is connected to other sprites. When 2 sprites "lock" together we set their group numbers the same. Then when we click on a sprite we can start a loop that moves them all together. We also need to get a list of all the sprites that are touching the current sprite. We can not execute this in the beginSprite handler as all of the sprites will not respond. (On sprite 1's beginSprite handler, 2,3,4, etc. do not yet exist on the stage). So how do we do this? For simplicity we can set a property in the beginSprite handler telling it that it has not initialized yet. On the exitFrame handler it checks this property and runs the initialization script the first time it exits the frame, then resets the variable so the loop will not happen again.
So now what? Well, once we know all this, we can turn the box upside down and throw everything out on the table! You will see below that there is a command called shuffle. This takes the pieces size and the stage size and picks a random place inside the stage to drop the piece. Now the fun starts. We don't want to set the sprites to moveable as it is harder to track them. We only want them to move when we let them. This way we can make everything move together using one handler. Every time the sprite is clicked a loop starts that runs until the mouse is released. In this loop we do a sendAllSprites call that tells every sprite to check its group number, and if they match, move the same amount as the mouse. Then when we let go we tell all the sprites to gather a list of all the sprites they can connect to and send them back. With this list we use the sendSprite command to send each sprite in the list a command to see if they are the same amount of space away from their original location as our clicked piece. If so we change the group and lock them. For ease of use I have also added a number of pixels that the user can be within and the pieces will snap together, as well as using the locZ to put the clicked piece above the other pieces. Below is the code...
on beginSprite me
if amDone = void then amDone = false
if tollerance = void then tollerance = 5
if allPieces = void then allPieces = 0
allPieces = allPieces + 1
topZ = sprite(spriteNum).locZ
origonalLoc = sprite(spriteNum).loc
group = spriteNum
baseMove = sprite(spriteNum).loc
initialized = false
end
on mouseDown me
if amDone = false then
topZ = topZ + 1
sendAllSprites(#pullUp, group)
startClick = the mouseLoc
sendAllSprites(#getBase)
repeat while the stilldown
sendAllSprites(#moveMe, group, (the mouseLoc - startClick))
updateStage
end repeat
--see if it "locked" with another bordering sprite
searchList = []
--get a searchList
sendAllSprites(#checkGroup, group, searchList)
returnList = []
--check the locking location of all touching sprites
repeat with x = 1 to searchList.count
sendSprite(searchList[x], #checkLock, group, origonalLoc - sprite(spriteNum).loc, returnList)
if returnList <> [] then
sendAllSprites(#lockGroup, group, returnList[1][1], returnList[1][2])
exit repeat
end if
end repeat
updateStage
winList = []
sendAllSprites(#checkWin, winList, group)
if winList.count = 0 then
--all true, winner
amDone = true
go "win"
end if
end if
end
on exitFrame me
if initialized = false then
lockList = []
sendAllSPrites(#getConnected, spriteNum, lockList)
lockSpritesList = lockList
initialized = true
end if
end
on getConnected me, whatSprite, lockList
if sprite(spriteNum).intersects(whatSprite) and whatSprite <> spriteNum then
lockList.add(spritenum)
end if
end
on shuffle me
okW = the stageRight - the stageLeft - sprite(spriteNum).width
okH = the stageBottom - the stageTop - sprite(spriteNum).height
randomH = random(okW)
randomV = random(okH)
sprite(spriteNum).rect = rect(randomH, randomV, randomH + sprite(spriteNum).width, randomV + sprite(spriteNum).height)
end
on pullUp me, whatGroup
if whatGroup = group then
sprite(spriteNum).locZ = topZ
end if
end
on getBase me
baseMove = sprite(spriteNum).loc
end
on checkGroup me, theGroup, searchList
if theGroup = group then
if lockSpritesList.count > 0 then
repeat with x = 1 to lockSpritesList.count
if searchList.getOne(lockSpritesList[x]) = 0 then searchList.add(lockSpritesList[x])
end repeat
end if
end if
end
on lockGroup me, whatGroup, newGroup, newOffset
if group = whatGroup then
group = newGroup
sprite(spriteNum).loc = origonalLoc - newOffset
end if
end
on moveMe me, whatGroup, whereMove
if group = whatGroup then
sprite(spriteNum).loc = sprite(spriteNum).baseMove + whereMove
end if
end
on dropDown me, howMany
sprite(spriteNum).locZ = sprite(SpriteNum).locZ - howMany
end
on checkLock me, whatGroup, whatOffset, returnList
--if already linked no reason to run
if whatGroup <> group then
myOffset = origonalLoc - sprite(spriteNum).loc
compareOffset = (myOffset - whatOffset)
if abs(compareOffset.locH) <= tollerance and abs(compareOffset.locV) <= tollerance then
--it is "snapable"
returnList.add([group, myOffset])
end if
end if
end
on checkWin me, winList, whatGroup
if group <> whatGroup then
winList.add(spriteNum)
end if
end
This may look like a lot at first, but basically all that we have done is taken each command out as a separate handler and allowed it to be accessible through a sendSprite or sendAllSprites command. From here we make our pieces, set them to background transparent (matte will not work as no pixels actually overlap), and make a marker called "win" to go to when all pieces match. Put the assembled puzzle on the stage, drop on the behavior, and let it go. To mix them up just issue the following command...
sendAllSprites(#shuffle)
That's it! Here is an example of the finished script inaction. All that was added were the two buttons, an exitFrame handler to issue the shuffle command, and a basic "go to the frame" script.
Contact
MMI
36 South Court Sq
Suite 300
Newnan, GA 30263
USA