PDA

View Full Version : [AS3] Creating a Word Search


finaiized
07-12-2009, 02:18 AM
I was going to make a word search, but I don't know how to approach it. What would be the simplest way to approach making one? I'm thinking of using a text field, copying the word search in there, and some how separating the letters so Flash can 'read' them. Any ideas?

abeall
07-12-2009, 03:15 AM
You mean a word search like this?
http://www.groundhog.org/teachers/wordsearch.php

finaiized
07-12-2009, 03:23 AM
Yes, however I want it to be interactive: ie: you can use your mouse and select words.

abeall
07-12-2009, 04:28 AM
Interesting. Well the first question is how is the data defined? Is it computer generated or pre-designed? In other words, are you trying to come up with a system that takes a set of words and automatically creates a grid of letters with the words and fill in random letters, or have you already created that and you're just trying to add the interaction?

finaiized
07-12-2009, 04:31 AM
I already have the words- interaction is all I am getting at.

abeall
07-12-2009, 05:50 AM
Okay. So before you can add any interaction you have to think about how the data will be structured. Using a single textfield will probably not work well at all. You'll probably want to create a character button and dynamically lay them out in a grid. For the data, I would use a 2 dimensional array (array of arrays) defining a grid of all the letters:


var wordSearch:Array = [
["a", "b", "c", "d", "e", "f"],
["a", "b", "c", "d", "e", "f"],
["a", "b", "c", "d", "e", "f"],
["a", "b", "c", "d", "e", "f"],
["a", "b", "c", "d", "e", "f"],
["a", "b", "c", "d", "e", "f"]
];


That would define a 6x6 grid of letters. (Obviously that's not real data and there are no real words in it anyway.) Alternatively, you could make it at least a little random by just defining the words, then putting in a character to represent a random letter and replace it with a random letter dynamically:


var wordSearch:Array = [
["%", "%", "%", "%", "%", "%"],
["s", "%", "s", "%", "%", "%"],
["h", "%", "h", "o", "t", "%"],
["o", "%", "e", "%", "%", "%"],
["p", "w", "e", "i", "g", "h"],
["%", "%", "p", "%", "%", "%"]
];

// convert % to random character
for(var i in wordSearch) {
for (var j in wordSearch[i]) {
if (wordSearch[i][j] == "%")
wordSearch[i][j] = String.fromCharCode(97 + int(26 * Math.random())); // random a-z lowercase
}
}


Then you need to define the solved words. Again, I would expect to define them in a multi dimensional array, this time defining a start and end coordinate for each word solution:


var solutions:Array = [
[[2, 1], [2, 5]], // (2,1) to (2,5) = sheep
[[0, 1], [0, 4]], // (0,1) to (0,4) = shop
[[1, 4], [5, 4]], // (1,4) to (5,4) = weigh
]


Now you are ready to build your grid of interactive letters. This may be more complicated than you were anticipating. There may be more author time centric ways of doing it, for instance you could create a whole bunch of MovieClips/Buttons directly in Flash and lay them out as a grid, then all you have to define in the data is the solutions -- something like:


var solutions:Array = [
[mc1, mc2, mc3, mc4, mc5], // sheep
[mc6, mc7, mc8, mc9], // shop
[mc10, mc4, mc11, mc12, mc13] // weigh, re-using the 'e' in sheep
]


But that could be extremely tedious if you have a large word search or you want to do a bunch of them.

Make sense so far? :)

For the interaction you have to think about how you want it to work. Should users click a start and end character to identify a word they see? Should users click and drag from one letter to another in order to identify a word? Some other way?

finaiized
07-12-2009, 03:42 PM
Awesome, I actually understand xD

To create the movieclips, I'd use this tutorial- http://www.adobe.com/designcenter/video_workshop/html/vid0134.html
The main problem was I don't know how to to let users select words. I was planning to click and drag, but it honestly doesn't matter- as long as it works.

abeall
07-12-2009, 11:24 PM
Oh cool, so it looks like you've already gotten this far, and like your original post asked you just need to add the interaction. :)

You could approach it numerous ways, of course, but I would think a smooth drag, click, release with visual indicator would be nice. Here's how I'd probably approach it:

1. Add a mouseDown event listener to the container of all those buttons (the main timeline?) Because of "event bubbling," when you mouse down on any of the buttons that event listener will fire without having to attach it to every button. You can use event.target in the handler to determine which object is currently firing the mouseDown event. Note that if you have other graphics inside the container, clicking on them will also fire mouseDown, so you'll want your button container to have nothing but the buttons. If you give each button in your button building code a name that represents the array location, like 5_2 for wordSearch[5][2] then you can easily connect that button to the data source as I described it above.

