Grid question ?

I have a 10 x 15 grid. I spawn and move display objects around the grid. There are times when two objects can occupy the same grid location. When this occurs, the display objects merge into a new object. I need to check the x/y position of each display object against all the other display objects. 

My thought is to set up a function to check the x/y position of each display object against all the other objects and use it in a Runtime event Listener. How would I set up a function to compare the positions of display objects? All display objects are stored in a table. There will never be more than 150 at one time. Thanks for any advice. 

Hi.

My thought is to set up a function to check the x/y position of each display object against all the other objects and use it in a Runtime event Listener. … There will never be more than 150 at one time.

Whether this is a good idea or not depends on how close to that 150 you’re going to go. If you never even have more than 10 objects, say, it should be fine. If only one object is ever in motion at a time, you could probably go higher still. But if in practice your grid can get pretty saturated, you’re looking at worst-case searches of 149 pairs (in the one moving object case; 150 if the object itself is iterated over during the search and ignored) or even 150 x 149 = 22,350 pairs (if everything needs to be compared).

Will the application in question be turn-based or real-time? Do conflicts only matter once the object is fully on a tile, or can they arise also while it’s transitioning?

In the simplest case (one object moving, only needs to check for a conflict once it lands on the tile), I’d probably just keep an auxiliary array, where a given entry will say what object, if any, is in a cell. Presumably you know (or can track) which column / row you’re in, so those can be flattened to an index. For example:

local NumberOfColumns = 10 -- or 15, not sure on your convention local function Index (column, row) return (row - 1) \* NumberOfColumns + column end ... local ObjectInCell = {} function OnEnterCell (object, column, row) local index = Index(column, row) local other = ObjectInCell[index] if other then local new = CombineObjects(object, other) ObjectInCell[index] = new -- put the combo object in `other`'s place else ObjectInCell[index] = object -- cell open: occupy it end end function MoveObject (object, new\_column, new\_row) local current\_column, current\_row = GetCoordinates(object) local index = Index(current\_column, current\_row) ObjectInCell[index] = nil -- leaving cell: vacate it StuffForMoving() end

If you’re still only moving one object, but also need to detect fairly general overlaps, the grid structure can still be of use. For instance, a bucket can be assigned for each cell, which will contain those objects currently overlapping it. Then you would, on each frame, do something like the following:

  • Figure out the object’s current bounding box
  • For each cell that overlaps that box, remove the object from the corresponding bucket
  • Figure out the object’s new bounding box at the end of the frame
  • For each cell that overlaps that box, add the object to the corresponding bucket

(If the motion can be large, it may be slightly more involved, but the general idea remains the same.)

The gist of this is that other objects would have also done this same thing during their own updates, so the active object can restrict itself to looking in only those buckets it overlaps, and comparing against any objects they contain. (This will inevitably find a lot of duplicates, of course, so you need a way to filter already-compared pairs.)

Anyhow, I hope that gives you some ideas.

StarCrunch, thanks. That’s great. 

This is a simple puzzle game. 

The game starts with certain balls of various colors on the grid. If you tap a ball an arrow overlay pops up. Tap left or right arrow and the ball divides to the left and right into two new balls. Balls can divide in any direction. If the starting ball is a primary color, the two new balls will be the same color. If the ball is a secondary color, it divides into its primary components(ie. purple = red and blue). As the game progresses, there will be situations where a ball occupies a space the player may be trying to divide into. If the division would result in a blue and red ball occupying the same grid location, the ball would morph into a purple ball. That’s the gist behind needing to know the location of each of the balls. 

If anyone has any more input into this question, it would be appreciated. More, specifically I’ve hit a snag as to how to incorporate what StarCrunch suggested.
I have two sseparate arrays, one which sets up the grid: grid [col][row], and one which deals with my ball display objects: spawnTable[]. I’ve been positioning my display objects using x/y coordinates. It seems I should be able to position them in a more efficient way. Basically what I’m doing now is setting down the grid and if I want to place a ball at grid [3][10], I’m using the x/y coordinates of that grid tile. When I move a ball to a new tile I’m using a transition and adding/subtracting from the x/y coordinates of ball’s original location. Any help would be great. Thanks.

Positioning them like that sounds ok.

You could do the following:

When you create your display objects, save their grid position.

[lua]

ball.row = 4, ball.col = 5

[/lua]

Loop through your grid before the game starts and set up a table for each tile.

[lua]

grid[col][row].objectIDs = {}

[/lua]

Now in your runtime listener (or perhaps just whenever a ball is moved by the player, after you’ve updated the .row and .col fields), loop through your display objects, and using their .row and .col properties, populate the relevant objectID table with the ID of each display object.

