Skip to content

18. Lessons from a simple board game – part 1

March 30, 2013

Recently, I programmed a board game which was one of the first apps I bought for the iPad, FloodIt, where you try to flood a board by connecting cells with the same color. I thought it might be interesting for relative beginners, because it is not over complex, it uses parameters, stores user selections for next time, has a multiple undo feature, reacts to simple user touches, and shows how to turn tables to and from strings. So it shows off some of the things Codea can do. And something called recursion.

And it’s a fun game. I found myself playing with it for hours as soon as it was finished.

This is how the game works. Starting with one cell at bottom left, you choose colors from the bottom to connect that cell to other cells of the same color, gradually flooding the board.

I’ve included the code here or here, so you can try it out. Don’t forget to hold down the new project button in Codea until you get the “Paste into new project” option. And if you’re feeling competitive, my average is 34 moves to solve a 22 square board (the same size as FloodIt).

I’m not going to go through every line because that would be totally boring. Instead, I’ll highlight some of the things I mentioned above, so you can pick out anything that interests you.

Just skip the rest.

Parameters

I’m using parameters both for input – to set the size of the grid, and to start the puzzle and undo moves – and also for feedback, using a number parameter to store moves played. They are very simple to use, with a little bit of practice.

The label you give to the parameter is also the name of the variable that will hold its value, so when I use the variable Size in my code, it is referring to the value of the parameter labelled “Size”.

I set up buttons to

  • Start a new puzzle, so a player can give up on a puzzle, or set a new size and start another puzzle
  • Undo previous moves (more below)
  • When you use action parameters, you need to tell Codea what function to run when the button is pressed.

    Saving user settings

    In the setup function, I look up the saved user setting (size of grid). If there is one, I change the default value of the Size parameter (yes, you can change their value using code).

    <pre>ss=readProjectData('Size') --read in user's last size selection
    

    if ss~=nil then Size=ss end –set size, if a saved value exists

    Later, in the Start function, I save the current size selection.

    saveProjectData('Size&amp;',Size) --save user size selection

    You can save as many of these as you like. Note you can only save strings or numbers, so if you want to save tables you have to “serialise” them, which just means turning them into text strings. I’ll discuss this under backup below, because I use this technique there.

    Creating 2D tables

    When you define a table, eg a={} , it is one dimensional. So how do you create a 2D table, which we will need for this game? The code from the Start function demonstrates.

    board={}--create 1D table
        for i=1,Size do --loop through columns
            --this is how you create a 2D table
            board[i]={} --as we start each new column, create an array of rows
            for j=1,Size do --choose random color for each square
                board[i][j]=tostring(math.random(1,6))
            end
        end

    So we tell Codea that each item in board is not just a single value, but a table of values. So if we have 15 columns and 15 rows, we define each of board[1] through [15] as a table, and then put 15 values into each of those tables, giving us a 15×15 table.

    Touch

    The touch options in this game are extremely simple. You press on the color you want to fill with next, and that’s it.

    So when I’m drawing, I store details of the location of each button like so

    button[i]={x=x,y=y,w=w,h=h}

    and when the user touches the screen, I check whether the touch falls within any of the colored buttons. Now I know it is a bit wasteful to calculate the same unchanging button positions 60 times a second when I could do this in setup just once, but this is version one.

    What I really wanted to point out how useful little tables can be for storing settings like these, in a clear way.that makes them easy to access later.

    Using recursion

    I have a love/hate relationship with recursion. I love its power, but I hate setting them up, and I often get in a real tangle. But if you haven’t used it, you should learn how.

    Essentially, recursion means getting a function to run itself, often many times over, and as you can imagine, it is easy to get in a tangle. But let’s start with the problem we’re trying to solve.

    Whenever the user chooses a new color A, the program needs to replace the existing color B in the bottom left hand square – and any neighboring squares with the same color B. If you think about it, it’s quite tricky to identify all squares with color B, but only those which are connected to the bottom left hand square via squares which are also colored B.

    So recursion comes to the rescue. The process goes like this

  • user selects new color A
  • we get the color of the bottom left square, B, as the color to replace
  • starting with the bottom left square, (1,1), we change its value to A, if it was B
  • ..and, if it was B, we run the same tests on its neighbours, (1,2) and (2,1) – that is, if the value is B, change to A and test all neighbours, otherwise do nothing
  • and repeat until we have nothing left to test
  • This means the tests spread outwards from the bottom left square, testing neighbours of squares that need to be changed, ie had color B. By definition, this means that every square we test is connected to the bottom left square, by squares with value B. If the square being tested is not B, we don’t test its neighbours.

    One puzzling thing may be that this seems to create a lot of duplicated effort, because when we test, say, (1,2), change it to A and then test its neighbours, one of those neighbours is (1,1), the corner square we started with. Doesn’t this create an endless loop? No – because the value of (1,1) is now A, not B, so when it gets retested, it won’t need to be changed and its neighbours won’t get retested. And although there will be quite a few of these unnecessary tests on squares we’ve already dealt with, it all happens so fast that it’s not worth writing complex code to make it more efficient.

    Have a look at the code in fillBoard and see if you can make sense of it. It really is amazingly simple, and that is the power of recursion. But it can be hard to get right.

    There is another feature you may find interesting. When neighbour squares are tested, fillBoard is given 4 parameters each time – the new color, the old color, and the x,y position of the square to be tested. But when fillBoard is called from the touch routine to start the process, only the new color is provided. This is because fillBoard can figure out the other parameters for itself – the old color by looking at (1,1), and the starting x,y is always 1,1. So at the top of fillBoard, we check if we’ve been given the old color r, and if not, it means we are just starting out, and need to set the missing parameters ourselves. So fillBoard initialises itself.

    --this is a recursive function that calls itself
    function fillBoard(c,r,x,y)
        --the first time it's called, from touched, it just passes the new color
        --so we'll make a note of the current color of bottom left
        --square, and our starting x,y, which is 1,1
        if r==nil then 
            r=board[1][1] 
            x=1 y=1 
        end
        --check if the square we are in, needs to be changed
        --if it is the first one, 1,1, the answer is always yes
        if board[x][y]==r then 
            board[x][y]=c
            --now check the neighbours around, excl diagonals
            if x>1 then fillBoard(c,r,x-1,y) end
            if x1 then fillBoard(c,r,x,y-1) end
            if y<Size then fillBoard(c,r,x,y+1) end
        end
    end 

    In the next post, I’ll tackle the undo feature (I’ve created a special class you can use in your own projects), converting tables to and from text, and more.

    From → Games

    One Comment
    1. Ken Duerden permalink

      Fantastic addition to your growing repertoire!! Not had a chance to play it yet but I have seen the video. I look forward to disecting it and learning more about this wonderful prog. Thanks for your troubles, this series is growing fast and is a fantastic resource for us newbies.

    Leave a comment