2. Inside the mouseDown event listener, which represents the start point of the users drag, you do two things. First, you set a variable specifying the start location, say startLetter, which will be compared against the end letter to check if a solutions was found. Second, you register a mouseMouse and mouseUp event listener to the stage (attaching to stage means it will fire those events in a global manner.)

3. In the mouseMove event I would use getObjectsUnderPoint (http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/display/DisplayObjectContainer.html#getObjectsUnderPoint() )(new Point(stage.mouseX, stage.mouseY)) to see which button the user has currently dragged over. I would check to see if that button falls on a valid axis from the startLetter (meaning is it same column, same row, or same difference in column and row, ie diagonal). If it is, I would set the current button as endLetter. I would also probably use the graphics API (http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/display/Graphics.html) to draw a simple line from the startLetter to the endLetter. It might also be nice to set the affected buttons apart by setting their emphasized property to true.

4. On the mouseUp event it's time to check if a solution was found. At this point it's pretty easy: check if startLetter and endLetter match a solution in the solutions array, and also check if it matches backwards (endLetter, startLetter.) If it does, you can do just about whatever you want. You probably don't want to leave them emphasized but you might want to use the graphics API to outline the solved series of letters similar to how you would circle a letter in a real word search. I'd probably also have a list of words (dynamic textfield) and I would strike out the solved word, and Array.splice (delete) the solution from the solutions array to avoid triggering repeat solutions. One last thing to do in mouseUp is to remove the event listeners for mouseMove and mouseUp -- they're done being used until the user mouseDowns again in which case they'll be re-added.

That's how I would approach it, anyway. I've done similar sorts of interaction before and this was my basic approach and I was happy with the results, so that's all I know. :)

GL

finaiized
07-13-2009, 03:53 AM
Thank you. I've sent you a PM that asks for more information, but I really do need help laying out the information in arrays.

abeall
07-13-2009, 05:41 AM
Ok man, see if the following does something like what you are after. :)

import fl.controls.Button;
import fl.transitions.Tween;
import fl.transitions.easing.*;

// paramas
const LINE_WIDTH:int = 35;

// define word seach grid and solutions
var wordSearch:Array = [
["%", "%", "%", "%", "%", "%"],
["s", "%", "s", "%", "%", "%"],
["h", "%", "h", "i", "t", "%"],
["o", "%", "e", "%", "g", "%"],
["p", "w", "e", "i", "g", "h"],
["%", "%", "p", "%", "%", "%"],
];

var solutions:Array = [
[[2, 1], [2, 5]], // (2,1) to (2,5) = sheep
[[0, 1], [0, 4]], // (0,1) to (0,4) = shop
[[1, 4], [5, 4]], // (1,4) to (5,4) = weigh
[[2, 2], [4, 2]], // (1,4) to (5,4) = hit
[[2, 1], [5, 4]], // (1,4) to (5,4) = sigh
]

// build word search buttons
var i:int = 0, j:int = 0, posX:int = 0, posY:int = 0, buttonSize:int = stage.stageWidth / wordSearch.length;
var buttonTextFormat:TextFormat = new TextFormat("Arial,Verdana",16,0x333333,true);
while(i < wordSearch.length){
posX = 0;
j = 0;
posY = i * buttonSize;

while(j < wordSearch[0].length) {
posX = j * buttonSize;

if (wordSearch[i][j] == "%")
wordSearch[i][j] = String.fromCharCode(97 + int(26 * Math.random())); // random a-z lowercase

var b:Button = new Button();
b.setSize(buttonSize, buttonSize);
b.move(posX, posY);
b.label = wordSearch[i][j];
b.name = "btn" + j + "_" + i;
b.setStyle("textFormat",buttonTextFormat);
addChild(b);

j++;
}
i++;
}

// this graphic will be used to draw graphics around the solved words
var solvedWords:Shape = new Shape();
addChild(solvedWords);

// start and end points when searching for a solution
var startLetter:Array, endLetter:Array;
var startButton:Button, endButton:Button;

// event listeners for click and drag
addEventListener("mouseDown",mouseDown);

function mouseDown(event:MouseEvent){
stage.addEventListener("mouseMove",mouseMove);
stage.addEventListener("mouseUp",mouseUp);
startLetter = getLetterFromName(event.target.name);
startButton = event.target as Button;
endButton = startButton;
updateDrag();
}

function mouseMove(event:MouseEvent){
updateDrag();
}

function mouseUp(event:MouseEvent){
checkForSolution();
stage.removeEventListener("mouseMove",mouseMove);
stage.removeEventListener("mouseUp",mouseUp);
}

