Tuesday, July 31, 2012

Tutorial 11 - Physics 101

   Webcomic Courtesy of Ethanol & Entropy

11.1 Dropping Things



We are constantly amazed by the capability of Codea. An example of this is the integration of the Box2D physics engine. The talented folks at Two Lives Left have made incorporating physics into your App absurdly easy.

The included Physics Lab example project provides a good overview of the techniques available. However, if you are starting out, sometimes it isn't obvious how you can integrate similar functionality into your program. This tutorial will show you which bits you need to include in your project and how you can attach a sprite to a physics object.

11.2 The Setup



In our Minesweeper game we wanted to add a bit of bling to the Menu screen. To this end we thought about dropping some mines from the top of the screen. Initially we had them bouncing off the ground but this didn't seem like a sensible thing to do with a mine so we removed the ground. We have left the code for the ground in the example below in case you need it. In your Main class, we start off by reading the sprite image into the img variable. You want to do this in setup() to ensure you only read the sprite into memory once, doing it in draw() can cause your App to crash due to running out of memory.
  
The next step is to create an instance of the PhysicsDebugDraw() class which we copied from the Physics Lab example project (and modified a bit). We will cover this class in section 11.4 below.
  
We then assign the sprite image we read to the associated spriteImage parameter in PhysicsDebugDraw() and set staticVisible to false. The only static physics object we create is the ground so setting this to false means that the ground will be invisible (but there nonetheless).
  
Finally we create the physics objects, the ground (createGround), a box (createBox), a circle (createCircle) and a random sized polygon (createRandPoly). The interesting one is the circle which we will attach our sprite to. Note that the radius of the circle is the same size (32 pixels) as half the width of the sprite (64 pixels). To make this more general you could use img.width/2 in place of the 32 (assuming your sprite looks roughly circular).



11.3 Physics Functions



We have also extracted the following physic object creation functions from the Physics Lab example project:
  • createCircle(x,y,r)
  • createBox(x,y,w,h)
  • createGround() - note you could change the dimensions of this by altering the physics.body polygon.
  • createRandPoly(x,y) - creates a random polygon at screen co-ordinates (x,y) with between 3 and 10 sides
  • cleanup() - will delete all of the physic objects that you added to the PhysicsDebugDraw rendering class. In Minesweeper we call this after creating 50 objects to ensure that we don't run out of memory. Remember that if you call this you will need to re-create any physics objects that you want to model.



11.4 The Engine Room - PhysicsDebugDraw Class



This class renders the physics objects that you have added to it. We have made two minor modifications to the class. We added the staticVisible flag to allow you to choose whether your static objects were visible or not. Note that static bodies don't collide with other static bodies and are immovable, so they are usually used to represent the ground. 

The other thing we added was the spriteImage parameter. If this is not nil then we draw the sprite image instead of a circle. And that's all there is to it. In future tutorials we will examine some of the other capabilities enabled by the physics engine.


.  




Sunday, July 29, 2012

Tutorial 10 - A Simple Dial Class


10.1 Your Personality and a Little Maths


This tutorial is based on some code that we ported from our iPhone App Personality Profile. In this App you use a dial to answer a number of questions and a profile is provided based on your selection. We used a dial because we wanted a touch friendly solution and this seemed like an intuitive approach. The App is FREE on iTunes so download it and compare the functionality of the Objective C version to our Lua implementation. The embedded video above gives you an idea of how it works but the effect is better understood when used on your iDevice.


The trick to working out how to move the dial is a mathematical one. In essence you need to convert the cartesian co-ordinates of your finger on the screen to equivalent polar co-ordinates.

It is about now that you realise that those maths lessons way back weren't completely useless and perhaps you should have paid more attention. Don't panic, all you need to know is that it is possible to convert from a point (x, y) to polar co-ordinates which consist of an angle and a distance. The angle tells you how much to rotate the dial and you can use the distance portion to work out whether a point is within a circle which is handy for detecting if a tap was on the dial.

To make life easy we created a function which does all the hard work. We have reproduced it below but note that you also  need the math.hypot() function which isn't available in the Codea version of Lua. Both of these functions (and more) are contained in the Math tab of RSLibrary.

We have made use of the new Codea dependency functionality so if you download the entire tutorial code in one file, then you also need to download RSLibrary and link it as a dependency to DialDemo. See Tutorial 9 if you want to understand dependencies better or see what is in RSLibrary. For this tute you only need the Math and Button tabs but you might as well grab the whole thing.

As an aside, a handy trick which we use in the math.polar function is to assign a default value to a parameter using "or". This works because nil evaluates to false in Lua. See the code below to understand how you can use this to overload functions.



10.2 The Main Class

   
In this instance the Main class is just there to demonstrate the Dial Class. Both Landscape and Portrait orientations are supported using the orientationChanged() function.


      

10.3 The Dial Class


The Dial Class is where the magic happens. We only have a licence to use the knob and dial images in our App, so you are going to have to draw or obtain your own version. Strictly speaking you only need a knob image (the bit that moves). The class should handle any differences in size to our sprites.


   

Friday, July 27, 2012

Tutorial 9 - Dependencies & Libraries


Webcomic Courtesy of Ethanol & Entropy


9.1 What is a Dependency and why do I want one?


The latest version (v1.4.3) of Codea introduced the concept of Dependencies. This feature is used to address the problem that over time there are certain functions or classes that you use again and again but you don't want to have to re-type these in each project. 

Prior to version 1.4.3 the answer was to copy and paste the relevant bits from one project to another. This works ok but it is a bit slow, breaks up your workflow and can lead to version problems if you forget where the master copy is located.

A better solution is to have a library which contains all of these functions and classes which you can then "include" in your projects as required. If you select the library as a dependency to your project then all of the code (outside the Main tab) within the library becomes accessible to that project.

You don't have to create a specific Library to use dependencies, any project can be selected as a dependancy, but to address the version control issue it is better to keep this code in a consistent location. We haven't worked out if it is better to have many small libraries or one big one. Our guess is that it won't make a difference as the compiler will just lift out the code it needs and a large library won't create unnecessarily bloated byte-code. 