[lua]

for i = 1 , #balls, 1 do

  local ball = balls[i]

  grid[ball.row][ball.col].objectIDs = i 

end

[/lua]

Finally loop back through each tile in the grid, checking whether there is more than one objectID in the table and if so, run your code to combine the balls. After checking a tile, reset the objectsID table to empty ready for the next check.

Just off the top of my head so not tested but worth a try!

@James

I read all your posts. Here are my assumptions about your game –

  1. Whenever a ball splits, the resultant balls move one tile over. Ex: If you tap on a purple ball and choose left on the arrow overlay, a red ball and a blue ball spawn at the same time. The red ball moves one tile to the left of the tapped tile while the blue ball moves one tile to the right of the tapped tile.

  2. Balls split (and possibly merge) only when the player interacts with them. As long as the player doesn’t tap a tile on the grid, the game is in a “sleeping” state (awaiting user input).

Also, there are some special cases which I’m not sure how you’re planning to handle –

  1. What happens when a primary color ball merges with a secondary color ball? Ex: Say there are two purple balls next to each other. If the player splits one purple ball, what should happen when one of the resultant balls (say red) lands on the next purple ball tile? Purple + Red = ??? At the moment, I’ll ignore this.

  2. What happens when a ball is at the grid’s edge and it is split against (perpendicular to) that edge? For the sake of our discussion, I’ll assume that a ball cannot be split against any grid edge i.e. the arrow overlay will only allow splitting in one direction which is parallel to the grid edge. In other words, no ball can split and hop over the grid boundaries.

  3. What happens when a ball is at one of the grid’s corners? The ball will have to be split in an ‘L’ shape. Again, this will depend on how you design the game. For the sake of simplicity, we won’t address this too much in the following approach.

If the above assumptions are correct, here’s what I’d suggest –

  1. Depending on how you’re setting up your content area in config.lua, the actual grid size (in pixels) may vary across different devices. Regardless of the grid size, ensure that the width of each grid tile is always even (and never odd) while you’re setting up the grid in your composer scene. Store the grid tile’s width in a variable, say gridSize. Also, record the x and y co-ordinates of the very first grid tile (say _ startX _ and _ startY _). I’m assuming here that you’ll be using default X and Y anchor points (0.5 and 0.5 respectively).

  2. Define a two dimensional table (say _ gridTable _) to hold 10 x 15 elements. Using a loop, initialize each element of _ gridTable _ to the string value “None” i.e. gridTable[1][1] = “None”, gridTable[1][2] = “None” and so on.

  3. Define a function to spawn a pre-defined/random set of balls at the start of the game. Here, generate two random numbers for each ball to be spawned. The first random number (say _ rowNum _) represents the row and the second random number represents the column (say _ colNum _) where the ball will be placed. Needless to say, _ rowNum _ must be within the range 1-15 and _ colNum _ must be within the range 1-10. Calculate the x and y co-ordinates of the to-be-spawned ball as follows –

    posX = startX + ( ( colNum - 1 ) * gridSize ) posY = startY + ( ( rowNum - 1 ) * gridSize )

Using a custom random function, you can also decide which color the newly spawned ball must have. Store this color as well as the spawned ball in the gridTable as follows –

ballColor = randomizeColor() -- This user-defined function will return a color as a string (Ex: "Red") gridTable[rowNum][colNum] = ballColor -- Spawn a ball with the decided ball color gridTable[rowNum][colNum].ball = display.newImageRect...