// update the dragging interaction
function updateDrag(){
// find the button currently under the mouse
var objects:Array = getObjectsUnderPoint(new Point(mouseX,mouseY));
while(objects.length && !(objects[0].parent.parent is Button))
objects.shift();
if(objects.length)
var btn:Button = objects[0].parent.parent as Button;

// if there's a button under the mouse, check to see if it's on a good axis (vertical, horizontal, diaganal
if(btn){
var letter:Array = getLetterFromName(btn.name);
if(letter[0] == startLetter[0] || letter[1] == startLetter[1]
|| Math.abs(letter[0] - startLetter[0]) == Math.abs(letter[1] - startLetter[1])){
endLetter = letter;
endButton = btn;
}
}

// draw a visual line between the start button and the end button
graphics.clear();
graphics.lineStyle(LINE_WIDTH,0x0077ff);
graphics.moveTo(startButton.x + startButton.width/2,startButton.y + startButton.height/2);
graphics.lineTo(endButton.x + endButton.width/2,endButton.y + endButton.height/2);
}

// check to see if the current dragging operation has found a solution
var fadeTween:Tween;
function checkForSolution(){
// clear temporary visual indicator of user select start/end letter
graphics.clear();

// check each solution to see if it matches what user selected
for(var i in solutions){
if((compareLetters(solutions[i][0], startLetter) && compareLetters(solutions[i][1], endLetter))
|| (compareLetters(solutions[i][0], endLetter) && compareLetters(solutions[i][1], startLetter))){
// solution found!

// draw perminant graphic around them
with(solvedWords){
graphics.lineStyle(LINE_WIDTH,0x0077ff,.25);
graphics.moveTo(startButton.x + startButton.width/2,startButton.y + startButton.height/2);
graphics.lineTo(endButton.x + endButton.width/2,endButton.y + endButton.height/2);
}

// remove the solution so that there are no repeats
solutions.splice(i,1);

break;
}
}

// if all solutions have been found, show a "YOU WIN!" graphic
if(!solutions.length){
var w:int = 400, h:int = 200;
var win:Sprite = new Sprite();
addChild(win);

// draw rounded rect with subtle vertical linear gradient fill and blue stroke
win.graphics.lineStyle(4,0x0077ff);
var mat:Matrix = new Matrix();
mat.createGradientBox(w, h, 90 * (Math.PI / 180));
win.graphics.beginGradientFill(GradientType.LINEAR ,[0xffffff,0xeeeeee],[1.00,1.00],[0,255],mat);
win.graphics.drawRoundRect(0,0,w,h,15,15);

// show center "YOU WIN!" text
var tf:TextField = new TextField();
tf.autoSize = TextFieldAutoSize.LEFT;
tf.antiAliasType = AntiAliasType.ADVANCED;
tf.defaultTextFormat = new TextFormat("Arial, Verdana",36,0x454545,true);
tf.text = "YOU WIN!";
tf.selectable = false;
win.addChild(tf);
tf.x = w/2 - tf.width/2;
tf.y = h/2 - tf.height/2;

// add a drop shadow
var dropShadow:DropShadowFilter = new DropShadowFilter(3,45,0,.35,8,8,1,3);
win.filters = [dropShadow];

// center the graphic
win.x = stage.stageWidth/2 - win.width/2;
win.y = stage.stageHeight/2 - win.height/2;

// fade the graphic in
fadeTween = new Tween(win,'alpha',None.easeNone,0.00,1.00,.5,true) ;

// remove interaction from cross word
removeEventListener("mouseDown",mouseDown);
}
}

// gets a letter array like [x,y] from a Button name like "btnX_Y"
function getLetterFromName(name:String):Array{
var newName:Array = name.split("btn")[1].split("_")
newName[0] = int(newName[0]);
newName[1] = int(newName[1]);
return newName;
}

// compare two letter arrays to see if they are the same
function compareLetters(letter1, letter2){
return letter1[0] == letter2[0] && letter1[1] == letter2[1]
}

finaiized
07-13-2009, 04:35 PM
Wow, I didn't expect you to do it all! =) All my thanks goes to you! There is only a tiny problem- when I run it, some of the buttons are below the stage, no matter how big I make it. For example, 'p' of shop is missing, and looking at your array, so is the random letter underneath.

To anyone who is going to Google this, to use the above code, all you need to do is drag a button component (Windows>Component) to the stage and delete it.

abeall
07-13-2009, 06:47 PM
This is probably because I set buttonSize:int = stage.stageWidth / wordSearch.length, wich means it will make the buttons fit evenly across the width of the stage (however many buttons are in a row), and because it makes them square if the stage height is not as tall as it is wide, they'll be off the page. You can just change buttonSize to make it fit however you want, though.

finaiized
07-13-2009, 07:21 PM
Worked perfectly. Thank you so much! Case closed!

abeall
07-13-2009, 10:26 PM
Cool, have fun. :)