9.2 How do you create a Library?


A Library is just another project. There are no rules or guidelines as to what a Library should look like so we are just going to do what seems sensible and allow it to evolve over time. To provide something to talk about we created the (Reefwing Software) RSLibrary which is a collection of code which we have found useful but isn't currently available within the Codea framework. 

We have structured the Library with the Main tab containing metadata (contents, version, date, etc.) and which if run, will demonstrate the main functionality of the code in the Library. Each subsequent tab contains the code we want to share grouped in what we think is a logical fashion.

Over time we suspect that people will tailor there own libraries using the best of breed from other shared code. Feel free to adopt some or any of the code in RSLibrary. We have freely borrowed from other contributors on the Codea Forum such as Two Lives Left, Vega and Bri_G.

9.3 Download RSLibrary

  1. Main.lua v1.1 - metadata and demonstration of the library functionality.
  2. Math.lua v1.0 - math.hypot(x, y), math.hypot2(x,y), math.polar(x,y,originX,originY), math.constrain(value,min,max), math.pi(), and math.round(value).
  3. Collisions.lua v1.0 - pointInCircle(pointX, pointY, centreX, centreY, radius), pointInRect(pointX, pointY, x, y, w, h).
  4. Colors v1.0 - Definitions of the 17 predefined colours from UIColor.
  5. Button v1.2 - Modified Vega mesh Button Class.
  6. RoundRect v1.0 - Modified Bri_G Rounded Rectangle Class.
  7. TextBox v1.0 - Two Lives Left TextBox Class (from Spritely example project).

9.4 How do I link a Library to my Project?


So you have created your shiny new Library, how do you make this a dependency of your project. The process is very simple.
  1. With your Project open there is a [+] tab in the top right corner.
  2. Tapping this will pop up a menu with three options:
    • Create New Class;
    • Create Blank File; and
    • Dependencies.
  3. Not surprisingly the section called Dependencies is where you select your library. Tap it to select and it will indicate the available tabs (6 for RSLibrary).
And that's it. You can remove a dependency using the same process.

9.5 What's Next?


As the idea of Libraries and Dependencies is evolving we would welcome thoughts and observations on the best structure or alternate approaches, so that we can share them here.

9.6 The Main Class

Here is a copy of the Main Class so you can see how RSLibrary is structured.
--# Main
-- RSLibrary by Reefwing Software (www.reefwing.com.au)
-- This Library contains functions and definitions which are commonly used
-- but not currently available in the Codea framework.
-- It includes code from: Two Lives Left
--                                        Vega
--                                        Bri_G

--
-- CONTENTS
--
-- Math Extensions:

--     1. math.round(value)
--     2. math.hypot(x, y)
--     3. math.hypot2(x, y)
--     4. math.polar(x, y) or math.polar(x, y, originX, originY)
--     5. math.pi()
--     6. math.constrain(value, min, max)
--
-- Collision Detection Functions:
--     1. pointInCircle(pointX, pointY, centreX, centreY, radius)
--     2. pointInRect(pointX, pointY, x, y, width, height)
--
-- Color Extensions: UIColor Predefined Colors
--     1. blackColor        10. yellowColor
--     2. darkGrayColor 11. magentaColor
--     3. lightGrayColor 12. orangeColor
--     4. whiteColor        13. purpleColor
--     5. grayColor          14. brownColor
--     6. redColor            15. clearColor
--     7. greenColor       16. lightTextColor
--     8. blueColor          17. darkTextColor
--     9. cyanColor
--
-- Classes:

--     1. Vega Modified Mesh Button Class v1.2
--     2. Two Lives Left TextBox Class v1.0
--     3. Bri_G Modified RoundRect Class v1.0
--
-- Note the Main class of the Library is just used to demonstrate the Library
-- functionality. Nothing defined here is available using dependancies.


function setup()
    -- Library Metadata
    version = 1.1
    saveProjectInfo("Description", "Reefwing Software Library v"..version)
    saveProjectInfo("Author", "Reefwing Software")
    saveProjectInfo("Date", "26th July 2012")
    saveProjectInfo("Version", version)
    print("RSLibrary v"..version.."\n")

    -- Parameters to demonstrate the RoundRect Class
    parameter("cr",0,2,1)
    iparameter("border",0,4,2)
    iparameter("x",0,WIDTH - 200,275)
    iparameter("y",0,HEIGHT - 40,350)
    iparameter("w",100,WIDTH,200)
    iparameter("h",0,HEIGHT,40)

    -- Construct the rounded rectangle
    roundRect = RoundRect(x, y, w, h, border, "test", cr)
    -- Construct a TextBox
    textBox = TextBox(WIDTH / 2 - 100, HEIGHT - 120, 200, "Enter your name!")
    showTextBox = false
end

