Corona – Triple Town style Matching Game Part 1

Someone recently pointed out the game Triple Town for IOS. This is a really great little time waster. Game play is fun and challenging. It’s more of a board game rather than a real time game. You can play online also, they have a Flash version: http://www.tripletown.com/

The game is played on a grid, and the basic mechanic is to match three adjacent items. Matching three items of one type replaces the last item placed with a new item and removes the others. Matching three items leave you with a new advanced item. For example starting with two Grass, adding a third Grass combines all grass into a Bush. Three Bushes combine into a Tree, three trees combine into a House etc. here’s a chart:

 

This mechanic of combining three items and trading them up is very interesting. The game has a few other elements that add more options to game play.

I haven’t yet been able to get up to the sky tower or sky castle. I can make it up past 100K in points, then the Ninja Bears, yes Ninja Bears, cause so much trouble and I get stuck. You’ll have to play the original game if you want to meet the Ninja Bears.

The idea of finding trios of matching elements on a grid made for an interesting problem to solve. It took a few tries to get right. The solution I came up with was to look at the grid square where you placed a new “piece” and examine all of the adjacent squares for a match. From here you’ll need to recursively examine all of the matching squares for another match.

Recursion is the idea of having the same block of code execute itself. In my case I had a function that looked at a grid square, then examined the adjacent grid squares for matches. In this adjacent means top, left, right, and bottom, not diagonal. In other words starting a at grid square, look for matches in the adjacent squares, then use the same function to find other matching squares in the adjacent matches.

Try it for yourself

I started a new Corona project. I used the basic project so i started with only the main.lua, config.lua, build.settings file. To keep things simple I will stick with the built in shape objects and avoid images for now. By avoiding images and sprites I can concentrate on functionality without getting hung up with non related issues.

Creating a grid

The first step is to create a grid. The grid will be made up of squares, equal height and width. Each grid square will be a rectangle shape. The first step is to define some variables describing the grid.

local TILE_ROWS = 6
local TILE_COLS = 6
local TILE_SIZE = 50
local TILE_MARGIN = 1

These variables define the number of rows and columns in the grid. Rows run horizontal while columns run vertical. The grid will be 6 by 6. Since the tiles will be square, I used single variable to set the size. The margin is the space between tiles.

Making these variables uppercase is a convention to denote that they are constants. Constants are values that do not change, they are essentially fixed while the program runs. Lua doesn’t support true constants, the uppercase is a just a reminder.

With these variables in place at the top of our script we can change the values here to modify the grid arrangement. This is much easier, and more flexible than having the dig around in code to change things.

We will want to store all a reference to each of the tiles. For this I will use an array. For this I will define an array named: tile_array. I use the extension _array as a convention. This is not required but makes a good reminder this variable contains an array. The name also implies the array contains tiles.

All of the game elements should probably be contained in a group. This will allow use to move, scale, and rotate all of the game elements together. Here again I used an extension to remind myself that this element contains a group.

local tile_array = {}

local game_group = display.newGroup()

Tile factory

To keep my code in organized blocks I like to use factory functions to create elements. In this case I need tiles so I’m going to make a function that creates tiles, and initialize them, then returns a reference to the new tile.

local function Tile()
	local tile = display.newRect( 0, 0, TILE_SIZE, TILE_SIZE )
	tile:setFillColor( 255, 255, 255, 100 )
	return tile
end

This simple function creates a new rectangle of TILE_SIZE width and height. Sets the color to a transparent white. Then returns the tile. Any other standard tile features would be added here.

Make a grid of tiles

Now that I have all of the preliminary work in place I can make the grid. The function below contains two loops. The outer loop, loops once for each row. The inner loop, loops once for each column. Imagine starting in the upper left and creating TILE_COLS number of tile squares from left to right, then moving down one row and making all of the tiles left to right, continuing this process for TILE_ROWS.

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

	print( display.contentWidth, tile_spacing * TILE_COLS, offset_x ) 

	for row = 1, TILE_ROWS, 1 do
		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
		end
	end
