153. Recreating the Threes game app – part 2
Now we’ll get Threes to play properly, so you can swipe to slide the tiles until you run out of space, and we will show a score.
Game states
If you haven’t heard of game states before, think about what is shown on the screen. When you start the game, there will be
- a splash screen, then
- a menu, then
- the game itself, and when it’s over,
- the final score
Each of these is drawn differently, so you need to tell Codea which one to draw, and you can do this easily with a state variable that holds a number or string.
So you could have a variable state, and if it equals 1, then we show the menu, whereas if it equals 2, we are playing, and Codea can use if tests on state to figure out what to draw.
I did it a little differently here. In the Settings function, I defined just two states (because I’m not bothering with splash screen, menus or end of game screens) in a table
states={PLAY=1,END=2}
So when I write state=states.PLAY, it is the same as writing state=1, except that anyone reading the code can see more easily what I’m doing. That’s the only reason for doing this, to make the code readable and reduce errors.
In the draw function, I’ve included “Game Over” text that is written across the board, with this code
if state==states.END then fontSize(72) fill(0,255,0) text("Game Over\nTouch to Restart",WIDTH/2,HEIGHT/2) end
So if state == states.END, then we draw “Game Over”.
And when people talk about “finite state machines” in game programming, this is pretty much what they mean.
Scoring
This is pretty easy. We just run through the values of each tile, and look up the score in the boardValues table.
function GetScore() local score=0 for c=1,4 do for r=1,4 do score=score+boardValues[board[c][r]][3] end end return score end
..so if a tile value in board[c][r]=6, then boardValues[6][3] = 9, and we add it to the score.
..and there is a little code in DrawBoard to show it on screen, which I haven’t bothered showing here.
Moving – handling swiping
To handle user swiping, I have defined these variables in Settings
dragXY=nil moves={vec2(-1,0),vec2(1,0),vec2(0,-1),vec2(0,1)} moveText={"Left","Right","Down","Up"}
dragXY will store the x,y start position of the swipe, and I will compare this with the position at the end of the swipe, to help me decide which direction it was in.
I’ve defined the four possible moves as one tile to the left, right, down or up (in each vec2, the first number is the x movement, sideways, and the second is the y movement, vertical). You’ll see how we use these below. I also defined a text description of each move in case I want to print it out for some reason.
Here is the touch handling. See the numbered comments and explanations underneath.
function touched(t) if t.state==BEGAN then --Started dragging --[1] if state==states.END then --[2] Initialise() state=states.PLAY else --reset movement variables --[3] dragXY=vec2(t.x,t.y) end elseif t.state==ENDED and dragXY then --[4] local dx=math.abs(t.x-dragXY.x) --[5] local dy=math.abs(t.y-dragXY.y) local m=nil --[6] if dx>dy then --X movement is larger --[7] if dx<cellWidth/2 then return end --[8] if t.x>dragXY.x then m=vec2(1,0) else m=vec2(-1,0) end --[9]] else then --Y movement is larger if dy<cellHeight/2 then return end if t.y>dragXY.y then m=vec2(0,1) else m=vec2(0,-1) end end if m then MoveCells(m) end --[10] dragXY=nil end end
[1] – when you first touch the screen, the touch state will be BEGAN. If you don’t know this, you may want to read up on how basic touch works.
[2] – if the game is finished, then this touch will restart it. We initialise the board tiles again, and set the game state to play.
[3] – if we are playing the game, then the player has started to swipe. Store the x,y position in dragXY
[4] – if the touch has ended, and if we have values in dragXY, then we need to process the swipe.
[5] – calculate the absolute change in x and y position
[6] – m will store the final move, start it with nothing
[7] – if x change is larger,we’ll move left or right
[8] – check if we’ve swiped at least half a tile width. If not, it doesn’t count, exit
[9] – set m based on whether the change is positive or negative
[10] – if we have a move, make it
Moving tiles
Suppose we have swiped left. The left hand column can’t go anywhere, so we look at the second column, and if has a number, and
- if the square to the left is blank, we move the number to the left, else if
- the total of the number and the square to the left is 3, we put 3 in the cell to the left, else if
- the cell to the left has the same number, and both are 3 or larger, we add them together in the left hand cell
and that is what the MoveCells function does. Of course, if the swipe is upwards, then it has to check the rows instead of columns starting from the second row and working down. At the end, it runs CheckAvailableMoves to see if there are any valid moves left. If not, it sets state = states.Ended.
I won’t go through these two functions in detail, because there is nothing clever about them, and the code is a bit messy.
Here is the code so far.
Next, we’ll try some monte carlo simulation to figure what is the best move to make. This will be the fun part.