Repeat this process for as many starting balls as you want, but ensure that you never reuse grid tiles where balls have already been spawned.

  1. Deduce and store the co-ordinates of the top-left vertex of the ENTIRE GRID as follows –

    – You will divide gridSize by 2 in the following logic – That’s why you ensure gridSize is always even in the beginning gridOriginX = startX - ( gridSize * 0.5 ) gridOriginY = startY - ( gridSize * 0.5 )

  2. A runtime event listener for the core game logic is unnecessary. Also, “tap” event listeners for each grid tile (150 in total) or “tap” event listeners for each spawned ball are equally unnecessary. Just define a “tap” event listener for the ENTIRE GRID. In your grid’s “tap” event listener function, have the following code –

    – Deduce the tapped position w.r.t the top-left vertex of the ENTIRE GRID diffX = ( event.x - gridOriginX ) diffY = ( event.y - gridOriginY ) – Deduce the row and column of the tapped grid tile if diffX > 0 then tappedColNum = math.ceil ( diffX / gridSize ) else tappedColNum = 1 end if diffY > 0 then tappedRowNum = math.ceil ( diffY / gridSize ) else tappedRowNum = 1 end – Check what colored ball was in the tapped tile tappedBallColor = gridTable[tappedRowNum][tappedColNum] – Insert code here to split tappedBallColor into its component colors . . . color1 = ??? color2 = ??? – Call the function that prompts the arrow overlay showArrowOverlay()

  3. In the function _ showArrowOverlay _, handle the special cases that I spoke of earlier and prompt the user with possible splitting directions. If a vertical split is possible, add a new “tap” event listener to both the up and down arrows called _ onVerticalSplit _. Similarly, if a horizontal split is possible, add a new “tap” event listener to both the left and right arrows called _ onHorizontalSplit _.

  4. The “tap” event listener function _ onVerticalSplit _ would look something like this –

    – Remove the ball which will be split gridTable[tappedRowNum][tappedColNum].ball : removeSelf() gridTable[tappedRowNum][tappedColNum].ball = nil – Clear the tapped tile gridTable[tappedRowNum][tappedColNum] = “None” – Identify the x, y co-ordinates of the tapped tile spawnX = startX + ( ( tappedColNum - 1 ) * gridSize ) spawnY = startY + ( ( tappedRowNum - 1 ) * gridSize ) – Spawn two balls at the same location spawnX, spawnY – Identify which colored ball images to load by observing color1 and color2 color1Ball = display.newImageRect… color2Ball = display.newImageRect… ---------------------------------------------------------------------------- – Deduce the fate of the first ball (splitting towards top) finalRowNum = tappedRowNum - 1 finalColNum = tappedColNum – Check what colored ball was in the top tile existingBallColor = gridTable[finalRowNum][finalColNum] finalX = spawnX finalY = spawnY - gridSize if existingBallColor == “None” then – Tile was empty – Transition color1Ball to the location finalX, finalY . . . – Upon transition completion, set color1Ball to the destination tile gridTable[finalRowNum][finalColNum] = color1 gridTable[finalRowNum][finalColNum].ball = color1Ball else – Tile was NOT empty – Insert code here to add color1 and existingBallColor . . . finalColor = ??? – Transition color1Ball to the location finalX, finalY . . . – Upon transition completion, remove existing ball and color1Ball gridTable[finalRowNum][finalColNum].ball : removeSelf() color1Ball : removeSelf() – Play a merging animation – Upon completion, spawn a ball with finalColor at location finalX, finalY gridTable[finalRowNum][finalColNum] = finalColor gridTable[finalRowNum][finalColNum].ball = display.newImageRect… end ---------------------------------------------------------------------------- – Deduce the fate of the second ball (splitting towards bottom) finalRowNum = tappedRowNum + 1 finalColNum = tappedColNum – Check what colored ball was in the bottom tile existingBallColor = gridTable[finalRowNum][finalColNum] finalX = spawnX finalY = spawnY + gridSize if existingBallColor == “None” then – Tile was empty – Transition color2Ball to the location finalX, finalY . . . – Upon transition completion, set color2Ball to the destination tile gridTable[finalRowNum][finalColNum] = color2 gridTable[finalRowNum][finalColNum].ball = color2Ball else – Tile was NOT empty – Insert code here to add color2 and existingBallColor . . . finalColor = ??? – Transition color2Ball to the location finalX, finalY . . . – Upon transition completion, remove existing ball and color2Ball gridTable[finalRowNum][finalColNum].ball : removeSelf() color2Ball : removeSelf() – Play a merging animation – Upon completion, spawn a ball with finalColor at location finalX, finalY gridTable[finalRowNum][finalColNum] = finalColor gridTable[finalRowNum][finalColNum].ball = display.newImageRect… end

Since you’ll be referencing many variables across functions, you can declare them as local to the scene you’re working in. The “tap” event listener function _ onHorizontalSplit _ will be similar except the ball splitting will be across the X axis instead of Y.

All this code is untested, so let me know how it goes.

Wow, thanks everyone for the quick and very detailed responses. I’ll be working through this stuff throughout the weekend. I’ll let you all know how it turns out. Cheers!

I ultimately chose StarCrunch’s suggestion to implement as it seemed to fit best with what I had already written. With that said, I do have some more questions. As always, any help is appreciated.

The game starts with the following function:

local function gameStart() blueBallSpawn() spawnTable[1].x = grid[3][4].x spawnTable[1].y = grid[3][4].y redBallSpawn() spawnTable[2].x = grid[3][3].x spawnTable[2].y = grid[3][3].y end gameStart()

The functions to spawn different colored balls are all virtually the same, here’s blueBallSpawn() as an example.

function blueBallSpawn() local spawns = spawn( { image = "blueball.png", objTable = spawnTable, hasBody = true, group = localGroup, myName = "blueBall", Id = "blueBall", x = xNew, y = yNew, width = 128, height = 128, anchorX = 0.5, anchorY = 0.5, } ) tracker = tracker + 1 end

