Corona – Triple Town style Matching Game score part 4

I like the idea of animating the combing of tiles. This shows the effect of game play graphically. In some situations a play will combine three tiles setting up a situation where three more tiles can be combined. This is covered in the recursive system from the last post. The game covers this but game play is greatly enhanced if the player can see some graphical representation of what is happening. The same applied to scoring points or other in game effects.

Graphically animating the cause an effect for things that could normally be invisible on the computer enhance the experience of using your apps. This is an important part of creating good user experience. Think of the animation as a little story telling a player about the points they just earned or the new power up they just received or anything that might have happened.

A good example of this can be applied to the tile matching. In the game placing a third tile combines the three tiles into the next color of tile. When this third tile appears it might appear next to another two tiles of the same color. At this point the we have another match and the computer needs to combine them again, and so on.

We have this covered. But the action applies in a single step, no matter how many tiles have been combined. In this system it is not exactly clear what has happened. Better would be to show each step animated sequentially. For example, show red tiles combining into a green, followed by three green tiles combining into a blue tile.

Animating combining tiles

Since the system described in the last post uses a recursive function call, creating this effect is easy to achieve. We only need to delay recursive call to check_tile_match(tile) the time of the animated transition.

Inside the check_tile_match(tile) function, there is a call to check_tile_match(tile). Instead of calling this directly, wrap it in a timer.perfromWithDelay(). check_tile_match(tile) requires that you include the tile you are looking for. Since this variable is local to the function we’ll to use a closure.

timer.performWithDelay( 500, function() check_tile_match( tile ) end, 1 )

Here I’ve added a function as the handler for timer.performWithDelay(). This function wraps, or encloses, check_tile_match(tile), effectively preserving the value of tile.

Adding some animated score

As long as we’re on the subject of providing information about game state and game actions, we should also provide some score information. The game currently does not have a score, then I’ll add a an animated point value that appears at the location where points are scored.

Adding the Score

Score is a big topic games. How many points should a play be worth? Great question that points to how your game progresses and how players will view escalation and game play. Rather than concentrating on the points scored I want to focus on how the score is displayed here.

The most common place to see the score is the upper left corner of the score. We’ll use that in this example. I first hid the status bar to make room for the score.

display.setStatusBar( display.HiddenStatusBar )

Create a variable to hold the actual score value.

local score = 0 -- Add a variable to hold the score

Next, create a text object to display the score.

-- Add a text object to display the score
local score_text = display.newText( score, 0, 0, native.systemFont, 16 )

Due to the way that Corona handles text we will need to set the reference point and set the position of the text object each time we change the text it displays. I created a function to handle this.

This function receives a parameter equal to the number of points just scored. It adds these to score, sets the get in the score_text to the updated value then sets the reference point. I want the text to align with the upper right corner. Normally text objects are aligned with a center reference point. After changing the text of the object I need to set the reference point with object:setReferencePoint() and then set the x and y.

When the project loads, the last line below, calls this function once. This sets the initial position of the text field.

-- A function to update the score 
local function set_score( points ) 
	score = score + points 	-- Add points to score
	score_text.text = score	-- Display the new score 
	-- These next three lines position the score based on the top left reference point
	score_text:setReferencePoint( display.TopLeftReferencePoint )
	score_text.x = 10
	score_text.y = 5
end

set_score( 0 ) -- Call set score once to position the score text correctly

Next set the score when points are scored. Inside check_tile_match(tile) find the line that sets the point score for the last play. Right after this call set_score(points) passing the points scored.

-- Calculate points 
local points = match_color_index * 100 * #matched_array
set_score( points ) -- Call set_score to add these points to the score 
show_points( points, tile.x, tile.y ) -- Display the points at the location of the scoring tile

Show points scored at the point where they are scored with motion

Now we’ll add a text field that shows the points scored. This will show up at the tile that was last played. Then animate up the screen and fade out. This is another good spot for a factory function.

First we need a function to create a text field that will show the point value scored. This will be another factory function that returns a reference to the text object created. All that happens here, is a text field is created, and the text is set to the number of points scored. Then the text field is returned. You pass what to display to this function.

It seems like this function does’t do much. It’s job is to keep us organized. Here the only thing that happens is we make the text object. Any code that would set the color or other features of this text object would go here.

-- Add a function to create score field objects
local function Score_Field( points )
	local score_text = display.newText( points, 0, 0, native.systemFont, 12 )
	return score_text
end 

Next we’ll make a factory function that creates the points and animates the text field. We’ll use a transition to animate the text and a function to remove the text when the transition is complete.

-- This function removes score fields
local function remove_score_text( score_text )
	display.remove( score_text )
end

-- this function shows points in a score field on the game board. 
local function show_points( points, x, y )
	local score_text = Score_Field( points )
	score_text.x = x
	score_text.y = y
	
	transition.to( score_text, {y=y-100, time=300, delay=200, alpha=0, onComplete=remove_score_text} )
end 

Last call this function from check_tile_match(tile) after the line that calculates the point value.

-- Calculate points 
local points = match_color_index * 100 * #matched_array
set_score( points ) -- Call set_score to add these points to the score 
show_points( points, tile.x, tile.y ) -- Display the points at the location of the scoring tile

Here’s a full listing the code that I used. This combines the code from the last example with the new code discussed here.

Continue reading Corona – Triple Town style Matching Game score part 4

Corona – Triple Town style Matching Game matches Part 3

Here’s a nice addition to the Triple Town like matching game. Add an animated transition to a match. The effect is that the tiles appear to merge into the final tile.

The effect is created by creating new tiles above the regular game tiles. These new tiles are only temporary. They are placed above the original tiles with the same colors and are animated toward the final tile. The motion uses alpha, so they fade to transparent when the transitions is complete. Each tile removes itself when the transitions are complete using the onComplete property.

The only new code added from the previous example, is a new function: animated_match(). This function makes use of the variables: matched_array, which contains all of the matched tiles, and match_color_index which contains the index of current color that was matched.

local function animated_match()
	local color = color_array[match_color_index] 
	local t = {time=500, x=matched_array[1].x, y=matched_array[1].y, alpha=0, onComplete=remove_tile}
	
	for i = 1, #matched_array, 1 do 
		local tile = Tile()
		tile:setFillColor( color.r, color.g, color.b )
		tile.x = matched_array[i].x
		tile.y = matched_array[i].y
		transition.to( tile, t )
	end 
end

Here’s a full list of the code for this example:

Continue reading Corona – Triple Town style Matching Game matches Part 3

Corona – Triple Town style Matching Game Logic Part 2

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

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.

Continue reading Corona – Triple Town style Matching Game Part 1