PDA

View Full Version : Preliminary grid-based game help.


LX.Vii
12-06-2007, 09:06 PM
Hi everyone.

I'm in the process of building a grid-based game.

What I'm looking to accomplish in this thread is the answer to this: How do I implement, if, for example, you click on a colored tile, if there are any other tiles of the same color touching a side, each of those tiles is removed?

My code right now (and I'll attach the FLA as is, too):


import caurina.transitions.Tweener;

var Grid:Array = ["Green", "Red", "Blue", "Yellow", "Black", ""];
var spacing:Number = 3;
function MakeGrid():void {
for (var x:int = 0; x<9; x++) {
for (var y:int = 0; y<9; y++) {
var colorBox = new (getDefinitionByName(Grid[int(Math.random()*(Grid.length-1))]))();
colorBox.x = (x*((colorBox.width)+spacing))+50;
colorBox.y = (y*((colorBox.height)+spacing))+50;
this.addChild(colorBox);
colorBox.buttonMode = true;
colorBox.alpha = .35;
colorBox.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
colorBox.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
colorBox.addEventListener(MouseEvent.CLICK, clickHandler);
}
}
}
MakeGrid();

function overHandler(event:MouseEvent):void {
this.setChildIndex(this.getChildByName(event.targe t.name), this.numChildren-1);
Tweener.addTween(event.currentTarget, {width:60, time:.75});
Tweener.addTween(event.currentTarget, {height:60, time:.75});
Tweener.addTween(event.currentTarget, {alpha:1, time:.25});
}
function outHandler(event:MouseEvent):void {
Tweener.addTween(event.currentTarget, {width:50, time:1});
Tweener.addTween(event.currentTarget, {height:50, time:1});
Tweener.addTween(event.currentTarget, {alpha:.35, time:2});
}
function clickHandler(event:MouseEvent):void {
if (event.target.currentFrame == 1) {
event.target.gotoAndPlay("shapetween");
}
}

newblack
12-06-2007, 11:54 PM
when you create your tiles, populate a 2-dimensional Array. a tile's position in the Array is representative of its position spatially. when you handle its click event, find the tile in the 2D Array. the tiles occupying the index directly before and after it should be tested for matching colors. the remaining tiles to check are those occupying the same index as the clicked tile in the 2 Arrays neighboring the Array the clicked tile resides in.

that's the general idea- i'm happy to help with implementation if necessary.

LX.Vii
12-07-2007, 02:18 PM
when you create your tiles, populate a 2-dimensional Array. a tile's position in the Array is representative of its position spatially. when you handle its click event, find the tile in the 2D Array. the tiles occupying the index directly before and after it should be tested for matching colors. the remaining tiles to check are those occupying the same index as the clicked tile in the 2 Arrays neighboring the Array the clicked tile resides in.

Ah, that makes sense.

that's the general idea- i'm happy to help with implementation if necessary.

Oh, that's be great. I haven't had much experience using 2D arrays yet. To have a reference would be fantastic.

newblack
12-07-2007, 03:32 PM
OK- so, here's your grid creation method reformulated. One notable change I made is not using Strings in what you had named Grid- you were deriving their qualified Class from the String, which is redundant. I rewrote the loop so as to not get too dirty mixing your code with mine.

var tileClasses:Array = [ Blue, Green, Black, Yellow, Red ];
var grid:Array = new Array( 9 );

var i:int = -1;
while( ++i < 9 )
{

var column:Array = new Array( 9 );
grid[ i ] = column;

var j:int = -1;
while( ++j < 9 )
{

var randomClass:Class = tileClasses[ Math.floor( Math.random() * tileClasses.length ) ];
var colorBox:DisplayObject = addChild( new randomClass() );

//all of your positioning/event listening as was

column[ j ] = colorBox;

}

}

Next, you should probably create some helper functions for locating tiles next to the clicked tile. Above and below are easiest

function getNeighborAbove( tile:DisplayObject ) : DisplayObject
{

for each( var column:Array in grid )
{

var index:int = column.indexOf( tile );
if( index > -1 )
{
//clicked tile is top-most
if( index == 0 ) return null;
return column[ index - 1 ];
}

}

}

//Below is nearly identical
function getNeighborBelow( tile:DisplayObject ) : DisplayObject
{

for each( var column:Array in grid )
{

var index:int = column.indexOf( tile );
if( index > -1 )
{
//clicked tile is bottom-most
if( index == 8 ) return null;
return column[ index + 1 ];
}

}

}

Tiles to the left and right are a little trickier, because they reside in a different Array- pretty simple nonetheless:

function getNeighborToLeft( tile:DisplayObject ) : DisplayObject
{

for each( var column:Array in grid )
{

var index:int = column.indexOf( tile );
if( index > -1 )
{
var columnIndex:int = grid.indexOf( column );
//clicked tile is left-most, doesn't have a neighbor to the left
if( columnIndex == 0 ) return null;
return grid[ columnIndex - 1 ][ index ];
}

}
}

//And, right neighbor, once again is relatively similar
function getNeighborToRight( tile:DisplayObject ) : DisplayObject
{

for each( var column:Array in grid )
{

var index:int = column.indexOf( tile );
if( index > -1 )
{
var columnIndex:int = grid.indexOf( column );
//clicked tile is right-most, doesn't have a neighbor to the right
if( columnIndex == 8 ) return null;
return grid[ columnIndex + 1 ][ index ];
}

}
}

I don't have Flash 9, so it's not easy for me to test your file, but this should work.

There are some nice ways you could consolidate these...

Anyway, when you handle the click event-

function clickHandler(event:MouseEvent):void {
if (event.target.currentFrame == 1) {
event.target.gotoAndPlay("shapetween");
}
var tile:DisplayObject = event.target as DisplayObject;
var tileToLeft:DisplayObject = getNeighborToLeft( tile );
if( tileToLeft ) //do whatever you need to do
//etc for up/down/right
}
A 2D Array isn't the only way of handling this. You could create what's called a graph, or a multi-dimensional linked-list- each item in the graph would have pointers to left, right, above and below, so that when you handle a click event, you can directly access its neighboring tiles:
var tileToLeft:DisplayObject = clickedTile.left;
But the 2D case is simpler for in-FLA.

LX.Vii
12-07-2007, 04:16 PM
Excellent. Jeez, I didn't expect you to do all that. Thanks so much! Should be of great use in the future.

One thing: I'm getting 1170 errors on each helper function (does not return value). I determined "DisplayObject" at the end of


function getNeighborBelow( tile:DisplayObject ) : DisplayObject
{

to be the culprit, so I removed it in each function.

I went to execute the program, but only one tile appears on the stage, in the top left. My over, out, and click handlers seem to work, but upon click, the tile disappears, as it should, but it reveals another tile below it.

Any ideas?

Here's the code. I examined it and couldn't figure out the problem.


var tileClasses:Array = [ Blue, Green, Black, Yellow, Red ];
var grid:Array = new Array( 9 );
var i:int = -1;
var spacing:Number = 3;
while ( ++i < 9 ) {
var column:Array = new Array( 9 );
grid[ i ] = column;
var j:int = -1;
while ( ++j < 9 ) {
var randomClass:Class = tileClasses[ Math.floor( Math.random() * tileClasses.length ) ];
var tile:DisplayObject = addChild( new randomClass() );

//all of your positioning/event listening as was
tile.x = (x*((tile.width)+spacing))+50;
tile.y = (y*((tile.height)+spacing))+50;
this.addChild(tile);
tile.alpha = .35;
tile.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
tile.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
tile.addEventListener(MouseEvent.CLICK, clickHandler);

column[ j ] = tile;
}
}

newblack
12-07-2007, 04:30 PM
the x and the y aren't being incremented- you need to replace the x and y with i and j respectively-

tile.x = i * etc
tile.y = j * etc

as for the Error, that's my fault- at the very bottom of each method, return null. ie:

function getNeighborAbove( tile:DisplayObject ) : DisplayObject
{

for each( var column:Array in grid )
{

var index:int = column.indexOf( tile );
if( index > -1 )
{
//clicked tile is top-most
if( index == 0 ) return null;
return column[ index - 1 ];
}

}

return null;

}
sorry about that- it's good practice to keep the return type.

LX.Vii
12-07-2007, 05:01 PM
the x and the y aren't being incremented- you need to replace the x and y with i and j respectively-

tile.x = i * etc
tile.y = j * etc


lol. Boy do I feel stupid.
It's often the very simplest things that hang me up.

Thanks again, newblack!

LX.Vii
12-07-2007, 05:13 PM
Bah. My if statement doesn't seem to be correct in the clickHandler either. I thought I had to just say something like

if (tileToLeft == tile) {
tile.gotoAndPlay("shapetween");
}


Guess not. Hmm.

newblack
12-07-2007, 07:34 PM
well in your original post, what you stated was that you needed to check adjacent tiles of the same color. each tile is its own instance- that means the only thing you're seeing that is equal to tile, is tile! what you really want to do (i'm guessing) is check to see if they are of the same datatype.


if( getQualifiedClassName( tileToLeft ) == getQualifiedClassName( tile ) )
{
tile.gotoAndPlay( "shapetween" );
}

LX.Vii
12-07-2007, 07:48 PM
well in your original post, what you stated was that you needed to check adjacent tiles of the same color. each tile is its own instance- that means the only thing you're seeing that is equal to tile, is tile! what you really want to do (i'm guessing) is check to see if they are of the same datatype.