Here’s the tap function which handles the tap events for balls in the middle of the grid. I’ve included only the down-arrow button for simplicity. 

local function ballTapped(event) if myButtons[1] == nil then for col = 2, 4 do for row = 2, 4 do local t = event.target xNew= t.x yNew = t.y if t.x == grid[col][row].x and t.y == grid[col][row].y then if t.Id == "blueBall" then blueBallSpawn() blueBallSpawn() elseif t.Id == "purpleBall" then blueBallSpawn() redBallSpawn() elseif t.Id == "redBall" then redBallSpawn() redBallSpawn() elseif t.Id == "yellowBall" then yellowBallSpawn() yellowBallSpawn() elseif t.Id == "orangeBall" then redBallSpawn() yellowBallSpawn() elseif t.Id == "greenBall" then yellowBallSpawn() blueBallSpawn() end t:removeSelf() t = nil local function removeButtons() myButtons[1]:removeSelf() myButtons[1] = nil end -- 'onRelease' event listener for buttons local function onBtnRelease(event) local target = event.target if target.myId == "Down" then audio.play(splitSoundFX) transition.to(spawnTable[tracker - 1],{time = 50, x = grid[col][row].x, y = grid[col][row].y - 128, onComplete = combineObj} ) transition.to(spawnTable[tracker], {time = 50, x = grid[col][row].x, y = grid[col][row].y + 128, onComplete = combineObj}) removeButtons() end return true -- indicates successful touch end myButtons[1] = widget.newButton{ labelColor = { default={0}, over={128} }, defaultFile="arrow.png", overFile="arrow.png", width=128, height=128, onRelease = onBtnRelease } myButtons[1].x = grid[col][row].x myButtons[1].y = grid[col][row].y + 128 myButtons[1].myId = "Down" end end end end end

And finally here is the code, based on StarCrunch’s suggestion, to handle the event of two balls occupying the same tile.

local function Index (column, row) return (row - 1) \* numCols + column end local ObjectInCell = {} local function combineObjects(object, other) if object.x == other.x and object.y == other.y then if object.Id == "redBall" and other.Id == "blueBall" then xNew = object.x yNew = other.y display.remove(object) object = nil display.remove(other) other = nil purpleBallSpawn() elseif object.Id == "blueBall" and other.Id == "redBall" then xNew = object.x yNew = object.y display.remove(object) object = nil display.remove(other) other = nil purpleBallSpawn() end end end local function onEnterCell (object, column, row) local index = Index(column, row) local other = ObjectInCell[index] if other then local new = combineObjects(object, other) ObjectInCell[index] = new -- put the combo object in `other`'s place else ObjectInCell[index] = object -- cell open: occupy it end end local function combineObj() for col = 1, 5, 1 do for row = 1, 5, 1 do for i = 1, #spawnTable, 1 do onEnterCell(spawnTable[i], col, row) end end end end

To start there is a red ball at grid[3][3] and a blue ball at grid[3][4]. Tapping the red ball will produce a down arrow. If the down arrow is tapped, two new red balls will spawn on top of the original. Using transitions, they will move up one tile and down one tile. The original ball is removed. When the red ball moves to the tile with the blue ball, they are combined and a purple ball spawns. Here are the problems. First, another purple ball also spawns at grid[3][3], the original position of the red ball. As well, as soon as the combined purple spawns at grid[3][4], an arrow overlay pops, even though I haven’t tapped on it. If I split the newly combined purple ball, it splits into two purple balls. Splitting those purple balls results in the expected split of red ball and blue ball. I’ve made progress, but feel like I’ve hit a snag. Any help would be appreciated. Thanks, Jim

Bump

Hi.

From looking at the code, it seems the ObjectInCell and spawnTable aren’t getting cleared out, the former when you do onBtnRelease(), since that cell is being vacated, and the latter during a combine. So there may be some “ghosts” still there, giving you false positives.

Also, the onComplete listener will be provided the transitioned object as its parameter, so you don’t really have to do the loop in combineObjects(). Also, stationary objects might end up detecting “collisions” with themselves that way, i.e. object == other.

Other stuff:

You don’t need to nil object and other in combineObjects(). They’re just parameters, and will go away on their own.

You might have some luck just making spawnTable a group. Then you get management for free when you remove the objects (though this probably matters less in light of the onComplete note), though your tracker logic would be slightly different, say as spawnTable.numChildren - 1 and spawnTable.numChildren?

Since you know which column / row the balls will land on, you could just stash the respective numbers in each ball before its transition. Then you wouldn’t have to loop over all the columns and rows in combineObjects().

