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()