Now that we have our randomized grid of tiles on the board, we need to add the touch handler to let us interact with them. Since our game mechanics are pretty simple, we will use just the
ccTouchesEnded
method as follows:
Filename: MTPlayfieldLayer.m
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // If game over, go back to the main menu on any touch if (isGameOver) { [[CCDirector sharedDirector] replaceScene:[MTMenuScene node]]; } UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInView: [touch view]]; CGPoint convLoc = [[CCDirector sharedDirector] convertToGL:location]; // If the back button was pressed, we exit if (CGRectContainsPoint([backButton boundingBox], convLoc)) { [[CCDirector sharedDirector] replaceScene:[MTMenuScene node]]; } // If we have 2 tiles face up, do not respond if ([tilesSelected count] == 2) { return; } else { // Iterate through tilesInPlay to see which tile // was touched for (MTMemoryTile *aTile in tilesInPlay) { if ([aTile containsTouchLocation:convLoc] && [aTile isFaceUp] == NO) { // Flip the tile [aTile flipTile]; // Hold the tile in a buffer array [tilesSelected addObject:aTile]; // Call the score/fail check, // if it is the second tile if ([tilesSelected count] == 2) { // We delay so player can see cards [self scheduleOnce:@selector(checkForMatch) delay:1.0]; break; } } } } }
In the touch handler, the touches are provided in an NSSet
. However, since we do not have multiple touches enabled, we can be sure we will only be getting a single touch that we care about. Why is there no multi-touch in this game? Multiple simultaneous touches would cause confusion for the player and really complicate the code to determine which tiles should be checked for a match. So by not enabling multiple touches, we save ourselves extra work, and reduce the confusion for the flow of the game.
The first section of the method checks to see if we have reached a game over state (as represented by a YES
value in the
isGameOver
variable, any touch after we have reached game over will return the player to the menu screen.
The second section of the method is detecting touches on the back button. The location
and convLoc
variables together convert the touch into a coordinate in our game screen. We use this location to check if the backButton
was touched. If it has been touched, we also exit to the menu screen, regardless of what else is going on in the game.
We then check to make sure the
tilesSelected
array doesn't have two items in it. The tilesSelected
array is with the tiles that have been flipped face up. If there are two tiles already face up, that means the match checking has not yet been resolved. In those cases, we don't want to let the user keep flipping over tiles, so we simply return without responding to the touch. This will effectively throw away the touch, so we can safely move on with our game.
If we don't have two tiles selected already, then we iterate through all of the tiles in the tilesInPlay
array and poll it to determine: a) are you being touched? and b) are you face down. If both of these are true
, we send the message to the touched tile to flip over (flipTile
), and we add the tile to the tilesSelected
array. If this was our second tile added to the tilesSelected
array, we will call the
checkForMatch
method after a delay of one second. This delay gives us two benefits: it allows the player to see the potential match they just made, and it gives plenty of time to finish iterating through the
tilesInPlay
array so we don't risk mutating the array.
Mutating an array means you tried to change it while it was being evaluated. If we skipped the delay, the checkForMatch
method would cause this mutation (and crash) because it can remove tiles from the tilesInPlay
array. Go ahead and try it yourself. Actually seeing the error messages when you know what you did wrong will help you know where to look later, when you cause a crash without knowing what you did wrong.
Since we have done quite a bit of preparation for the rest of the mechanics of the game, it might come as a surprise that the logic to check for matching tiles is very simple. Since we stored the name of the image used for each tile inside the MTMemoryTile
object, it is a matter of comparing the two and see if they are the same.
Filename: MTPlayfieldLayer.m
-(void) checkForMatch { // Get the MemoryTiles for this comparison MTMemoryTile *tileA = [tilesSelected objectAtIndex:0]; MTMemoryTile *tileB = [tilesSelected objectAtIndex:1]; // See if the two tiles matched if ([tileA.faceSpriteName isEqualToString:tileB.faceSpriteName]) { // We remove the matching tiles [self removeMemoryTile:tileA]; [self removeMemoryTile:tileB]; } else { // No match, flip the tiles back [tileA flipTile]; [tileB flipTile]; } // Remove the tiles from tilesSelected [tilesSelected removeAllObjects]; } -(void) removeMemoryTile:(MTMemoryTile*)thisTile { [thisTile removeFromParentAndCleanup:YES]; }
If you recall, in the
ccTouchesEnded
method we stored the face up tile in the tilesSelected
array. Our logic only allows there to be two objects in the tilesSelected
array, and the
checkForMatch
method is called only when there are two objects in that array. Because of those restrictions, we can safely assume that there are objects in that array at index 0
and index 1
. (We create references to them as tileA
and tileB
to make the code simpler.)
It is trivial at this point to call
isEqualToString
on the
faceSpriteName
variable of tileA
and pass it the value of the faceSpriteName variable from tileB
. If these strings are equal, we have a match. When comparing strings, you cannot use ==
operations, you must use isEqualToString:
.
When a match is found, we call the
removeMemoryTile
method that simply removes the tile passed. If we don't have a match, we send the message to each tile to flip itself back over. Since we have resolved the matching, either by making a match or by turning the tiles back over, we then remove the tiles from the
tilesSelected
array so we have an empty array to hold the next possible match.