If I remove the loop with #spawnTable, the combination does not happen? The onComplete listener receives the transitioned object as its parameter as you said. How do I get the parameter’s row and col, without looping?

How do I go about “clearing out” ObjectInCell  and spawnTable? Not sure what you mean here.

I figure when you combine the two balls you don’t want them still affecting the world, say when you’re looping over them. That’s what I mean by clearing them out. If you just put the balls into a group, the removeSelf()'s will do this for you.

Clearing out ObjectInCell is the same idea. In my example code I did it in MoveObject(), whereas you’d probably do it in the button click (and move the local declaration before that, if it’s earlier in the file).

Actually, if I misunderstood you and only one of the two blobs will move, then you DON’T need to clear ObjectInCell (though I presume you would still want to switch the object).

That self-collision combination shouldn’t happen, no, once the objects are removing themselves from ObjectInCell, as well.

Since you know which ways the ball will split, you can just stuff the new column / row in the objects when you do your click, like Nick does above, then read them back in combineObjects(). Then when you add a new object, just set the new row / column relative to that one (and probably rewrite it in the other ball).

You could either start in a known position and assign the row / column that way (a fixed position, or assign the position itself from the column / row) or just compute them from your position and grid, like Prathap has done.

I keep thinking of this while reading the topic.  :slight_smile: Never did beat that one.

Hi.

My thought is to set up a function to check the x/y position of each display object against all the other objects and use it in a Runtime event Listener. … There will never be more than 150 at one time.

Whether this is a good idea or not depends on how close to that 150 you’re going to go. If you never even have more than 10 objects, say, it should be fine. If only one object is ever in motion at a time, you could probably go higher still. But if in practice your grid can get pretty saturated, you’re looking at worst-case searches of 149 pairs (in the one moving object case; 150 if the object itself is iterated over during the search and ignored) or even 150 x 149 = 22,350 pairs (if everything needs to be compared).

Will the application in question be turn-based or real-time? Do conflicts only matter once the object is fully on a tile, or can they arise also while it’s transitioning?

In the simplest case (one object moving, only needs to check for a conflict once it lands on the tile), I’d probably just keep an auxiliary array, where a given entry will say what object, if any, is in a cell. Presumably you know (or can track) which column / row you’re in, so those can be flattened to an index. For example:

local NumberOfColumns = 10 -- or 15, not sure on your convention local function Index (column, row) return (row - 1) \* NumberOfColumns + column end ... local ObjectInCell = {} function OnEnterCell (object, column, row) local index = Index(column, row) local other = ObjectInCell[index] if other then local new = CombineObjects(object, other) ObjectInCell[index] = new -- put the combo object in `other`'s place else ObjectInCell[index] = object -- cell open: occupy it end end function MoveObject (object, new\_column, new\_row) local current\_column, current\_row = GetCoordinates(object) local index = Index(current\_column, current\_row) ObjectInCell[index] = nil -- leaving cell: vacate it StuffForMoving() end

If you’re still only moving one object, but also need to detect fairly general overlaps, the grid structure can still be of use. For instance, a bucket can be assigned for each cell, which will contain those objects currently overlapping it. Then you would, on each frame, do something like the following:

  • Figure out the object’s current bounding box
  • For each cell that overlaps that box, remove the object from the corresponding bucket
  • Figure out the object’s new bounding box at the end of the frame
  • For each cell that overlaps that box, add the object to the corresponding bucket

(If the motion can be large, it may be slightly more involved, but the general idea remains the same.)

The gist of this is that other objects would have also done this same thing during their own updates, so the active object can restrict itself to looking in only those buckets it overlaps, and comparing against any objects they contain. (This will inevitably find a lot of duplicates, of course, so you need a way to filter already-compared pairs.)

Anyhow, I hope that gives you some ideas.

StarCrunch, thanks. That’s great. 

This is a simple puzzle game. 

The game starts with certain balls of various colors on the grid. If you tap a ball an arrow overlay pops up. Tap left or right arrow and the ball divides to the left and right into two new balls. Balls can divide in any direction. If the starting ball is a primary color, the two new balls will be the same color. If the ball is a secondary color, it divides into its primary components(ie. purple = red and blue). As the game progresses, there will be situations where a ball occupies a space the player may be trying to divide into. If the division would result in a blue and red ball occupying the same grid location, the ball would morph into a purple ball. That’s the gist behind needing to know the location of each of the balls. 