if( getQualifiedClassName( tileToLeft ) == getQualifiedClassName( tile ) )
{
tile.gotoAndPlay( "shapetween" );
}


Sorry! I should have been more clear.
That's what I meant. If they are the same datatype, then remove all from the stage (the remove method is in the "shapetween" frame of each movieclip).

Which still isn't happening. Hmm. I'll go back and examine this ...

LX.Vii
12-07-2007, 08:20 PM
Looks like what's happening is, some tiles will be removed upon click. Some won't. However, the ones that are removed, there will randomly be other tiles adjacent to the clicked one that can be removed with an additional click.

It's sort of like finding a random path through the grid by clicking all the tiles, around one tile, and finding which way the path can lead.

That make sense?

newblack
12-08-2007, 03:54 PM
it doesn't, sorry :(

can you be more specific about what you want to happen, what's not happening and what you think the bridge between those might be?

cheers

LX.Vii
12-10-2007, 03:12 PM
Indeed.

What I have right now: Grid of colored tiles, arranged randomly.

What I'd like to happen: When I click on a colored tile (we'll say a blue one here, for example), Flash will see if any tiles touching it are also blue. It will also look to see if any other tiles touching that tiles are blue, and so on. So if there is a group of two or more blue tiles touching, they will be removed from the stage.

