TGB Learning Project (Continued)
by Tom Cassiotis · 10/12/2007 (1:56 pm) · 1 comments
Following up from my previous .plan here some documentation on the development approach that i took.
Connect3 Video
Send me a message at tom at game2 dot com and I will send you a link to the game source.
Organization:
The game logic almost exclusively resides in the class GameGrid (gameGrid.cs). It is instantiated by a empty tileset named GameTileLayer.
Game Pieces are the only other 'objects' and they have a minor set of code in the form of a behaviour in order to be able to quickly determine if they are moving.
The game is set to different states and the game state is checked on a timer currently set to fire every 150ms. Doing the same code on update seems uneccessary (I think that fires every 16ms) and causes some stuttering.
User action is handled by onMouseDown/Up at the sceneWindow2D level (game.cs) and pushed to GameGrid to be handled. The mouse down event sets the game piece to be moved (GameGrid::swapAchor) and on the mouse up if the anchor game piece and the game piece that is under the mouse are adjacent then it begins the swap and sets the game state to $waitingOnMockSwap. When the game pieces stop moving the result of the swap is considered.
Game States:

$waitingOnUser - The game is done all its work and is waiting for the user to make their next move. Mouse handling routines are only considered in this state (so you don't move Game Pieces while a previous move is still being resolved).
$waitingOnMockSwap - A swap of Game Pieces happens even if it does not result in any matches. In this game state, we will wait until all Game Pieces have stopped moving and then consider if that swap causes a match, if it does we switch to $waitingOnSwap game state otherwise we reverse the swap and set the game state to $waitingOnSwapReverse.
$waitingOnSwap - Once the Game Pieces stop moving, we remove the matched pieces and (GameGrid::clearMatchedPieces) and execute 'gravity' which makes any Game Pieces that are 'floating' to fall down. We now set the game state to $waitingOnGravity.
$waitingOnSwapReverse - The user swap did not resolve in a match and so in this game state we wait for the Game Pieces to get back to their original position and once they do we switch the game state to $waitingOnUser.
$waitingOnGravity - Gravity has been executed and we are just waiting for the pieces to come to their destination. As soon as they stop moving we start the creation of new Game Pieces one Game Piece per row at a time (so that the falling pieces are spaced). If there are still empty spots in the grid we switch states to If there are more pieces and switch to $waitingOnFillSpotsCont otherwise it changes to $waitingOnFillSpots.
$waitingOnFillSpotsCont - We ended up in this state if there are still empty spots in the grid. We create another Game Pieces for each column that is missing a spot and if by this act we have filled the grid then we switch the game state to $waitingOnFillSpots otherwise we stay in $waitingOnFillSpotsCont state so that we can create more Game Pieces.
$waitingOnFillSpots - The grid now has all its spots filled but the Game Pieces may still be falling into place. As soon as the Game Pieces stop moving we switch to $waitingOnSwap so we can evaluate if the new Game Pieces have also caused a match to happen.
Memory Representation and Game Pieces:
There are two 2-dimensional arrays that represent the board. The first array stores the Game Piece type for each spot in the grid with zero implying an empty position and the second array holding that actual Game Piece (t2dStaticSprite).
To create the Game Pieces dynamically I used the approach that the Checkers example introduced, namely having one named object per Game Piece type added in TGB Builder and then using 'cloneWithBehaviors' to make copies.
Knowing when the Game Pieces have stopped moving:
I needed to be able to wait until the Game Pieces have finished their movement before I could continue and was having quite a bit of head scratching before I figured out a clean way of doing this.
All movement is done through 'moveTo' which has a callback 'onPositionTarget' when the object has reached it's destination. I was then able to keep track which Game Pieces were moving or stationary by, assigning a 'isMoving' variable to true when 'moveTo' was executed and then set it back to false when 'onPositionTarget' was called. In the onTimer, we can then iterate through all Game Pieces and see if any of them are moving (GameGrid::anyPiecesMoving). My first attempt was to use a Class but due to a a bug with cloneWithBehaviors I had to use a Behaviour to do this (~/game/behaviors/GamePiece.cs).
Determining a match:
GameGrid::evaluateBoard does this by iterating through the memory representation of the grid checking for each grid position if there is a horizontal match, followed by a search for any vertical matches. Along the way, matched GamePieces are added to a list so that they can be destroyed all at once.
Determining if any moves are left:
GameGrid::isBoardComplete iterates through all pieces on the board and swaps (just the memory representation) the Game Piece with the right and checks for a horizontal and vertical match for both the current spot and the spot to the right. It then swaps the Game Piece with the piece below it and does the same horizontal/vertical check on those to spots.
The function does not evaluate the whole game grid for each swap, just the two spots that were swap which makes it pretty efficient. The memory representation is reset back after each evaluation.
This function is pretty heavy regardless but luckily it iis only called when the player completes a successful swap and so does not pose a performance issue.
Connect3 Video
Send me a message at tom at game2 dot com and I will send you a link to the game source.
Organization:
The game logic almost exclusively resides in the class GameGrid (gameGrid.cs). It is instantiated by a empty tileset named GameTileLayer.
Game Pieces are the only other 'objects' and they have a minor set of code in the form of a behaviour in order to be able to quickly determine if they are moving.
The game is set to different states and the game state is checked on a timer currently set to fire every 150ms. Doing the same code on update seems uneccessary (I think that fires every 16ms) and causes some stuttering.
User action is handled by onMouseDown/Up at the sceneWindow2D level (game.cs) and pushed to GameGrid to be handled. The mouse down event sets the game piece to be moved (GameGrid::swapAchor) and on the mouse up if the anchor game piece and the game piece that is under the mouse are adjacent then it begins the swap and sets the game state to $waitingOnMockSwap. When the game pieces stop moving the result of the swap is considered.
Game States:

$waitingOnUser - The game is done all its work and is waiting for the user to make their next move. Mouse handling routines are only considered in this state (so you don't move Game Pieces while a previous move is still being resolved).
$waitingOnMockSwap - A swap of Game Pieces happens even if it does not result in any matches. In this game state, we will wait until all Game Pieces have stopped moving and then consider if that swap causes a match, if it does we switch to $waitingOnSwap game state otherwise we reverse the swap and set the game state to $waitingOnSwapReverse.
$waitingOnSwap - Once the Game Pieces stop moving, we remove the matched pieces and (GameGrid::clearMatchedPieces) and execute 'gravity' which makes any Game Pieces that are 'floating' to fall down. We now set the game state to $waitingOnGravity.
$waitingOnSwapReverse - The user swap did not resolve in a match and so in this game state we wait for the Game Pieces to get back to their original position and once they do we switch the game state to $waitingOnUser.
$waitingOnGravity - Gravity has been executed and we are just waiting for the pieces to come to their destination. As soon as they stop moving we start the creation of new Game Pieces one Game Piece per row at a time (so that the falling pieces are spaced). If there are still empty spots in the grid we switch states to If there are more pieces and switch to $waitingOnFillSpotsCont otherwise it changes to $waitingOnFillSpots.
$waitingOnFillSpotsCont - We ended up in this state if there are still empty spots in the grid. We create another Game Pieces for each column that is missing a spot and if by this act we have filled the grid then we switch the game state to $waitingOnFillSpots otherwise we stay in $waitingOnFillSpotsCont state so that we can create more Game Pieces.
$waitingOnFillSpots - The grid now has all its spots filled but the Game Pieces may still be falling into place. As soon as the Game Pieces stop moving we switch to $waitingOnSwap so we can evaluate if the new Game Pieces have also caused a match to happen.
Memory Representation and Game Pieces:
There are two 2-dimensional arrays that represent the board. The first array stores the Game Piece type for each spot in the grid with zero implying an empty position and the second array holding that actual Game Piece (t2dStaticSprite).
To create the Game Pieces dynamically I used the approach that the Checkers example introduced, namely having one named object per Game Piece type added in TGB Builder and then using 'cloneWithBehaviors' to make copies.
Knowing when the Game Pieces have stopped moving:
I needed to be able to wait until the Game Pieces have finished their movement before I could continue and was having quite a bit of head scratching before I figured out a clean way of doing this.
All movement is done through 'moveTo' which has a callback 'onPositionTarget' when the object has reached it's destination. I was then able to keep track which Game Pieces were moving or stationary by, assigning a 'isMoving' variable to true when 'moveTo' was executed and then set it back to false when 'onPositionTarget' was called. In the onTimer, we can then iterate through all Game Pieces and see if any of them are moving (GameGrid::anyPiecesMoving). My first attempt was to use a Class but due to a a bug with cloneWithBehaviors I had to use a Behaviour to do this (~/game/behaviors/GamePiece.cs).
Determining a match:
GameGrid::evaluateBoard does this by iterating through the memory representation of the grid checking for each grid position if there is a horizontal match, followed by a search for any vertical matches. Along the way, matched GamePieces are added to a list so that they can be destroyed all at once.
Determining if any moves are left:
GameGrid::isBoardComplete iterates through all pieces on the board and swaps (just the memory representation) the Game Piece with the right and checks for a horizontal and vertical match for both the current spot and the spot to the right. It then swaps the Game Piece with the piece below it and does the same horizontal/vertical check on those to spots.
The function does not evaluate the whole game grid for each swap, just the two spots that were swap which makes it pretty efficient. The memory representation is reset back after each evaluation.
This function is pretty heavy regardless but luckily it iis only called when the player completes a successful swap and so does not pose a performance issue.
Robert Simmonds
I just tried to e-mail you on the above but it didn't work so i'm hoping this will get through to you.
I'm currently trying to write a small match3 type game but i'm having trouble when destroying tiles and trying to shift the new ones down. Do you have another e-mail so that I can get the link for your source code so i can have a look and see how you've done it please? It'd be very much appreciated!
Rob