If anyone has any more input into this question, it would be appreciated. More, specifically I’ve hit a snag as to how to incorporate what StarCrunch suggested.
I have two sseparate arrays, one which sets up the grid: grid [col][row], and one which deals with my ball display objects: spawnTable[]. I’ve been positioning my display objects using x/y coordinates. It seems I should be able to position them in a more efficient way. Basically what I’m doing now is setting down the grid and if I want to place a ball at grid [3][10], I’m using the x/y coordinates of that grid tile. When I move a ball to a new tile I’m using a transition and adding/subtracting from the x/y coordinates of ball’s original location. Any help would be great. Thanks.

Positioning them like that sounds ok.

You could do the following:

When you create your display objects, save their grid position.

[lua]

ball.row = 4, ball.col = 5

[/lua]

Loop through your grid before the game starts and set up a table for each tile.

[lua]

grid[col][row].objectIDs = {}

[/lua]

Now in your runtime listener (or perhaps just whenever a ball is moved by the player, after you’ve updated the .row and .col fields), loop through your display objects, and using their .row and .col properties, populate the relevant objectID table with the ID of each display object.

[lua]

for i = 1 , #balls, 1 do

  local ball = balls[i]

  grid[ball.row][ball.col].objectIDs = i 

end

[/lua]

Finally loop back through each tile in the grid, checking whether there is more than one objectID in the table and if so, run your code to combine the balls. After checking a tile, reset the objectsID table to empty ready for the next check.

Just off the top of my head so not tested but worth a try!

@James

I read all your posts. Here are my assumptions about your game –

  1. Whenever a ball splits, the resultant balls move one tile over. Ex: If you tap on a purple ball and choose left on the arrow overlay, a red ball and a blue ball spawn at the same time. The red ball moves one tile to the left of the tapped tile while the blue ball moves one tile to the right of the tapped tile.

  2. Balls split (and possibly merge) only when the player interacts with them. As long as the player doesn’t tap a tile on the grid, the game is in a “sleeping” state (awaiting user input).

Also, there are some special cases which I’m not sure how you’re planning to handle –

  1. What happens when a primary color ball merges with a secondary color ball? Ex: Say there are two purple balls next to each other. If the player splits one purple ball, what should happen when one of the resultant balls (say red) lands on the next purple ball tile? Purple + Red = ??? At the moment, I’ll ignore this.

  2. What happens when a ball is at the grid’s edge and it is split against (perpendicular to) that edge? For the sake of our discussion, I’ll assume that a ball cannot be split against any grid edge i.e. the arrow overlay will only allow splitting in one direction which is parallel to the grid edge. In other words, no ball can split and hop over the grid boundaries.

  3. What happens when a ball is at one of the grid’s corners? The ball will have to be split in an ‘L’ shape. Again, this will depend on how you design the game. For the sake of simplicity, we won’t address this too much in the following approach.

If the above assumptions are correct, here’s what I’d suggest –

  1. Depending on how you’re setting up your content area in config.lua, the actual grid size (in pixels) may vary across different devices. Regardless of the grid size, ensure that the width of each grid tile is always even (and never odd) while you’re setting up the grid in your composer scene. Store the grid tile’s width in a variable, say gridSize. Also, record the x and y co-ordinates of the very first grid tile (say _ startX _ and _ startY _). I’m assuming here that you’ll be using default X and Y anchor points (0.5 and 0.5 respectively).

  2. Define a two dimensional table (say _ gridTable _) to hold 10 x 15 elements. Using a loop, initialize each element of _ gridTable _ to the string value “None” i.e. gridTable[1][1] = “None”, gridTable[1][2] = “None” and so on.

  3. Define a function to spawn a pre-defined/random set of balls at the start of the game. Here, generate two random numbers for each ball to be spawned. The first random number (say _ rowNum _) represents the row and the second random number represents the column (say _ colNum _) where the ball will be placed. Needless to say, _ rowNum _ must be within the range 1-15 and _ colNum _ must be within the range 1-10. Calculate the x and y co-ordinates of the to-be-spawned ball as follows –

    posX = startX + ( ( colNum - 1 ) * gridSize ) posY = startY + ( ( rowNum - 1 ) * gridSize )

Using a custom random function, you can also decide which color the newly spawned ball must have. Store this color as well as the spawned ball in the gridTable as follows –

ballColor = randomizeColor() -- This user-defined function will return a color as a string (Ex: "Red") gridTable[rowNum][colNum] = ballColor -- Spawn a ball with the decided ball color gridTable[rowNum][colNum].ball = display.newImageRect...