I was thinking of possible ways around this that we haven't tried yet. Maybe using hitTestObject?

newblack
12-10-2007, 04:28 PM
ok- so what works now? do the tiles immediately adjacent get removed? i didn't realize it extended past the currently clicked tile...

sooooo, back to some theory then! to handle this as simply as possible, your tiles need a new property- markedForRemoval. MovieClips are dynamic, so we can add this propert to them... though if this project is larger than just this facet, i'd recommend writing a class.

From your tile creation code:

while( ++j < 9 )
{

var randomClass:Class = tileClasses[ Math.floor( Math.random() * tileClasses.length ) ];
var colorBox:DisplayObject = addChild( new randomClass() );
MovieClip( colorBox ).markedForRemoval = false;
//all of your positioning/event listening as was

column[ j ] = colorBox;

}
Now, you need to generalize a recursive version of your tile neighbor check method. I haven't seen this method from you, so I'll try to keep this as agnostic as possible, but still taking from your Mouse Event handler:

function clickHandler(event:MouseEvent):void {
removeTile( MovieClip( event.target ) );
}

//now, set up removeTile method to recursively find neighbors
function removeTile( tile:MovieClip ) : void
{
if( tile.markedForRemoval ) return;
tile.markedForRemoval = true;

//check each neighbor
var left:MovieClip = MovieClip( getNeighborToLeft( tile ) );
if( getQualifiedClassName( left ) == getQualifiedClassName( tile ) ) removeTile( left );
//etc etc for right/above/below

//all your removing code for tile
//most simply- removeChild( tile );
}
if you're not familiar with recursion, the idea here is that we continue calling the same method from within itself until we cannot go on any longer. if this logic is malformed, you'll create an infinite loop, so it's good to be careful. what's happening is when we handle a tile's click event, we call removeTile, passing the clicked tile. if the tile has been marked for removal, then we return, nothing else needs to be done (this is our protection against an infinite loop. otherwise, we go through and check each of its neighbors. if a neighbor is the same datatype, we check all of its neighbors- it should make sense why it can go on infinitely if we don't set markedForRemoval... a tile's left neighbor's right neighbor is the same tile (say that 10 times fast).

that should get you started!

LX.Vii
12-10-2007, 04:36 PM
Excellent. That's making sense. Thank you. I'll check this stuff out in a little bit. I've got a couple other things going on right now.

As for what works now? Well, some tiles are removed upon clicking (and only one at a time). I'm not certain why some won't work right now.

But I'll go ahead and check things out pretty soon. :)

LX.Vii
12-12-2007, 03:49 PM
Yes! This is working perfectly! :)

My next task: Filling the gaps that are created when the tiles are removed. I'm thinking I can create a gravity variable for any tiles above the gap, and then implement a hitTest to stop them in place. I'll play around with it and see what happens. And then probably come back into this thread all complainy. lol

LX.Vii
12-12-2007, 06:15 PM
I think I'm on the right path here.


var vy:Number = 0
var gravity:Number = 0.2
stage.addEventListener(Event.ENTER_FRAME, enterHandler);

function enterHandler(event:Event):void {
vy += gravity;
tile.y += vy;
if (tile.y + tile.height / 2 > stage.stageHeight) {
tile.y = stage.stageHeight - tile.height / 2;

trace("hit");
}
}


Just using the bottom of the stage as a tester to see if my gravity is working. It is.

You see I have two variables, gravity and vy (velocity on the Y axis).

That code will only work on one tile, the bottom right one.

If I could get each tile to have the vy and gravity variables applied to it, I can move on and work on filling the empty tile spaces. I figured I could use event.target or something, but it doesn't seem to work.

LX.Vii
12-12-2007, 11:00 PM
Bah. This is driving me up the wall. Time to take a break and re-visit tomorrow with a clear head. Been working on a lot of different stuff today and I think I just need to recharge. Hopefully. Ha.

LX.Vii
12-13-2007, 09:02 PM
Well, making a little more progress.
My gravity works on all tiles now (I figured out the event.target problem).

How, I need the hitTestObject to work. That I'm not sure how to accomplish. Been messing around with it for a bit and I'm stuck. I just want a tile to fall into place once one is removed and perform a hitTest on the tile below it. It's got to be similar code to

if (tile.y + tile.height / 2 > stage.stageHeight) {
tile.y = stage.stageHeight - tile.height / 2;

but with the tiles replacing the stage height, but that's not working. Wonder what I'm doing wrong.