|
Want to thank me for making this book available for free? Just buy Special Edition Using Macromedia Director MX
Advanced Lingo For Games
| |
| -- get character code
if ascii >= chartonum("a") and ascii <= chartonum("z") then -- is it lowercase?
c = numtochar(ascii-32) -- convert to uppercase
end if
return c
end
Creating a matrix from the word list is a fairly complex process. First, a list of lists is created in the property "pMartix"{1}. There will be 15 lists of 15 characters. Each list is a row in the matrix. All the characters in each row start off as "*" characters. Next, the "pWordList" property is initialized to an empty list. As words fit into the matrix, they are added to the word list as well. Then, the program loops, trying to fit random words into the matrix. This is done by first picking a random word from the list{2}. Then, it picks a random direction for the word to lay in the matrix{3}. Next, a random starting spot is determined{4}, with special care that the word doesn't extrude out from the sides of the matrix. After the handler knows which word, where it will be placed, and in what direction it will lay, it tries to fit the word into the matrix{5}. If all the characters in those positions in the matrix are "*"s, the word fits in. However, if another letter has been placed in a spot by a previous word, then the overlapping letters must be exactly the same. Otherwise, the insertion is aborted and the whole process starts over again. As the matrix fills up with words, it becomes harder and harder to fit words in. If you have enough words, it might reach the point where some will never be able to fit. In that case the loop repeats endlessly, trying to fit the word in. To prevent this, there is a variable called "loopCount" that counts the number of times the loop repeats. After it hits a high number, the loop exits{6}. If all the words fit quickly into the matrix, this will never happen. However, if there are too many words, the loop is assured to end, even if it means excluding some of the words. The last two things that the "on buildMatrix" handler does is replace any remaining "*"s with random letters, and sort the word list. The latter function is done so that the word list appears in alphabetical order on the screen.
-- create a matrix (list of lists) with the letters
on buildMatrix me
-- build matrix with all * characters{1}
pMatrix = []
repeat with i = 1 to pMatrixSize
temp = []
repeat with j = 1 to pMatrixSize
add temp, "*"
end repeat
add pMatrix, temp
end repeat
-- get list of words
list = getWords(me)
pWordList = []
loopCount = 0
-- loop until all words are used
repeat while list.count > 0
-- get random word{2}
w = list[random(list.count)]
-- pick random direction{3}
horizPlace = random(3)-2 -- horizontal direction
vertPlace = random(3)-2 -- vertical direction
-- check for no direction at all
if horizPlace = 0 and vertPlace = 0 then next repeat
-- pick a random starting spot{4}
x = random(pMatrixSize-abs(horizPlace)*(w.length+1))
y = random(pMatrixSize-abs(vertPlace)*(w.length+1))
-- add to position if the word is to be placed backwards
if horizPlace = - 1 then x = x + w.length-1
if vertPlace = - 1 then y = y + w.length-1
-- see if the word fits in that spot{5}
ok = TRUE
repeat with i = 1 to w.length
-- get this letter
letter = pMatrix[y+vertPlace*(i-1)][x+horizPlace*(i-1)]
-- see if this letter will overlap another
if (letter <> "*") and (letter <> w.char[i]) then ok = FALSE
end repeat
-- if it will fit, then add
if ok then
repeat with i = 1 to w.length
-- set letter in matrix
pMatrix[y+vertPlace*(i-1)][x+horizPlace*(i-1)] = w.char[i]
end repeat
-- remove word from list
deleteOne list, w
-- add word to word list
add pWordList, w
end if
-- limit loops to avoid words that can never fit in
loopCount= loopCount + 1
if loopCount > 10000 then exit repeat{6}
end repeat
-- replace all *s with random letters
repeat with y = 1 to pMatrixSize
repeat with x = 1 to pMatrixSize
if pMatrix[y][x] = "*" then
pMatrix[y][x] = numtochar(64+random(26))
end if
end repeat
end repeat
-- alphabetize word list
sort pWordList
end
After the matrix is complete, it needs to be drawn on the screen. The matrix is drawn by building a string from the contents of the "pMatrix" list. A space is placed between every letter, so that the width of the matrix is doubled, which matches the height of the matrix a little closer, making it close to square. The handler also sets the fixedLineSpace property of the text member to 14, and the color of all the letters to black. The text member property fixedLineSpace is a somewhat unknown and unused Lingo property. However, it's the only way to set the line spacing of a text member in Lingo. This is especially useful for making sure that line spacing is consistent cross-platform.
-- take matrix list and populate text member
on showMatrix me
text = ""
repeat with y = 1 to pMatrixSize
repeat with x = 1 to pMatrixSize
put pMatrix[y][x]&SPACE after text
end repeat
put RETURN after text
end repeat
-- set member text, line spacing and color
member("Matrix").text = text
member("Matrix").fixedLineSpace = 14
member("Matrix").color = rgb("000000")
end
Another element that needs to be shown on the screen is the word list. The following handler put the contents of the "pWordList" property into a text member. As the player finds words, this list is redrawn to show only those words that remain.
-- put word list into text member
on showWordList me
text = ""
repeat with i = 1 to pWordList.count
put pWordList[i]&RETURN after text
end repeat
member("Word Display").text = text
end
From here on, all the handlers deal with the player's interaction with the matrix. The player's only move in this game is to select a consecutive group of letters in the matrix. To start this off, the player positions the cursor over the first letter and presses down on the mouse button. When this happens, the on mouseDown handler determines which character the player has selected, and sets both the "pFirstChar" and "pLastChar" to a list that contains the horizontal and vertical position of that character. -- begin selection on mouseDown me c = getChar(me,the clickLoc) -- make sure it is a valid selection if c = 0 then exit -- activate selection process pFirstChar = c pLastChar = c end To determine which character the mouse is located over, the following handler first figures out how far apart the characters are in the matrix. The horizontal distance is calculated by getting the position of the third character in the text member. By using charpostoloc to do this, we can calculate the distance between the first and third characters, which is the same as the distance between any two characters. Because the characters in the matrix have a space between them, this distance is the same as the distance between letters in the matrix. The vertical position is much more simply defined by the fixedLineSpace property of the text member. After we know the horizontal and vertical spacing of the letters, we can take the mouse position and return a list with the horizontal and vertical position of the letter that the user is pointing to. Experienced Lingo programmers will ask why I'm not using mouseChar or pointToChar here. Both of these functions return the character under the mouse, but neither return the horizontal and vertical position of the character in the matrix without many more lines of code. -- get character position from cursor position on getChar me, loc -- get width and height of letter in grid w = charpostoloc(sprite(me.spriteNum).member,3).locH h = sprite(me.spriteNum).member.fixedLineSpace -- remove offset of sprite loc = loc - sprite(me.spriteNum).loc -- calculate location x = loc.locH/w y = loc.locV/h return [x,y] end The on exitFrame handler has the task of determining the current selection and highlighting it. The "pFirstChar" property stays the same, but the "pLastChar" property is constantly rechecked every frame to determine the current selection. After we have a "pFirstChar" and a "pLastChar", we need to be sure the selection is valid. The only valid selections are the ones that are completely horizontal, vertical, or diagonal. By diagonal, we mean a 45-degree angle in this case. If the selection is valid, then "on drawLine" is called to show the selection.
-- show selection if needed
on exitFrame me
if not voidP(pFirstChar) then
-- get current character under cursor
c = getChar(me,the mouseLoc)
-- assume a valid selection
ok = FALSE
-- horizontal selection
if c[1] = pFirstChar[1] then ok = TRUE
-- vertical selection
if c[2] = pFirstChar[2] then ok = TRUE
-- diagonal selection
if abs(c[1]-pFirstChar[1]) = abs(c[2]-pFirstChar[2]) then ok = TRUE
-- if not a valid selection, change nothing
if not ok then exit
-- set a new last character in selection
pLastChar = c
-- highlight selection
drawLine(me)
end if
end
Even though highlighting the selection is little more than a detail in this game, the following handler is the most complex. There are actually many ways that you could highlight the selection. In a word search game that I have written for my site, I use vector shapes to draw an oval around the selection. This is even more involved than the following handler, which uses a line shape sprite. The "on drawLine" handler first gets the horizontal and vertical spacing of the letters in the matrix in the same way that the "on getChar" handler did. It then uses these values to determine the starting and ending screen points. The line sprite can be a horizontal, a vertical or a diagonal line. As it turns out, a diagonal line appears to be thicker when used as a background to letters. To compensate, the line is thinner when a diagonal is needed{7}. It is 65 percent of the size of a horizontal or vertical line, to be exact. Adjustments also need to be made to the start and end points of the line so that it covers the letters when it is positioned at different angles{8}. These adjustments can be determined with logic, but I used trial and error to get them just right. Finally, the line sprite is set into place. The direction of the line is set so that the proper type of line is drawn in the proper situation. Sometimes an upper left to lower right line is needed, and sometimes an upper right to lower left line is needed{9}. Every sprite in Director is defined by the rectangle of its bounding box. In the case of rectangle shapes and bitmaps, this is easy to see. However, in the case of line shapes, the bounding box rectangle does not tell the whole picture. Two lines can share exactly the same rectangle, but one can be drawn from the upper left to the lower right and the other from the upper right to the lower left. In Lingo, this difference is indicated by the lineDirection property.
-- draw the selection line
on drawLine me
-- get the width of the grid
w = charpostoloc(sprite(me.spriteNum).member,3).locH
-- get the height of the grid
h = sprite(me.spriteNum).member.fixedLineSpace
-- get the basic location of the two ends of the line
p1 = point(pFirstChar[1]*w,pFirstChar[2]*h)
p2 = point(pLastChar[1]*w,pLastChar[2]*h)
-- add location of sprite
p1 = p1 + sprite(me.spriteNum).loc
p2 = p2 + sprite(me.spriteNum).loc
-- smaller line size if at an angle{7}
if (p1.locH = p2.locH) or (p1.locV = p2.locV) then
lineSize = w
else
lineSize = .65*w
end if
-- adjust the line according to the angle{8}
if (p2.locH >= p1.locH) and (p2.locV >= p1.locV) then
p2 = p2 + point(w,h)
else if (p2.locH < p1.locH) and (p2.locV >= p1.locV) then
p1 = p1 + point(w,0)
p2 = p2 + point(0,h)
else if (p2.locH < p1.locH) and (p2.locV < p1.locV) then
p1 = p1 + point(w,h)
else if (p2.locH >= p1.locH) and (p2.locV < p1.locV) then
p1 = p1 + point(0,h)
p2 = p2 + point(w,0)
end if
-- adjust line slightly
sprite(me.spriteNum-1).rect = rect(p1,p2) - rect(3,2,3,2)
-- set line direction{9}
if ((p1.locH < p2.locH) and (p1.locV < p2.locV)) or ((p1.locH > p2.locH) and (p1.locV > p2.locV)) then
sprite(me.spriteNum-1).member.lineDirection = 0
else
sprite(me.spriteNum-1).member.lineDirection = 1
end if
-- set line size
sprite(me.spriteNum-1).lineSize = lineSize
end
When the player lifts up the mouse button, "pFirstChar" and "pLastChar" are used to determine what the current selection actually is. There is no need to recalculate the "pLastChar" again, because it was just updated by the last on exitFrame handler. The "on compileSelection" handler is used to build a string from the selection points. Then, the "on select" handler is called to determine if the word matches one in the list. If it does, then the "on grayLetters" handler is called to permanently color those letters in. Regardless, the selection line is removed from the screen, and "pFirstChar" and "pLastChar" are reset.
-- end selection
on mouseUp me
if not voidP(pFirstChar) then
-- get word from selection
text = compileSelection(me,pFirstChar,pLastChar)
-- see if it is a word in the list
if select(me,text) then
-- change color of letters in word
grayLetters(me)
end if
-- remove selection line
sprite(me.spriteNum-1).locV = -1000
pFirstChar = VOID
pLastChar = VOID
end if
end
The following on mouseUpOutside handler is used to redirect any "mouseUp" messages to the on mouseUp handler regardless of whether the cursor is still over the matrix when released. This might occur if the player stretches the selection beyond the boundaries of the matrix sprite. -- send ALL mouseUps to same place on mouseUpOutside me mouseUp(me) end The next handler, "on compileSelection" takes two selection points and compiles a string of characters from them. It does this by determining the direction of the selection as horizontal and vertical differences. Then, the "on compileSelection" handler moves from the first to the last character and adds each letter to the list.
-- take a first and last character and compile word
on compileSelection me, c1, c2
-- determine difference between start and end
dx = c2[1] - c1[1]
dy = c2[2] - c1[2]
-- determine line direction
if dx <> 0 then dx = abs(dx)/dx
if dy <> 0 then dy = abs(dy)/dy
text = ""
-- loop through characters
c = c1
repeat while TRUE
-- see if this is past the edge of the puzzle
if c[1] < 0 or c[1] > pMatrixSize-1[cc]
or c[2] < 0 or c[2] > pMatrixSize-1 then exit repeat
-- add character to text
put pMatrix[c[2]+1][c[1]+1] after text
-- see if this is the last character
if c = c2 then exit repeat
-- next character
c = c + [dx,dy]
end repeat
return text
end
The "on select" handler takes a word and determines if it is in the word list. If so, the handler removes the word from the word list, and redisplays the word list. Then, it checks to see if all words have been found. Either way, it returns TRUE only if a word from the list was found.
-- try out a player's selection
on select me, text
-- see if it is in the word list
if getOne(pWordList,text) then
-- remove from list
deleteOne pWordList, text
showWordList(me)
-- see if the game is over
if pWordList.count < 1 then
go to frame pEndGameFrame
else
return TRUE
end if
end if
end
When a word is found, the letters in the text member are changed to a different color to signify that they have been used in a word. The "on grayLetters" handler actually resembles the "on compileSelection" handler quite a bit. It uses the first and last character position, determines a direction, and then loops through the characters. Instead of compiling them into a string, it turns them all gray.
-- change letters in selection to different color
on grayLetters me
-- determine difference between start and end
dx = pLastChar[1] - pFirstChar[1]
dy = pLastChar[2] - pFirstChar[2]
-- determine line direction
if dx <> 0 then dx = abs(dx)/dx
if dy <> 0 then dy = abs(dy)/dy
-- loop through characters
c = pFirstChar
repeat while TRUE
-- change color
member("Matrix").line[c[2]+1].char[2*(c[1]+1)-1].color = rgb("999999")
-- see if the last character has been reached
if c = pLastChar then exit repeat
-- next character
c = c + [dx,dy]
end repeat
end
In addition to the sprite behavior, there is a simple frame behavior used to keep the frame looping while the game is being played. on exitFrame go to the frame end | |