Repeat this process for as many starting balls as you want, but ensure that you never reuse grid tiles where balls have already been spawned.

  1. Deduce and store the co-ordinates of the top-left vertex of the ENTIRE GRID as follows –

    – You will divide gridSize by 2 in the following logic – That’s why you ensure gridSize is always even in the beginning gridOriginX = startX - ( gridSize * 0.5 ) gridOriginY = startY - ( gridSize * 0.5 )

  2. A runtime event listener for the core game logic is unnecessary. Also, “tap” event listeners for each grid tile (150 in total) or “tap” event listeners for each spawned ball are equally unnecessary. Just define a “tap” event listener for the ENTIRE GRID. In your grid’s “tap” event listener function, have the following code –

    – Deduce the tapped position w.r.t the top-left vertex of the ENTIRE GRID diffX = ( event.x - gridOriginX ) diffY = ( event.y - gridOriginY ) – Deduce the row and column of the tapped grid tile if diffX > 0 then tappedColNum = math.ceil ( diffX / gridSize ) else tappedColNum = 1 end if diffY > 0 then tappedRowNum = math.ceil ( diffY / gridSize ) else tappedRowNum = 1 end – Check what colored ball was in the tapped tile tappedBallColor = gridTable[tappedRowNum][tappedColNum] – Insert code here to split tappedBallColor into its component colors . . . color1 = ??? color2 = ??? – Call the function that prompts the arrow overlay showArrowOverlay()

  3. In the function _ showArrowOverlay _, handle the special cases that I spoke of earlier and prompt the user with possible splitting directions. If a vertical split is possible, add a new “tap” event listener to both the up and down arrows called _ onVerticalSplit _. Similarly, if a horizontal split is possible, add a new “tap” event listener to both the left and right arrows called _ onHorizontalSplit _.

  4. The “tap” event listener function _ onVerticalSplit _ would look something like this –

    – Remove the ball which will be split gridTable[tappedRowNum][tappedColNum].ball : removeSelf() gridTable[tappedRowNum][tappedColNum].ball = nil – Clear the tapped tile gridTable[tappedRowNum][tappedColNum] = “None” – Identify the x, y co-ordinates of the tapped tile spawnX = startX + ( ( tappedColNum - 1 ) * gridSize ) spawnY = startY + ( ( tappedRowNum - 1 ) * gridSize ) – Spawn two balls at the same location spawnX, spawnY – Identify which colored ball images to load by observing color1 and color2 color1Ball = display.newImageRect… color2Ball = display.newImageRect… ---------------------------------------------------------------------------- – Deduce the fate of the first ball (splitting towards top) finalRowNum = tappedRowNum - 1 finalColNum = tappedColNum – Check what colored ball was in the top tile existingBallColor = gridTable[finalRowNum][finalColNum] finalX = spawnX finalY = spawnY - gridSize if existingBallColor == “None” then – Tile was empty – Transition color1Ball to the location finalX, finalY . . . – Upon transition completion, set color1Ball to the destination tile gridTable[finalRowNum][finalColNum] = color1 gridTable[finalRowNum][finalColNum].ball = color1Ball else – Tile was NOT empty – Insert code here to add color1 and existingBallColor . . . finalColor = ??? – Transition color1Ball to the location finalX, finalY . . . – Upon transition completion, remove existing ball and color1Ball gridTable[finalRowNum][finalColNum].ball : removeSelf() color1Ball : removeSelf() – Play a merging animation – Upon completion, spawn a ball with finalColor at location finalX, finalY gridTable[finalRowNum][finalColNum] = finalColor gridTable[finalRowNum][finalColNum].ball = display.newImageRect… end ---------------------------------------------------------------------------- – Deduce the fate of the second ball (splitting towards bottom) finalRowNum = tappedRowNum + 1 finalColNum = tappedColNum – Check what colored ball was in the bottom tile existingBallColor = gridTable[finalRowNum][finalColNum] finalX = spawnX finalY = spawnY + gridSize if existingBallColor == “None” then – Tile was empty – Transition color2Ball to the location finalX, finalY . . . – Upon transition completion, set color2Ball to the destination tile gridTable[finalRowNum][finalColNum] = color2 gridTable[finalRowNum][finalColNum].ball = color2Ball else – Tile was NOT empty – Insert code here to add color2 and existingBallColor . . . finalColor = ??? – Transition color2Ball to the location finalX, finalY . . . – Upon transition completion, remove existing ball and color2Ball gridTable[finalRowNum][finalColNum].ball : removeSelf() color2Ball : removeSelf() – Play a merging animation – Upon completion, spawn a ball with finalColor at location finalX, finalY gridTable[finalRowNum][finalColNum] = finalColor gridTable[finalRowNum][finalColNum].ball = display.newImageRect… end

Since you’ll be referencing many variables across functions, you can declare them as local to the scene you’re working in. The “tap” event listener function _ onHorizontalSplit _ will be similar except the ball splitting will be across the X axis instead of Y.

All this code is untested, so let me know how it goes.