function draw()

    -- This sets the background color to black
    background(blackColor)
    -- Update our roundRect with the latest parameter values (not required if they
    -- don't change). The larger the width and height the smaller cr needs to be
    -- otherwise you will end up with an ellipse.

    roundRect.x = x
    roundRect.y = y
    roundRect.w = w
    roundRect.h = h
    roundRect.cr = cr
    roundRect.border = border

    -- Set the rectangle fill and border colours. Text colour is also available.

    roundRect.fillColor = blueColor
    roundRect.borderColor = redColor

    -- call the roundRect draw() function
    roundRect:draw()
    -- If tap is not on the rectangle then draw the textBox
    if showTextBox then
         textBox:draw()
    end
end

function touched(touch)

-- Use this method to demonstrate the pointInRect() collision detection function
    if touch.state == BEGAN and pointInRect(touch.x, touch.y, x, y, w, h) then
         showTextBox = false
         print("Rounded Rectangle Tapped.")
    elseif touch.state == ENDED and not pointInRect(touch.x, touch.y, x, y, w, h) then
         showTextBox = true
         showKeyboard()
    end
end


-- KeyBoard handling function
-- Used to enter name if touch is not on the rounded rectangle


function keyboard(key)
    if key ~= nil then
        if string.byte(key) == 10 then
-- <RETURN> Key pressed
            print("Hello "..textBox.text)
            showTextBox = false
            hideKeyboard()
        elseif string.byte(key) ~= 44 then
-- e.g. filter out commas
            textBox:acceptKey(key)
        end
    end
end

Thursday, July 26, 2012

Tutorial 8 - A Directional Pad (dPad) Class


8.1 A Skinned Directional Pad (dPad) Class



In Interlude 9 we looked at moving an object on the screen using 4 buttons. This is such a common requirement for games that the directional pad evolved and is now a ubiquitous part of any console controller. While this is not always the best control mechanism for touch screen device it has the advantage of being intuitive and simple.

We have used the earlier moveShip program to demonstrate this new dPad class. The program includes the optional standard Codea function orientationChanged(newOrientation), to demonstrate how the dPad can be automatically positioned based on the iPad orientation (i.e. landscape or portrait).




To see the dPad in action, you can download the entire moveShip code including the dPad class or just download the individual classes as required.
  1. Main v3.1 - The main moveShip class. In setup() a new dPad is instantiated using the statement: dPad = DirectionalPad(x, y). Where (x, y) are the CENTER screen co-ordinates for the dPad. CORNER alignment is not supported in v1.0 of the DirectionalPad class.
  2. Bullet v1.0 - A simple prototype bullet class. Every time you tap on any part of the screen that isn't on the dPad a new bullet will be spawned in the current ship direction. Note that this isn't a good implementation as you can only fire one bullet at a time (or the earlier bullet will stop). This will be refined in due course.
  3. DirectionalPad v1.0 - The new dPad class. The contents of this class are described in the next section. This class requires v1.2 of the modified Vega Mesh Button class.
  4. Button v1.3 - The (updated faster) modified Vega mesh button class. The following has been added to the base class: call back functionality, pushStyle() & popStyle(), tapped status, pointInRect() function and the location vec2 has been changed to x and y points.
  5. Twinkle v1.1 - The twinkling star background class courtesy of Ipad41001.
Because we are using sprites for the dPad skin you will need to add at least one of the following images to your linked dropbox account. The dimensions of the skins vary from 200 x 200 pixels to 250 x 250 pixels to match up with the four underlying directional buttons (up, down, left and right). If you want to create you own skins, you will need to play with the dimensions to best match the button orientation. 
  1. White dPad v1.0 (dPadW200x200.png).
  2. Coloured Buttons v1.0 (dPadCB200x200.png).
  3. Black dPad v1.0 (dPadTran250x250.png).
  4. PS3 Inspired v1.0 (dPadPS250x250.png).
  5. XBox Inspired v1.0 (dPadXB250x250.png).
The first three skins have transparent sections which allow the button glow to show through when the directional buttons are pressed.

We would suggest not using the PS3 or Xbox inspired versions if you plan submitting the associated App to Apple for approval!




8.2 The Directional Pad Class Code



We will now look at the complete Directional Pad Class code. 

--# DirectionalPad
DirectionalPad = class()

-- DirectionalPad Class
-- Reefwing Software (www.reefwing.com.au)
--
-- 21 July 2012
-- Version 1.0
--
-- Requires the modified @Vega Mesh Button Class v1.2

function DirectionalPad:init(x, y)

   -- These parameters are used to customise your dPad

The dPad class uses (x, y) to define the centre location on the screen for the dPad. The width and height is a constant in this version (250 x 250 pixels). The sprite skin may be less than this depending on the design. At the moment, only CENTER alignment is available but we will update this to handle CORNER alignment in a future version. 

The four directional buttons are then defined. You can access the button parameters via the dPad class (e.g. dPad.upButton.status is valid).

Finally there are two booleans, visible which is used to determine whether to draw the dPad and tapped which is true if the last touch was on the dPad.

   self.x = x
   self.y = y
   self.width = 250
   self.height = 250
   self.alignment = CENTER
   self.upButton = Button("", x - 25, y + 35, 50, 50)
   self.downButton = Button("", x - 25, y - 85, 50, 50)
   self.leftButton = Button("", x - 80, y - 25, 50, 50)
   self.rightButton = Button("", x + 35, y - 25, 50, 50)
   self.visible = true
   self.tapped = false

end

function DirectionalPad:draw()

   -- Codea does not automatically call this method
   -- The buttons are drawn under the sprite "skin"

   if self.visible then
       self.upButton:draw()
       self.downButton:draw()
       self.leftButton:draw()
       self.rightButton:draw()

After drawing the four directional buttons we overlay the sprite skin. Change the sprite name to change the "skin".

       sprite("Dropbox:dPadLight250x250", self.x, self.y)
   end
end

function DirectionalPad:moveBy(offset)

   -- This function is used to move the position of the
   -- dPad if the iPad orientation changes. This ensures
   -- that it is always visible.

This function is not called automatically. Have a look at the main class to see how to use this capability. You need to implement the orientationChanged(newOrientation) function and then define your offset (e.g. local offset = dPad.x - (WIDTH - dPad.width/2 - 20)) and apply it using dPad: moveBy(offset).

   self.x = self.x - offset
   self.upButton.x = self.upButton.x - offset
   self.rightButton.x = self.rightButton.x - offset
   self.leftButton.x = self.leftButton.x - offset
   self.downButton.x = self.downButton.x - offset

end

function DirectionalPad:touched(touch)

   -- Codea does not automatically call this method
   -- You need to pass through the touches to the
   -- Button class.
   --
   -- The tapped boolean keeps track of whether the tap
   -- was on the dPad (true) or elsewhere (false).

   self.tapped = false

   -- Note that the pointInRect() function and the Button class in general
   -- assume a CORNER alignment but the DirectionalPad uses CENTER (because
   -- it is easier with sprites) so we need to translate between the two.

   local x = self.x - self.width/2
   local y = self.y - self.height/2

   if self.visible and pointInRect(touch.x, touch.y, x, y, self.width, self.height) then
       self.tapped = true
       self.upButton:touched(touch)
       self.downButton:touched(touch)
       self.leftButton:touched(touch)
       self.rightButton:touched(touch)
   end

end




Wednesday, July 25, 2012

Tutorial 7 - MineSweeper Part 2


7.1 Overview



In Part 1 of this tutorial we covered elements of game design. We will now get much closer to the metal and look at the key parts of the code for MineSweeper. Between tutorials we updated the functionality to include High Scores. There aren't many games which don't have this and they are one method of adding longevity to your App (a subject for a later tutorial).


7.2 Download the Code



As the code base has been updated we have provided links to the latest versions. Some of the classes haven't changed from version 1 so you could just copy those across from your current project but for completeness they are all included below. You can either download the entire MineSweeper code in one file or by class:

  1. Main.lua v1.2 - this has been updated from the previous version.
  2. Cell.lua v1.1 - this has been updated from the previous version.
  3. Button.lua v1.1 - unchanged from previous version.
  4. RoundBorder v1.1 - unchanged from previous version.
  5. ScoreScreen v1.0 - a new class to display your High Scores.
  6. SplashScreen v1.1 - unchanged from previous version.
  7. Fader v1.1 - unchanged from previous version.
  8. TextBox v1.0 - a new class, used to enter the high score player's name.
  9. Twinkle v1.1 - unchanged from previous version.
  10. IconImages v1.1 - unchanged from previous version.

7.3 How it Works


7.3.1 The Main Class


The best place to start is usually the beginning so we will look at the Main class first. The code is fairly well commented but we will provide additional commentary at key points.


--# Main

-- MineSweeper
-- Reefwing Software (reefwing.com.au)
--
-- Version 1.2
--
-- A reprisal of the classic MineSweeper game, written entirely on the iPad.
-- Game icons were built using Spritely, the pixel editor included with Codea.
--
-- Version 1.2 adds high score functionality.
--
-- This demonstration game was built as part of a series of Tutorials on Codea
-- and programming with Lua for the iPad. These tutorials may be found at
-- www.codeatuts.blogspot.com.au
--
-- To make import of code into Codea easier, each class is available as a separate file
-- in our dropbox repository. This will also make it easier to reuse classes in
-- your own projects.

-- Define supported iPad orientations
-- This way we don't forget to handle orientation changes if required.
-- "ANY" is the default assignment. This version of MineSweeper only handles landscape
-- orientation. The next tutorial will demonstrate how to handle changing orientations.

Note that supportedOrientations is set before the setup() function. It is best to do this to ensure that no drawing gets done before this is assigned.

supportedOrientations(LANDSCAPE_ANY)

function setup()

   version = 1.2

   saveProjectInfo("Description", "Codea version of Minesweeper")
   saveProjectInfo("Author", "Reefwing Software")
   saveProjectInfo("Date", "1st July 2012")
   saveProjectInfo("Version", version)
   saveProjectInfo("Comments", "High Score added. FPS code streamlined.")

   print("Minesweeper v"..version.."\n")

   -- Define the game colour palette

   whiteColour = color(255, 255, 255)
   blackColour = color(0, 0, 0)
   redColour = color(243, 157, 33, 255)
   blueColour = color(0, 188, 255, 255)
   greenColour = color(45, 226, 23, 255)

   -- Keep an eye on your Frames Per Second (FPS)

   FPS = 0
   watch("FPS")

   -- keep track of your Game State, Tap State, and Game Difficulty
   -- using Finite State Machines.
   --
   -- gameState is the overall state of the game.
   --
   -- tapState is used when the game is running. It toggles the tap function between
   -- revealing a cell and flagging it as a possible mine. The button text of flagButton
   -- indicates the current state.
   --
   -- gameDifficulty allows us to setup a new game with the correct parameters when the
   -- newGameButton is tapped.

You don't have to define each state using variables, you could just use the numbers directly but doing it this way is easier to remember and makes your code much easier to read and maintain. Lua doesn't have enums so this is one way to provide similar functionality. Another option would be to use a table.

   stateSplash = 0
   stateMenu = 1
   stateRun = 2
   stateWon = 3
   stateLost = 4
   stateScore = 5

   gameState = stateSplash

   stateReveal = 5
   stateFlag = 6

   tapState = stateReveal

   stateEasy = 7
   stateMedium = 8
   stateHard = 9

   gameDifficulty = stateEasy

   -- initialise the game variables and grid matrix

   score = 0
   numberOfCells = 0
   numberOfMines = 0
   gridWidth = 0
   gridHeight = 0

   cellsRevealed = 0
   watch("cellsRevealed")

   cellsLeft = 0
   watch("cellsLeft")

   gameTime = 0
   watch("gameTime")
   gameTimerOn = false

The grid table contains all the data for the mine field. It is the real work horse for the game but we share the load a bit by creating a cell class. The grid then becomes a table of cell "objects".

   grid = {}

   -- initialise the cell icon images
   -- These images were drawn using Spritely.

   ii = IconImages()

   newCellImage = ii:getNewCell()
   emptyCellImage = ii.getEmptyCell()
   mineCellImage = ii.getMineCell()
   flagCellImage = ii.getFlagCell()

   iconSize = newCellImage.width

   -- create the 4 menu buttons, they wont be visible until you draw them.
   -- Note that 50 pixels is the minimum height for the default font size

   local mButtonSize = vec2(180, 50)
   local mLocX = WIDTH/2 - mButtonSize.x/2
   local mLocY = HEIGHT/2 + 20

   easyButton = Button("Easy", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   easyButton.action = function() easyButtonPressed() end

   mLocY = mLocY - 80
   mediumButton = Button("Medium", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   mediumButton.action = function() mediumButtonPressed() end

   mLocY = mLocY - 80
   hardButton = Button("Hard", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   hardButton.action = function() hardButtonPressed() end

   mLocY = mLocY - 160
   scoreButton = Button("High Score", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   scoreButton.action = function() scoreButtonPressed() end

   menuBorder = RoundBorder(10, 10, WIDTH - 10, HEIGHT - 10, 1, blueColour, blackColour)

   -- create the run screen buttons

   mLocX = WIDTH - mButtonSize.x/2 - 25
   mLocY = HEIGHT - 195
   mButtonSize = vec2(100, 50)

   flagButton = Button("Show", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   flagButton.action = function() flagButtonPressed() end

   mLocY = 110 + mButtonSize.y*2
   newGameButton = Button("New", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   newGameButton.action = function() newGameButtonPressed() end

   mLocY = 110
   menuButton = Button("Menu", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   menuButton.action = function() menuButtonPressed() end

   -- create the splash screen

   splashScreen = SplashScreen("Minesweeper", 3)

   -- Create the twinkling star background

   twinkleBackground = Twinkle(50)

   -- Load previous High Score data if available
   -- High scores are stored for easy, medium and hard games.

We initially set highScoreSaved to true as this flag is set false when we want to save a new high score.

   playerName = "Player 1"
   highScoreSaved = true

   -- Load High Score data into the three high score data tables.

   loadHighScoreData()

   -- Create the text box used to enter the highscore name

   textBox = TextBox(WIDTH / 2 - 100, HEIGHT - 120, 200, playerName)

   -- Create the High Score Screen

   highScoreScreen = ScoreScreen()

end

-- Draw routines

function draw()

   -- The main drawing function, called 60 times per second if possible.
   -- This sets a black background color

   background(blackColour)

   -- Calculate and display FPS and track game time if game running.

We have moved to displaying instantaneous FPS as the code used is a bit shorter. The downside is the displayed FPS changes every frame. DeltaTime is the time elapsed since the last frame was drawn. It is a number provided by Codea.

   if gameTimerOn then
       gameTime = gameTime + DeltaTime
   end
   FPS = math.round(1/DeltaTime)

   -- Draw appropriate screen based on gameState

This is where the Finite State Machine magic happens. The pattern should be familiar from earlier tutorials. You can change what gets drawn to the screen each frame by just changing the gameState. An alternative to using a string of if-then-elseif-end blocks is to use a table. Yes tables can be used for just about anything and everything in Lua. We will explore this technique in the next tutorial.

   if gameState == stateSplash then
       splashScreen: draw()
   elseif gameState == stateMenu then
       drawMenu()
   elseif gameState == stateRun then
       twinkleBackground: draw()
       drawGrid()
       drawGameButtons()
       drawCellsLeftDisplay()
   elseif gameState == stateWon then
       twinkleBackground: draw()
       drawGrid()
       drawGameButtons()
       drawCellsLeftDisplay()
       text("Game Won!", WIDTH/2, 60)
       if highScore() and not highScoreSaved then
           showKeyboard()
           textBox:draw()
       end
   elseif gameState == stateLost then
       twinkleBackground: draw()
       drawGrid()
       drawGameButtons()
       drawCellsLeftDisplay()
       text("Game Over!", WIDTH/2, 60)
   elseif gameState == stateScore then
       highScoreScreen: draw()
   end

end

function drawGameButtons()

   -- These are the buttons visible on the game run screen

   flagButton:draw()
   newGameButton:draw()
   menuButton:draw()

end

function drawCellsLeftDisplay()

   -- Draw the number of cells left to be revealed using the
   -- funky LCD font. This is a more accurate representation
   -- of progress than mines flagged.

   pushStyle()

   font("DB LCD Temp")
   fontSize(64)
   local w, h = textSize(cellsLeft)
   fill(whiteColour)
   text(cellsLeft, w + 50, HEIGHT - 100)

   text(math.round(gameTime), WIDTH - 150, HEIGHT - 100)

   popStyle()

end

function drawMenu()

   -- Draw the Game Menu Buttons

   menuBorder: draw()

   font("Arial-BoldMT")
   fill(whiteColour)
   fontSize(20)
   text("Select Game Difficulty", WIDTH/2, HEIGHT/2 + 150)

   fill(blueColour)
   fontSize(72)
   text("Minesweeper", WIDTH/2, HEIGHT/2 + 220)

   easyButton:draw()
   mediumButton:draw()
   hardButton:draw()
   scoreButton:draw()

end

function drawGrid()

   -- Iterate through the grid matrix and draw each cell

   for i = 1, gridWidth do
       for j = 1, gridHeight do
           grid[i][j]: draw()
       end
   end

end

-- High Score Functions

function highScore()

   -- Check if a new high score has been achieved for the
   -- current game difficulty state. A high score is saved
   -- for each difficulty level.

When a game is won, this function is used to work out whether this is a new high score for the current difficulty level. We use a table to contain the data for each level. There are lots of different ways that you can approach this. The table method is fairly simple and easy to read and write to disk (as described below). We only use two fields in each table, index 1 contains the player's name and index 2 the score. 

This function will return true if the current score is higher than the saved score (e.g. ehsData[2] for easy difficulty], or if no score has previously been saved (i.e. the table is empty).

Initially we only checked to see if the table was nil to return true, but you can get the situation where the table is initialised but empty. To ensure we cover off this case, we check the length of the high score table using the #function (e.g. #ehsData == 0).

   if gameDifficulty == stateEasy then
       if ehsData == nil or #ehsData == 0 or gameTime <= ehsData[2] then
           return true
       end
   elseif gameDifficulty == stateMedium then
       if mhsData == nil or #mhsData == 0 or gameTime <= mhsData[2] then
           return true
       end
   elseif gameDifficulty == stateHHard then
       if hhsData == nil or #hhsData == 0 or gameTime <= hhsData[2] then
           return true
       end
   end
   return false

end

function loadHighScoreData()

This function loads previously saved high score data (if it exists) into the relevant table for use later. We have used a technique described by @John at Two Lives Left. 

The readLocalData() and saveLocalData() functions allow you to read and save a string against a key, which is another string. To load the player name and score from the combined saved string and allocate to our table we use a very powerful function called loadstring().

To understand how this works we will step through the code for reading the easy high score data. The medium and hard routines use exactly the same methodology.

  1. Step 1: ehsData = {} - Create or empty a table if it has already been created, which will hold the high score data.
  2. Step 2: if readLocalData("easyHighScore") ~= nil then - Check if any high score data has been saved previously. If not an empty table will be returned.
  3. Step 3: If high score data is available then easyHighScoreString = loadstring(readLocalData("easyHighScore")). This needs some explanation. Starting in the deepest nested function, readLocalData(key) will return a string which has previously been saved against key ("easyHighScore" in this case). The string returned will look something like "return {"Player 1", 1000}". We will demonstrate how to save a string like this when we get to the saveHighScore() function. The loadstring(mString) function will return a function which executes whatever is contained in mString, and we assign this to easyHighScoreString.
  4. Step 4: ehsData = easyHighScoreString() - Using the created easyHighScoreString() function we load up the player name and score into our table. The easyHighScoreString() function looks like return {"Player 1", 1000}. So this returns a table with two fields which is assigned to ehsData.
  5. Step 5: playerName = ehsData[1] - It is now a trivial matter to access the player name (ehsData[1]) and score (ehsData[2]).
  6. Step 6: print("Easy High Score -> Name: "..playerName.." Time: "..ehsData[2]) - The last line of code was just used to assist with debugging. We left it in here to help illustrate the workings, you can remove from your production code.
   ehsData = {}
   if readLocalData("easyHighScore") ~= nil then
       easyHighScoreString = loadstring(readLocalData("easyHighScore"))
       ehsData = easyHighScoreString()
       playerName = ehsData[1]
       print("Easy High Score -> Name: "..playerName.." Time: "..ehsData[2])
   end

   mhsData = {}
   if readLocalData("mediumHighScore") ~= nil then
       mediumHighScoreString = loadstring(readLocalData("mediumHighScore"))
       mhsData = mediumHighScoreString()
       playerName = mhsData[1]
       print("Medium High Score -> Name: "..playerName.." Time: "..mhsData[2])
   end

   hhsData = {}
   if readLocalData("hardHighScore") ~= nil then
       hardHighScoreString = loadstring(readLocalData("hardHighScore"))
       hhsData = hardHighScoreString()
       playerName = hhsData[1]
       print("Hard High Score -> Name: "..playerName.." Time: "..hhsData[2])
   end

end

function saveHighScore(d)

   -- Build the high score data into a string which is saved
   -- using saveLocalData(key, value).
   --
   -- n = playerName
   -- t = gameTime
   -- d = os.date() [current date] not used in this version

We get the player name from whatever was entered into the text box which appears following a winning game.

   playerName = textBox.text
   print("New High Score by: "..playerName)

For the loadstring() trick to work when we loadHighScoreData(), the saved string needs to be in the following format: "return {"player name", score}. hsDataString creates this string. Note the backslash quotes to include double quotes in the string. saveLocalData(key, savedString) will save the savedString against the key (which is just another string).

   local hsDataString = string.format("return {\"%s\", %d}", playerName, gameTime)

   if gameDifficulty == stateEasy then
       saveLocalData("easyHighScore", hsDataString)
   elseif gameDifficulty == stateMedium then
       saveLocalData("mediumHighScore", hsDataString)
   elseif gameDifficulty == stateHard then
       saveLocalData("hardHighScore", hsDataString)
   end

Once we have saved the high score we can hide the keyboard and set the highScoreSaved flag (which is used in draw() to determine whether to show the keyboard when a game is won).

   hideKeyboard()
   highScoreSaved = true

end

-- Count Neighbouring Mine Functions
--
-- We will discuss closure functions in a separate tutorial, but
-- for now to understand what is going on in the count neighbouring mine
-- functions you need to know that when a function is enclosed in
-- another function, it has full access to local variables from the
-- enclosing function. In this example, inNeighbourCells() increments the local
-- variable mineNum in countMines().

function inNeighbourCells(startX, endX, startY, endY, closure)
   for i = math.max(startX, 1), math.min(endX, gridWidth) do
       for j = math.max(startY, 1), math.min(endY, gridHeight) do
           closure(i, j)
       end
   end
end

function countMines(index)

   local mineNum = 0

   inNeighbourCells(index.x - 1, index.x + 1, index.y - 1, index.y + 1,
       function(x, y) if grid[x][y].mine then mineNum = mineNum + 1 end
       end)

   return mineNum
end

-- Grid Creation Function

function createGrid()

   local baseX = WIDTH/2 - (iconSize * gridWidth) / 2
   local y = HEIGHT/2 - (iconSize * gridHeight) / 2
   local x = baseX

   -- Create the grid using nested tables.
   -- It operates as a two dimensional array (or matrix)

The grid is in effect a two dimensional array of cell "objects", with a dimension of gridWidth x gridHeight. The Cell is defined with its index in the grid [i, j], whether it contains a mine (initially false), and its position on the screen (x, y).

   for i = 1, gridWidth do
       grid[i] = {}     -- create a new row
       for j = 1, gridHeight do
           grid[i][j] = Cell(i, j, false, x, y)
           grid[i][j].action = function() handleCellTouch(grid[i][j].index) end
           x = x + iconSize
       end
       x = baseX
       y = y + iconSize
   end

   -- Seed the mines at random locations on the grid

   for i = 1, numberOfMines do
       local mineX, mineY
       repeat
           mineX = math.random(1, gridWidth)
           mineY = math.random(1, gridHeight)
       until not grid[mineX][mineY].mine == true   -- we dont want to duplicate mine location
       grid[mineX][mineY].mine = true
   end

   -- Count the neighbouring mines for each cell and save in the cell.
   --
   -- You could alternatively calculate this each time a cell is revealed
   -- to shorten loading times, but for simplicity we will do it here.

   for i = 1, gridWidth do
       for j = 1, gridHeight do
           grid[i][j].neighbourMines = countMines(grid[i][j].index)
       end
   end

end

function resetGrid()

   -- When starting a new game this function will
   -- reset the table

   for i = 1, gridWidth do
       for j = 1, gridHeight do
           grid[i][j] = nil
       end
   end

   grid = {}

end

-- KeyBoard handling function
-- Used to enter name if High Score achieved

function keyboard(key)

   if key ~= nil then
       if string.byte(key) == 10 then         -- <RETURN> Key pressed
           saveHighScore(os.date())
       elseif string.byte(key) ~= 44 then -- filter out commas
           textBox:acceptKey(key)
       end
   end

end

-- Touch & Button Handling Functions

-- In Game Buttons

function flagButtonPressed()

   -- Toggle tapState every time this button is pressed

   if tapState == stateReveal then
       tapState = stateFlag
       flagButton.text = "Flag"
   else
       tapState = stateReveal
       flagButton.text = "Show"
   end

end

function newGameButtonPressed()

If the user forgets to hit the <return> key we still want to save the high score if a game was just won.

   if not highScoreSaved then
       saveHighScore(os.date())
   end

   resetGrid()
   gameTimerOn = false

   if gameDifficulty == stateEasy then
       easyButtonPressed()
   elseif gameDifficulty == stateMedium then
       mediumButtonPressed()
   elseif gameDifficulty == stateHard then
       hardButtonPressed()
   end

end

function menuButtonPressed()

If the user forgets to hit the <return> key we still want to save the high score if a game was just won.

   if not highScoreSaved then
       saveHighScore(os.date())
   end

   gameState = stateMenu
   gameTimerOn = false

end

-- Menu Buttons and associated functions.

function resetGameParameters()

   -- Regardless of the game difficulty selected, these parameters
   -- need to be reset when a new game is started.

   cellsLeft = numberOfCells
   cellsRevealed = 0
   gameTime = 0

   -- Make sure that the Flag / Show cell button is in the default state.

   tapState = stateReveal
   flagButton.text = "Show"

   -- Create the cell grid and seed with mines then run game

   createGrid()
   gameState = stateRun

end

function easyButtonPressed()

   -- Initialise the parameters which determine difficulty
   -- Namely: number of mines and the grid size

   gameDifficulty = stateEasy

   numberOfMines = 10
   gridWidth = 8
   gridHeight = 8
   numberOfCells = gridWidth * gridHeight
   resetGameParameters()

end

function mediumButtonPressed()

   -- Initialise the parameters which determine difficulty
   -- Namely: number of mines and the grid size

   gameDifficulty = stateMedium

   numberOfMines = 15
   gridWidth = 12
   gridHeight = 12
   numberOfCells = gridWidth * gridHeight
   resetGameParameters()

end

function hardButtonPressed()

   -- Initialise the parameters which determine difficulty
   -- Namely: number of mines and the grid size

   gameDifficulty = stateHard

   numberOfMines = 40
   gridWidth = 16
   gridHeight = 16
   numberOfCells = gridWidth * gridHeight
   resetGameParameters()

end

function scoreButtonPressed()

   -- High Score button tapped in the menu screen

   loadHighScoreData()
   gameState = stateScore

end

-- Handle Touches

function touched(touch)

   -- It is important to explicitly state when to handle touches.
   --
   -- If you don't do this based on gameState then for example, when in the
   -- game run state your menu button handling functions will still get called
   -- even if the buttons are not being drawn.

   if gameState == stateMenu then
       easyButton:touched(touch)
       mediumButton:touched(touch)
       hardButton:touched(touch)
       scoreButton:touched(touch)
   elseif gameState == stateRun then
       for i = 1, gridWidth do
           for j = 1, gridHeight do
               grid[i][j]:touched(touch)
           end
       end
       menuButton:touched(touch)
       newGameButton:touched(touch)
       flagButton:touched(touch)
   elseif gameState == stateLost or gameState == stateWon then
       menuButton:touched(touch)
       newGameButton:touched(touch)
       flagButton:touched(touch)
   elseif gameState == stateScore then
       highScoreScreen:touched(touch)
   end

end

function revealCell(index)

   -- If neighbourMines = 0 for the touched cell then
   -- we reveal the neighbour cells as well using a
   -- recursive call to this function.

   grid[index.x][index.y].revealed = true
   cellsRevealed = cellsRevealed + 1
   cellsLeft = numberOfCells - cellsRevealed - numberOfMines

   if grid[index.x][index.y].neighbourMines == 0 then
       inNeighbourCells(index.x - 1, index.x + 1, index.y - 1, index.y + 1, function(x, y)
           if not grid[x][y].revealed and not grid[x][y].mine and not grid[x][y].flagged then
               revealCell({x = x, y = y})
           end
       end)
   end

   if cellsRevealed == (numberOfCells - numberOfMines) then
       gameState = stateWon
       highScoreSaved = false
       gameTimerOn = false
       sound(SOUND_POWERUP, 20494)
   end

end

function handleCellTouch(index)

   -- Cell touched call back function.
   --
   -- Reveal or flag touched cell depending on tap state

   if gameState == stateRun then
       gameTimerOn = true
   else
       gameTimerOn = false
   end

   if tapState == stateReveal then
       revealCell(index)
   else    -- tapState == flagged, toggle cell flagged state
       grid[index.x][index.y].flagged = not grid[index.x][index.y].flagged
   end

end

7.3.2 The Cell Class


You would have seen that the two dimensional grid contains cells. The Cell class is described below.

--# Cell
Cell = class()

-- MineSweeper Cell Class
-- Reefwing Software
--
-- Version 1.1
--
-- Each element in the MineSweeper two dimensional grid{}
-- consists of a cell object. Each cell is responsible for
-- tracking its own state and drawing the appropriate icon
-- based on this state.

function Cell:init(i, j, mine, x, y)

   -- Cell Initialisation.
   -- you can accept and set the cell parameters here

   self.index = vec2(i, j)     -- location of cell within the grid{} table
   self.mine = mine            -- boolean indicating if cell contains a mine
   self.revealed = false     -- boolean indicating if cell has been revealed
   self.flagged = false       -- boolean indicating cell has been flagged
   self.pos = vec2(x, y)     -- position of cell on the screen
   self.action = nil              -- call back function when cell tapped
   self.neighbourMines = 0     -- number of mines in surrounding cells
   self.size = vec2(newCellImage.width, newCellImage.height)    -- size of cell on screen

end

-- Cell Draw Functions

function Cell:drawMineCountText()

   -- This function will draw the number of mines
   -- in adjacent cells, if the cell is revealed.
   --
   -- If there are no surrounding mines then no
   -- number is shown.

   -- Save the graphic context

   pushStyle()

   if self.neighbourMines ~= 0 then

       -- Set the text colour based on number of mines
       -- in neigbouring cells.

       if self.neighbourMines == 1 then
           fill(blueColour)
       elseif self.neighbourMines == 2 then
           fill(greenColour)
       elseif self.neighbourMines == 3 then
           fill(redColour)
       else
           fill(blackColour)
       end

       -- Draw the text centred in the cell

       fontSize(16)
       text(self.neighbourMines, self.pos.x, self.pos.y)

   end

   -- return the graphic context to its original parameters.

   popStyle()

end

function Cell:draw()
   -- Codea does not automatically call this method

   -- Draw the appropriate cell image based on whether it has been revealed or not,
   -- has been flagged or whether it contains a mine.
   --
   -- At the end of the game (lost or won) display all mine locations

   local endOfGame = (gameState == stateWon or gameState == stateLost)

   if endOfGame and self.mine then
       sprite(mineCellImage, self.pos.x, self.pos.y)
   elseif self.mine and self.revealed then
       sprite(mineCellImage, self.pos.x, self.pos.y)
   elseif self.revealed then
       sprite(emptyCellImage, self.pos.x, self.pos.y)
       self: drawMineCountText()
   elseif self.flagged then
       sprite(flagCellImage, self.pos.x, self.pos.y)
   else
       sprite(newCellImage, self.pos.x, self.pos.y)
   end

end

-- Cell Touch Handling

function Cell:hit(p)

   -- Was the touch on this cell?
   -- Note code repurposed from the original button class
   -- provide in the Codea examples.

   local l = self.pos.x - self.size.x/2
   local r = self.pos.x + self.size.x/2
   local t = self.pos.y + self.size.y/2
   local b = self.pos.y - self.size.y/2
   if p.x > l and p.x < r and
      p.y > b and p.y < t then
       return true
   end
   return false

end

function Cell:touched(touch)

   -- Codea does not automatically call this method

   if touch.state == ENDED and self:hit(vec2(touch.x,touch.y)) then
       if self.mine and tapState == stateReveal then
           -- You just tapped a mine! Game over.
           gameState = stateLost
           highScoreSaved = true
           gameTimerOn = false
           sound(SOUND_EXPLODE, 43277)
       end

       if self.action then
           -- call back method called.
           self.action(self.index)
       end

   end

end



7.3.3 The High Score Screen


The last class we will look at is the one used to draw the High Score screen. It is fairly simple and draws on the load and save high score functions described in the Main class above. For those looking for something more advanced have a look at Vega's sample project to save high score data to the cloud.

--# ScoreScreen
ScoreScreen = class()

-- ScoreScreen
-- Reefwing Software (reefwing.com.au)
--
-- This class demonstrates a simple high score screen for your App.
--
-- Version 1.0

function ScoreScreen:init()
   -- Initialise the High Score Screen

   local mButtonSize = vec2(180, 50)
   local mLocX = 100
   local mLocY = HEIGHT/2 - 300

   backButton = Button("Back", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   backButton.action = function() backButtonPressed() end

   clearButton = Button("Clear", vec2(WIDTH - 280, mLocY), mButtonSize.x, mButtonSize.y)
   clearButton.action = function() clearButtonPressed() end

end

function ScoreScreen:draw()

   -- Codea does not automatically call this method

   pushStyle()

   -- Set the screen background to blue

   background(blackColour)

   menuBorder:draw()

   -- To use the column formatting below you have to use a fixed
   -- width font. This means either Inconsolata or one of the
   -- Courier varieties.

   font("Courier-Bold")
   fill(blueColour)
   fontSize(72)
   textAlign(CENTER)

   text("High Scores", WIDTH/2, HEIGHT/2 + 220)

   fill(whiteColour)
   fontSize(24)

   local str

   -- The high score data for easy, medium and hard levels are stored
   -- temporarily in 3 tables, ehsData, mhsData and hhsData respectively.
   --
   -- Index 1 of the table (e.g. ehsData[1]) contains the playerName.
   -- Index 2 contains the score.
   --
   -- If there is no current high score then a default name and score is used.
   -- You need to make the score high so that it is replaced when beaten by
   -- your player.
   --
   -- To understand the formatting strings used below (e.g. %-10.10s) have a
   -- look at Interlude 10 on the Codea Tutorial site (codeatuts.blogspot.com.au).

   if ehsData ~= nil and #ehsData > 0 then
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Easy", ehsData[1], ehsData[2])
   else
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Easy", "Player 1", 1000)
   end
   text(str, WIDTH/2, HEIGHT/2 + 40)

   if mhsData ~= nil and #mhsData > 0 then
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Medium", mhsData[1], mhsData[2])
   else
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Medium", "Player 2", 2000)
   end
   text(str, WIDTH/2, HEIGHT/2)

   if hhsData ~= nil and #hhsData > 0 then
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Hard", hhsData[1], hhsData[2])
   else
       str = string.format("%-10.10s\t%-15.15s\t%10d", "Hard", "Player 3", 3000)
   end
   text(str, WIDTH/2, HEIGHT/2 - 40)

   popStyle()

   backButton:draw()
   clearButton:draw()

end

function ScoreScreen:touched(touch)

   -- Codea does not automatically call this method

   backButton:touched(touch)
   clearButton:touched(touch)

end

-- High Score Button Action Methods

function clearButtonPressed()

   -- This function will erase the High Score Data.
   --
   -- Note that this deletion is permanent and the App doesn't
   -- confirm that this is what the user wants to do.
   -- In a real App you should include a confirming dialog box.

   clearLocalData()
   loadHighScoreData()

end

function backButtonPressed()

   -- This function makes use of the gameState Finite State Machine
   -- to indicate that your App should now draw the Menu screen.

   gameState = stateMenu

end

7.4 Conclusion


That concludes our review of the MineSweeper game. This should provide you with some valuable techniques for writing your own games.