end

Positioning the tiles

The function above include some funny math at the top and a bit math in the middle to position the tiles. At the top I declared two local variables. Tiles spacing sets the space between each tile. This is the size of the tile plus the margin between tiles. Look inside the loop and you’ll see the x and y position of the tile is set to tile_spacing times row and col.

Tiles are positioned off their center point, and the first number we are count from is 1. I used offset_x to center the tiles inside the group. First I found the left over space by subtracting the total width of tiles from the width of the screen, then dividing by two. imagine the left over space is split on each side. Last I need to offset half the width of a tile, side the tiles are positioned on their center.

Also note that all tiles are added to the game_group.

Adding tiles to the array

The tiles need to be added to the tile_array. The array can act as a two dimensional storage space. Which may more accurately describe our tile grid. To do this we’ll use an array to store each row and store these row arrays in the tile_array.

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 

			table.insert( row_array, tile )
		end
		table.insert( tile_array, row_array )
	end
end

In this arrangement we will be able to access any tile with a row and col value. For example, the following would access the tile in the third row second column:

tile_array[2][3]

Also notice that I assigned each tile a row and col property. This way each tile will know it’s position in the grid. This will be important later one. Tapping a tile we can ask that tile it’s row and column.

Tapping the Grid

For this I’ll add a touch event to each tile. All of the touches can be handled from the same function since all tiles will know their row and col.

I’m using touch rather than tap as it provides more options.

For testing the touch function will print out the tiles row and column.

local function touch_tile( event )
	local phase = event.phase

	if phase == "ended" then
		local tile = event.target
		print( tile.row, tile.col )
	end
end

Using “ended” here creates an interaction that occurs after your finger lefts off the screen. I feel this might out, since if you were to touch down on the wrong tile you could drag your finger to the correct tile before the event was registered.

Register the event listener in make_grid().

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:addEventListener( "touch", touch_tile )

			table.insert( row_array, tile )
		end
		table.insert( tile_array, row_array )
	end
end

Cycling colors

To keep things simple I’ll stick with shapes. Clicking a tile we need to change it in some way. For now I’ll just set the color. Colors are made up of red, green and blue values. It’s a good idea to keep them in a table. To this end a factory function would work well.

-- Make a function to return a color
local function Color( red, green, blue )
	return {r=red, g=green, b=blue}
end

In OOP languages constructors begin with an uppercase letter. I’ll follow this convention here. This is a simple function that takes three values, and returns a table containing three values r, g, and b.

Now let’s make some colors. The game will need to cycle through a series of colors in order. An array is needed to keep these organized.

local color_array = {} -- An array to store colors

Then make some colors and add them to the array.

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

 

Now tap to cycle through colors. When the game begins tiles will be empty. Tiles will take on a color when they are tapped. This means that a tile will need to keep track of it’s color. A couple variables are needed. Add a pair of property variables to each tile in make_grid().

tile.is_empty = true -- set this tile to empty
tile.color_index = 0

Now modify touch_tile() to handle the color change.

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
		else 
			tile.color_index = tile.color_index + 1
		end 
		
		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 
end

This post has set a basic tile board with some interactivity. The next post will apply some Triple Town style matching logic.

Below is a complete copy of the source code from this example.

-----------------------------------------------------------------------------------------
--
-- main.lua
--
-----------------------------------------------------------------------------------------

-- Your code here

local TILE_ROWS = 6
local TILE_COLS = 6
local TILE_SIZE = 50
local TILE_MARGIN = 1

local tile_array = {}
local color_array = {} -- An array to store colors

local game_group = display.newGroup()


-- Make a function to return a color
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 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
		else 
			tile.color_index = tile.color_index + 1
		end 
		
		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 
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()

Leave a Reply

Your email address will not be published. Required fields are marked *