Wow, thanks everyone for the quick and very detailed responses. I’ll be working through this stuff throughout the weekend. I’ll let you all know how it turns out. Cheers!

I ultimately chose StarCrunch’s suggestion to implement as it seemed to fit best with what I had already written. With that said, I do have some more questions. As always, any help is appreciated.

The game starts with the following function:

local function gameStart() blueBallSpawn() spawnTable[1].x = grid[3][4].x spawnTable[1].y = grid[3][4].y redBallSpawn() spawnTable[2].x = grid[3][3].x spawnTable[2].y = grid[3][3].y end gameStart()

The functions to spawn different colored balls are all virtually the same, here’s blueBallSpawn() as an example.

function blueBallSpawn() local spawns = spawn( { image = "blueball.png", objTable = spawnTable, hasBody = true, group = localGroup, myName = "blueBall", Id = "blueBall", x = xNew, y = yNew, width = 128, height = 128, anchorX = 0.5, anchorY = 0.5, } ) tracker = tracker + 1 end

Here’s the tap function which handles the tap events for balls in the middle of the grid. I’ve included only the down-arrow button for simplicity. 

local function ballTapped(event) if myButtons[1] == nil then for col = 2, 4 do for row = 2, 4 do local t = event.target xNew= t.x yNew = t.y if t.x == grid[col][row].x and t.y == grid[col][row].y then if t.Id == "blueBall" then blueBallSpawn() blueBallSpawn() elseif t.Id == "purpleBall" then blueBallSpawn() redBallSpawn() elseif t.Id == "redBall" then redBallSpawn() redBallSpawn() elseif t.Id == "yellowBall" then yellowBallSpawn() yellowBallSpawn() elseif t.Id == "orangeBall" then redBallSpawn() yellowBallSpawn() elseif t.Id == "greenBall" then yellowBallSpawn() blueBallSpawn() end t:removeSelf() t = nil local function removeButtons() myButtons[1]:removeSelf() myButtons[1] = nil end -- 'onRelease' event listener for buttons local function onBtnRelease(event) local target = event.target if target.myId == "Down" then audio.play(splitSoundFX) transition.to(spawnTable[tracker - 1],{time = 50, x = grid[col][row].x, y = grid[col][row].y - 128, onComplete = combineObj} ) transition.to(spawnTable[tracker], {time = 50, x = grid[col][row].x, y = grid[col][row].y + 128, onComplete = combineObj}) removeButtons() end return true -- indicates successful touch end myButtons[1] = widget.newButton{ labelColor = { default={0}, over={128} }, defaultFile="arrow.png", overFile="arrow.png", width=128, height=128, onRelease = onBtnRelease } myButtons[1].x = grid[col][row].x myButtons[1].y = grid[col][row].y + 128 myButtons[1].myId = "Down" end end end end end

And finally here is the code, based on StarCrunch’s suggestion, to handle the event of two balls occupying the same tile.

local function Index (column, row) return (row - 1) \* numCols + column end local ObjectInCell = {} local function combineObjects(object, other) if object.x == other.x and object.y == other.y then if object.Id == "redBall" and other.Id == "blueBall" then xNew = object.x yNew = other.y display.remove(object) object = nil display.remove(other) other = nil purpleBallSpawn() elseif object.Id == "blueBall" and other.Id == "redBall" then xNew = object.x yNew = object.y display.remove(object) object = nil display.remove(other) other = nil purpleBallSpawn() end end end local function onEnterCell (object, column, row) local index = Index(column, row) local other = ObjectInCell[index] if other then local new = combineObjects(object, other) ObjectInCell[index] = new -- put the combo object in `other`'s place else ObjectInCell[index] = object -- cell open: occupy it end end local function combineObj() for col = 1, 5, 1 do for row = 1, 5, 1 do for i = 1, #spawnTable, 1 do onEnterCell(spawnTable[i], col, row) end end end end

To start there is a red ball at grid[3][3] and a blue ball at grid[3][4]. Tapping the red ball will produce a down arrow. If the down arrow is tapped, two new red balls will spawn on top of the original. Using transitions, they will move up one tile and down one tile. The original ball is removed. When the red ball moves to the tile with the blue ball, they are combined and a purple ball spawns. Here are the problems. First, another purple ball also spawns at grid[3][3], the original position of the red ball. As well, as soon as the combined purple spawns at grid[3][4], an arrow overlay pops, even though I haven’t tapped on it. If I split the newly combined purple ball, it splits into two purple balls. Splitting those purple balls results in the expected split of red ball and blue ball. I’ve made progress, but feel like I’ve hit a snag. Any help would be appreciated. Thanks, Jim

Bump