The goal of this post is to create some Triple town style matching logic. Triple town if you haven’t played it is a very addictive matching game. The core concept is that you place elements on the game board. Three adjacent elements combine into a new elements. Elements combine in a progression.
In the last example I posted, I had created a grid with squares that cycled through colors: red, green, blue, yellow, magenta, cyan. This will by our progression. Tapping a square will color that square red. Three red squares will combine to form a green square, three green squares will combine to form a blue square etc.
I’ll be starting with the source code from the last post.
The first step is to retool the touch_tile() function. In the last example each touch advanced a tile through all of the colors in turn. Tapping an empty tile will color it red. Tapping other tiles will not effect.
local function touch_tile( event ) local phase = event.phase if phase == "ended" then local tile = event.target if tile.is_empty then tile.is_empty = false tile.color_index = 1 local r = color_array[ tile.color_index ].r local g = color_array[ tile.color_index ].g local b = color_array[ tile.color_index ].b tile:setFillColor( r, g, b ) end -- Matching logic logic starts here. check_tile_match( tile ) end end
When looking for matching tiles we might find any number of tiles that match. Define an array to hold the matched tiles. As we search for matching, and adjacent tiles, add them to this new array. When the search is complete examine the array to see how many matches there are.
The color_index property assigned to each represents the color displayed by the tile. This is what we will match. If two tiles have the same color_index they should display the same color. Create a variable to hold the match index while we search for a match.
local matched_array = {} -- An array to hold matched tiles local match_color_index = 0 -- Match this color index
Searching adjacent tiles
To search for adjacent tiles we need to look at tiles that to the left, top, right and bottom of the current tile. Tiles are stored in tile_arrays. This is organized into arrays for containing tiles for each row. For example you could access the third tile in the second row with tile_array[2][3]. Think about about it this way: tile_array[row][column].
Imagine starting at tile_array[2][3]. To look at the four tiles surrounding this tile you’d need to get tiles:
- tile_array[2][2] — left
- tile_array[1][3] — top
- tile_array[2][4] — right
- tile_array[3][3] — bottom
You could think of this as:
- tile_array[2][3-1] — left
- tile_array[2-1][3] — top
- tile_array[2][3+1] — right
- tile_array[2+1][3] — bottom
Think about the surrounding adjacent tiles as coordinates specified in rows and columns:
- 0, -1 — left
- -1, 0 — top
- 0, +1 — right
- +1, 0 — bottom
After checking each of the adjacent tiles, if there is a match we can begin the search again from that tile. With each match we add that tile to the matched_array.
When checking tiles at the edge of the board some results will fall outside of the array. We’ll need to check for this.
Here’s a rough sketch of the code that will run the matching system:
local function check_neighbors( tile ) -- look at adjacent tiles -- add a match to matched_array -- for each matched tile call check_neighbors( matching_tile ) end
This is called recession. Essentially the function above calls on itself. In Lua you’ll need to use a forward declaration or the function will not be able to “see itself”, and think it has not been declared.
local check_neighbors -- forward declaration function check_neighbors( tile ) -- ... code ... check_neighbors( matching_tile ) end
We also need some code to start the matching process and look at the results of the match check. Here’s the rough code
local check_neighbors -- Forward decalraitons local check_tile_match local function get_tile_frame_at_col_row( col, row ) -- that tile at col, row is inside of the array end function check_neighbors( tile ) -- Check adjacent tiles for a match check_neighbors( matched_tile ) end function check_tile_match( tile ) -- Find all matching adjacent tiles check_neighbors( tile ) -- Call recursive function to find all tiles -- look at the match results and set the colors of tiles accordingly end local function touch_tile( event ) check_tile_match( tile ) -- Call check_tile_match() with event.target end
Here’s a full listing of the code:
----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Your code here local TILE_ROWS = 6 local TILE_COLS = 6 local TILE_SIZE = 50 local TILE_MARGIN = 1 local match_color_index = 0 local tile_array = {} local color_array = {} local matched_array = {} -- An array to hold matched tiles local game_group = display.newGroup() local function Color( red, green, blue ) return {r=red, g=green, b=blue} end table.insert( color_array, Color(255, 0, 0) ) table.insert( color_array, Color(0, 255, 0) ) table.insert( color_array, Color(0, 0, 255) ) table.insert( color_array, Color(255, 255, 0) ) table.insert( color_array, Color(255, 0, 255) ) table.insert( color_array, Color(0, 255, 255) ) local check_neighbors local check_tile_match local function get_tile_frame_at_col_row( col, row ) if col >= 1 and col <= TILE_COLS and row >= 1 and row <= TILE_ROWS then return tile_array[row][col] else return false end end function check_neighbors( tile ) -- Get the row and col of this tile local row = tile.row local col = tile.col -- Define the coords of Left, Top, Right, and Bottom local ltrb_array = { {row=0, col=-1}, { row=-1, col=0 }, { row=0, col=1 }, { row=1, col=0 } } -- print("Check from tile:".. tile.row .. tile.col .. " " .. tile.alien.currentFrame ) -- Loop through left, top, right and bottom for i = 1, #ltrb_array, 1 do local check_row = row + ltrb_array[i].row local check_col = col + ltrb_array[i].col -- Check that the row and col are on the board local n_tile = get_tile_frame_at_col_row( check_col, check_row ) if n_tile then -- on board if n_tile.color_index == match_color_index then -- matches -- Check that this tile doesn't exist in matched_array local index = table.indexOf( matched_array, n_tile ) if index == nil then -- tile hasn't been found yet! print( "match at:" .. n_tile.row .. n_tile.col ) table.insert( matched_array, n_tile ) -- add to array check_neighbors( n_tile ) -- recur this function with new tile end end end end end function check_tile_match( tile ) matched_array = {tile} -- Add the first tile to the array match_color_index = tile.color_index -- Get the index to match check_neighbors( tile ) -- Start looking for matching tiles -- Time to clear the tiles, if there are more than 2 matches if #matched_array > 2 then -- If more than two tiles match for i = 2, #matched_array, 1 do -- Loop through all but the first tile local tile = matched_array[i] -- Clear all these tiles tile.color_index = 0 tile.is_empty = true tile:setFillColor( 255, 255, 255, 100 ) end local tile = matched_array[1] -- Get the first tile and local color_index = match_color_index + 1 -- advance it's color local color = color_array[ color_index ] tile.color_index = color_index tile:setFillColor( color.r, color.g, color.b ) tile.is_empty = false check_tile_match( tile ) end end ---------------------------------------------------------------------------------------- local function touch_tile( event ) local phase = event.phase if phase == "ended" then local tile = event.target if tile.is_empty then tile.is_empty = false tile.color_index = 1 local r = color_array[ tile.color_index ].r local g = color_array[ tile.color_index ].g local b = color_array[ tile.color_index ].b tile:setFillColor( r, g, b ) end -- Matching logic logic starts here. check_tile_match( tile ) end end local function Tile() local tile = display.newRect( 0, 0, TILE_SIZE, TILE_SIZE ) tile:setFillColor( 255, 255, 255, 100 ) return tile end local function make_grid() local tile_spacing = TILE_SIZE + TILE_MARGIN local offset_x = ( display.contentWidth - ( tile_spacing * TILE_COLS ) ) / 2 offset_x = TILE_SIZE / 2 - offset_x for row = 1, TILE_ROWS, 1 do local row_array = {} for col = 1, TILE_COLS, 1 do local tile = Tile() game_group:insert( tile ) tile.x = ( col * tile_spacing ) - offset_x tile.y = row * tile_spacing tile.row = row tile.col = col tile.is_empty = true -- set this tile to empty tile.color_index = 0 tile:addEventListener( "touch", touch_tile ) table.insert( row_array, tile ) end table.insert( tile_array, row_array ) end end make_grid()