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.

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

-- Your code here

-- Hide the status bar
display.setStatusBar( display.HiddenStatusBar )

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

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

local tile_array = {}
local color_array = {} 
local matched_array = {}

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


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

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


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


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

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


local function remove_tile( tile ) 
	print( "removing tile", tile )
	display.remove( tile )
end 

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 


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
		
		animated_match()
		
		-- 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
		
		-- Wait for the animation to complete then check for another match set
		timer.performWithDelay( 500, function() check_tile_match( tile ) end, 1 )
		
	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 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 *