Showing posts with label Game. Show all posts
Showing posts with label Game. Show all posts

Friday, October 19, 2012

Tutorial 21 - Integrating Game Center (Part 3)



21.1 On the Shoulders of Giants...


Juan Belón (@juaxix over on the Codea Forums) has already done a lot of the hard work required to get Game Center up and running on your Codea App. If all you need is to submit scores to a single leaderboard then just follow Juan's instructions at this post. Juan has also provided the ability to play and stop mp3's from a Codea App.

We want to be able to submit scores to multiple leaderboards (Easy, Medium and Hard) and incorporate achievements so we need to add to the code that Juan has generously contributed. To this end we will fork his Game Center class on GitHub.

We will start by updating the Codea Runtime Objective C code.

21.2 Modifications to LuaState


In Xcode, make sure that you are showing the Project Navigator (top left button on the tool bar). Open up the Frameworks group, then Codea -> Backend. In Backend, click on the LuaState.m file. Within this file you will see a method called - (void)create{ }. To this method add the following:

//  Game Center Functions:
    LuaRegFunc(aGameCenter_start);
    LuaRegFunc(aGameCenter_isGameCenterAvailable);
    LuaRegFunc(aGameCenter_showEasyLeaderboard);
    LuaRegFunc(aGameCenter_showMediumLeaderboard);
    LuaRegFunc(aGameCenter_showHardLeaderboard);
    LuaRegFunc(aGameCenter_reportEasyScore);
    LuaRegFunc(aGameCenter_reportMediumScore);
    LuaRegFunc(aGameCenter_reportHardScore);
    LuaRegFunc(aGameCenter_reportAchievementIdentifier);
    LuaRegFunc(aGameCenter_showAchievements);
    LuaRegFunc(aGameCenter_resetAchievements);

//  Play Music
    LuaRegFunc(playMusic);
    LuaRegFunc(stopMusic);


21.3 Modifications to OSCommands


In the same directory as LuaState you will see OSCommands.h and OSCommands.m, click on the header file OSCommands.h and after int alert(struct lua_State *L) add the following:

//  Game Center:
    int aGameCenter_start(struct lua_State *state);
    int aGameCenter_isGameCenterAvailable(struct lua_State *state);
    int aGameCenter_showEasyLeaderboard(struct lua_State *state);
    int aGameCenter_showMediumLeaderboard(struct lua_State *state);
    int aGameCenter_showHardLeaderboard(struct lua_State *state);
    int aGameCenter_reportEasyScore(struct lua_State *state);
    int aGameCenter_reportMediumScore(struct lua_State *state);
    int aGameCenter_reportHardScore(struct lua_State *state);
    int aGameCenter_reportAchievementIdentifier(struct lua_State *state);
    int aGameCenter_showAchievements(struct lua_State *state);
    int aGameCenter_resetAchievements(struct lua_State *state);
  //  Play Music
    int playMusic(struct lua_State *L);
    int stopMusic(struct lua_State *L);

Then in the implementation file, OSCommands.m add the following:

static aGameCenter_Codea *CodeaGameCenter;

int aGameCenter_start(struct lua_State *state){
    NSLog(@"Starting Game Center");
    if (CodeaGameCenter==nil){
        CodeaGameCenter = [[[aGameCenter_Codea alloc] init] autorelease];
    }

    [CodeaGameCenter start];
    return 0;
}

int aGameCenter_isGameCenterAvailable(struct lua_State *state){
    return [CodeaGameCenter isGameCenterAvailable];
}

int aGameCenter_showLeaderboard(struct lua_State *state) {
    [CodeaGameCenter showLeaderboard];
    return 0;
}

int aGameCenter_reportEasyScore(struct lua_State *state) {
    [CodeaGameCenter reportEasyScore:lua_tonumber(state,1)];
    return 0;
}

int aGameCenter_reportMediumScore(struct lua_State *state) {
    [CodeaGameCenter reportMediumScore:lua_tonumber(state,1)];
    return 0;
}

int aGameCenter_reportHardScore(struct lua_State *state) {
    [CodeaGameCenter reportHardScore:lua_tonumber(state,1)];
    return 0;
}

int aGameCenter_reportAchievementIdentifier(struct lua_State *state) {
    [CodeaGameCenter reportAchievementIdentifier:lua_tonumber(state,1)];
    return 0;
}

int aGameCenter_showAchievements(struct lua_State *state) {
    [CodeaGameCenter showAchievements];
    return 0;
}

int aGameCenter_resetAchievements(struct lua_State *state) {
    [CodeaGameCenter resetAchievements];
    return 0;
}

int playMusic(struct lua_State *L){
    [CodeaGameCenter playMusic:lua_tonumber(L,1)];
    return 0;
}

int stopMusic(struct lua_State *L){
    [CodeaGameCenter stopMusic];
    return 0;
}


21.4 Modifications to the aGameCenter_Codea Class


The final change in Xcode is to add the updated version of the aGameCenter_Codea class. You can download the forked version of Juan's code here. Add these two files to the Supporting Files group in the runtime.

21.5 Modifications to your Codea App

  
Once you start adding the Game Center functionality you wont be able to compile and run your App in Codea anymore (since these functions are only defined in the runtime). Consequently, debug all of your code apart from the Game Center specific parts before you make the following modifications. 

After you are in Xcode and using the runtime, you can still update your Lua code but you need to force the runtime to reload the Lua files into the documents directory. You can do this by changing the version number in the Lua info.plist which can be found in Project.codea. 

This number just needs to be different (not necessarily larger) to what it was the last time you compiled, to force an upload. Alternatively, if you get sick of doing this each run-test cycle (like we did), you can go into the CodifyAppDelegate.m file, find the - (BOOL) migrateProjectAtPath:(NSString*)path toPath:(NSString*)destPath method and comment out the following code as shown below.
  
// NSString* oldVersion = [self versionForProjectAtPath:destPath];
// NSString* newVersion = [self versionForProjectAtPath:path];

// if ([oldVersion isEqualToString:newVersion]) 

//    {
//        return YES;
//    }

Should you need to add sprites, you can save these directly into the dropbox folders in the runtime but you may need to do a clean build (select Product from the menu bar and then Clean) before Xcode will see them.

As part of the Game Center update of MineSweeper (v1.5) we took the opportunity to tweak the code based on feedback from the Codea Forum. In particular:
  1. There is a new MineSweeper logo on the Menu screen courtesy of @derhannes (you can see their web site at: http://www.boba-soft.com).
  2. The falling mines on the Menu Screen now start dropping from above the screen which makes the animation look smoother. Thanks to @Fred for this suggestion.
  3. The textBox used to enter the players name, if a new high score is achieved, now handles a changing orientation (e.g. Portrait to Landscape). Thanks to @West for finding this bug.

Once you have added all the code above, the actual Game Center implementation in Lua is very simple. In the Setup() function in Main add the following code:


-- Initialise Game Center if it is available (requires a device running iOS 4.1
-- or later and the App needs to be running within the Codea run time).
    
    aGameCenter_start()
    if aGameCenter_isGameCenterAvailable() then
        print("Game Center Started.")
    else
        print("Game Center not available")
    end

Then you just need to report your scores and achievements as they happen. For example in MineSweeper when the game is won we do the following achievement checks:

-- Check if any Achievements were completed once game
-- has been won.
        
        if gameDifficulty == stateEasy then
            if readLocalData("easyWinner") == nil then
                saveLocalData("easyWinner", "YES")
                aGameCenter_reportAchievementIdentifier(1)
                print("Easy Winner Achievement.")
            end
        end
        
        if gameDifficulty == stateMedium then
            if readLocalData("mediumWinner") == nil then
                saveLocalData("mediumWinner", "YES")
                aGameCenter_reportAchievementIdentifier(2)
                print("Medium Winner Achievement.")
            end
        end
        
        if gameDifficulty == stateHard then
            if readLocalData("hardWinner") == nil then
                saveLocalData("hardWinner", "YES")
                aGameCenter_reportAchievementIdentifier(3)
                print("Hard Winner Achievement.")
            end
        end
        
        if readLocalData("gamesPlayed") == nil then
            gamesPlayed = 0
        else
            gamesPlayed = readLocalData("gamesPlayed")
        end
        
        gamesPlayed = gamesPlayed + 1
        saveLocalData("gamesPlayed", gamesPlayed)
        if gamesPlayed == 10 then
            if readLocalData("decathlon") == nil then
                saveLocalData("decathlon", "YES")
                aGameCenter_reportAchievementIdentifier(6)
                print("Decathlon Achievement.")
            end
        end
        
        if gamesPlayed == 100 then
            if readLocalData("centurion") == nil then
                saveLocalData("centurion", "YES")
                aGameCenter_reportAchievementIdentifier(7)
                print("Centurion Achievement.")
            end
        end

To save high scores onto the relevant leader board we updated the saveHighScore function as shown below.

function saveHighScore(d)
    
    -- Build the high score data into a string which is saved
    -- using saveLocalData(key, value). Also save gameTime as
    -- the score on Game Center for the appropriate leader board
    -- (easy, medium or hard).
    --
    -- n = playerName
    -- t = gameTime
    -- d = os.date() [current date] not used in this version
    
    playerName = textBox.text
    print("New High Score by: "..playerName)
    
    local hsDataString = string.format("return {\"%s\", %d}", playerName, gameTime)
    
    if gameDifficulty == stateEasy then
        saveLocalData("easyHighScore", hsDataString)
        aGameCenter_reportEasyScore(gameTime)
    elseif gameDifficulty == stateMedium then
        saveLocalData("mediumHighScore", hsDataString)
        aGameCenter_reportMediumScore(gameTime)
    elseif gameDifficulty == stateHard then
        saveLocalData("hardHighScore", hsDataString)
        aGameCenter_reportHardScore(gameTime)
    end
    
    hideKeyboard()
    highScoreSaved = true
    
end

Thursday, October 4, 2012

Tutorial 20 - Integrating Game Centre (Part 2)

20.1 Scores and Achievements


Before getting too much further we need to decide what scores and achievements that you want to have for your game. For MineSweeper we will keep things fairly simple. We will set up a leader board for each game difficulty (easy, medium and hard) and keep track of the following achievements:
  • Easy Winner (25 points) - Won an Easy Difficulty Game;
  • Medium Winner (50 points) - Won a Medium Difficulty Game;
  • Hard Winner (100 points) - Won a Hard Difficulty Game;
  • Boom (25 points) - tapped a mine;
  • Bad Luck (50 points) - tapped a mine on your first move;
  • Decathlon (50 points) - play 10 games of any difficulty; and
  • Centurion (100 points) - play 100 games of any difficulty.

20.2 iTunes Connect - Leaderboards



To set up the metadata for your App, log into your iOS developer account and go to iTunes Connect. We are assuming that you have already set up your App ID and provisioning profiles. If you haven't, look at Tutorials 12 and 13. 

Click on the Manage your Applications link and then click on the App icon that you want to set up for Game Center. On the App Information screen you will see a button in the top right called Manage Game Center. Click on this and then enable your game (as either a single game or part of a group of games which shares scores and achievements). For MineSweeper we will just enable it as a single game, you can always change it to a group game later if you wish.

From the Game Center screen you can add leader boards and achievements. Remember that Leader boards which are live for any app version cannot be removed. This is true for achievements as well.

Click on the Add Leaderboard Button and then select add Single Leaderboard (You cannot create a combined leaderboard until you have two or more single leaderboards with the same score format type and sort order).


Figure 1. Add Language Screen.

Fill out the information for your leader board. The leaderboard reference name is an internal name that you must provide for each leaderboard. It is the name you should use if you search for the leaderboard within iTunes Connect. We used "Easy Difficulty" for our first leaderboard reference name.

The Leaderboard ID is a unique alphanumeric identifier that you create for this leaderboard. It can contain periods and underscores. We used the reverse URL style for ours (e.g. au.com.reefwing.minesweeper.easyDifficulty).

Then choose the score format for this app leaderboard and choose "High to Low" if you want highest scores displayed first or choose "Low to High" if you want the lowest scores displayed first. We select "Low to High" because a lowering your time to solve the grid is the objective.

Optionally, you can define the score range using 64-bit signed integers. The values must be between the long min (-2^63) and long max (2^63 - 1). Any scores outside of this range will be deleted. We don't define a score range for MineSweeper.

You must add at least one language for your leader board. For each language, you will have to provide a score format and a leaderboard name. Click on the Add Language button to bring up the screen to do this (see Figure 1).

The score format suffix will be added to the end of scores displayed on your leaderboard. Use this field to specify a singular suffix. This is optional, and is useful for clarifying the type of score your app uses. Examples include "point", "coin", or "hit".

Finally, you can assign an image to your leaderboard. The image must be a .jpeg, .jpg, .tif, .tiff, or .png file that is 512x512 or 1024x1024 pixels, at least 72 DPI, and in the RGB color space. Click the Save button and you are done.


Figure 2. Add an Achievement.

20.3 iTunes Connect - Achievements


Creating achievements is very similar to leaderboards. Click on the "Add Achievement" button to get started (Figure 2).

The Achievement Reference Name is an internal name that you must provide for each achievement. It is the name you should use if you search for the achievement within iTunes Connect (e.g. Easy Winner).

The Achievement ID is a unique alphanumeric identifier that you create for this achievement. It can contain periods and underscores. Once again we used the reverse URL style for ours (e.g. au.com.reefwing.minesweeper.easyWinner).

Point Value is the amount of points your achievement is worth. There is a maximum of 100 points per achievement and 1000 points for all achievements combined. The points we assigned to each achievement are shown in section 20.1. You will see that we have left lots of spare points in case we want to add additional achievements in the future.

Achievements marked as Hidden will remain hidden on Game Center until a player has achieved them. We wont hide any of our achievements.

For the "Achievable More Than Once" field, If you select Yes, users can accept Game Center challenges for achievements they have already earned.

Once you have filled out the above metadata for your App you need to add at least one language. For each language you need to add:

  • Title: The localized title of this achievement as you would like it to appear in Game Center (e.g. Easy Winner).
  • Pre-earned Description: The description of your achievement as it will appear to a Game Center user before they have earned it (e.g. Win at least one Mine Sweeper game on Easy Difficulty).
  • Earned Description: The description of your achievement as it will appear to a Game Center user after they have earned it (e.g. Won at least one Mine Sweeper game on Easy Difficulty).
  • An image for your achievement. The image must be a .jpeg, .jpg, .tif, .tiff, or .png file that is 512x512 or 1024x1024 pixels, at least 72 DPI, and in the RGB color space.
Click the Save Button and you are finished. Rinse and repeat for each one of your achievements.

In part 3 of the tutorial we will detail the code required in Lua and Objective C to tie these leaderboards and achievements to your game.

Sunday, September 30, 2012

Tutorial 19 - Integrating Game Centre (Part 1)

19.1 Game Center

   
Game Center is Apple’s social gaming network. Integrating Game Center functionality into your Codea App has to be done in Xcode. There are three main areas that we need to concern ourself with to implement Game Center:

  1. Players;
  2. Scores; and
  3. Achievements.

All of your metadata for Game Center functionality is set up and managed in iTunes Connect, allowing you to test your Game Center features before submitting your app to the App Store. You use iTunes Connect to enable your app for Game Center testing, and set up your leaderboards and achievements. Then use the Game Kit framework in your app to add Game Center functionality.
   
Apple suggests that you should consider scores and achievements as part of your initial game design (as opposed to tacking it on at the end). This is a legitimate observation in most cases, for example passing other players in DoodleJump is arguably why it is so popular.
  
Be aware that once your Game Center assets are published to the live servers, some assets become more difficult to change because they are already in use by players and on live versions of your game. For example, leaderboard scores are formatted using the leaderboard assets you created. If you change your scoring mechanism and change your leaderboard assets to match, older scores would still be posted on Game Center and would be inconsistent with the newer scores. For this reason, some assets you create cannot be modified after the game ships.
    

19.2 Why Bother?

   
Many iOS games use Game Center, but not all of them use every feature. Apps can choose to include any or all of the following features supported by Game Center:
  • Leader-boards – compares scores with the player's friends and with other players from around the world
  • Achievements – shows goals that can be accomplished by the player and also allows the player to compare with friends' achievements
  • Multiplayer – the game can host matches in real time, either between the player's friends or by "auto-matching" with random players from around the world.
Some of the reasons that you may want to include Game Centre are:
  1. Improve the longevity and replayability of your game by adding challenges (aka Achievements) or multiplayer capability.
  2. Being able to see usage of your Apps via leaderboard activity.
  3. Improve the discoverability of your Apps through players challenging their friends and the new facebook "like" button.
  4. Allows players to rate your App from Game Center (if your App is rubbish this may not be a good thing).
To setup Game Centre there is code that we need to add to the App and then metadata which we need to add to iTunes Connect for the App. This tutorial will deal with the App side changes.
      

19.3 Step 1 - Authenticate the Player

     
To demonstrate the implementation of Game Center, we will use the MineSweeper App developed in previous tutorials. We are assuming that you are familiar with the Codea runtime and have got your App running in Xcode. If you haven't then read Tutorials 12 and 13 first. 
     
    
To start, open up your App in Xcode. The first thing we need to do is to link the GameKit framework. In the navigator area of Xcode 4, select the project name in the top left, it should be “CodeaTemplate”. Next, select the current target (“MineSweeper” in our case), and then select the “Build Phases” tab. Expand the “Link Binary With Libraries” option, and then click the “+” button to add a new framework. Type “g″ into the search box, and select the GameKit.framework framework that appears in the list. Click “Add” to link this framework to  your project. So far so good.

Once again in the Project Navigator pane, Under Classes -> Supporting Files, click on the CodifyAppDelegate.h file and add:
  
#import <GameKit/GameKit.h>
  
Your App will now recognise the Game Center methods and variables. If we weren't using Codea, setting up Game Center is pretty simple, to illustrate:
 
Click on CodifyAppDelegate.m and add the following to the end of the didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Existing code here...

    // Authenticate Player with Game Center
    
    GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
    
    // Handle the call back from Game Center Authentication
    
    [localPlayer authenticateWithCompletionHandler:^(NSError *error)
    {
        if (localPlayer.isAuthenticated)
        {
            // Player was successfully authenticated.
            // Perform additional tasks for the authenticated player.
        }
        else if (error != nil)
        {
            NSLog(@"error : %@", [error description]);
        }
    }];

    // ...and back to the existing code.

    Return YES;
 
}

This is the code for pre-iOS 6 (since I have an iPad 1) devices. If you are developing for iOS 6, note that authenticateWithCompletionHandler is deprecated (use localPlayer.authenticateHandler instead).
  
Running this code in the Xcode simulator should present you with the pop up shown in Figure 1. Log in with your usual App ID to test.
  
Figure 1. Game Center Sign In.
  
If you haven't enabled your game for Game Center, once you log in you will get the error shown in Figure 2.
  
Figure 2. Game Center not enabled Error.

The problem is that we need to be able to submit scores and achievements from within our Codea App and Codea doesn't know about Game Center. However, one of the gun coders, @juaxix over on the Codea Forums has built a bridge between Objective C and Lua. We will extend this and use it to enable Game Center for MineSweeper. So delete the authentication code above if you added it to your App and we will show you the correct way to bring Game Center functionality to your Codea App.

In the next tutorial we will show you how to add leader boards and achievements in iTunes Connect. We will need these before we can make the changes required to our Lua and Objective C code.

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




Tuesday, July 10, 2012

Tutorial 6 - MineSweeper Part 1 (Updated 23/01/16)


6.1 Game Design


To further illustrate the many uses of Finite State Machines (FSM) we will deconstruct a simple game called MineSweeper. This game will illustrate a bunch of new concepts including recursive coding and closures as well as using a lot of the code that we developed in early tutorials.

Our MineSweeper code uses no less than three state machines to keep track of what is happening. The main state machine is illustrated above. This is a pretty standard pattern for most games. The goal was to produce the entire game using just an iPad. 

The aim of MineSweeper is to clear the minefield represented by a square grid without tapping a mine. When the splash screen fades away you will be presented with a menu of three buttons: Easy, Medium and Hard which you can use to select the game difficulty and launch the game. 



Game difficulty is tracked using a state machine and is used when the New button is tapped on the game screen to ensure we launch another game at the same difficulty level as the current game. The variables effected by difficulty are shown in the following table.


Game Difficulty
Easy Medium Hard
Number of Mines 10 15 40
Grid Width 8 12 16
Grid Height 8 12 16


Once you select the game difficulty by tapping on the appropriate button, you will be presented by the main game screen. The blue grid represents the minefield. The number on the left above the minefield is the number of cells left to clear. The number on the right is the elapsed time in seconds (approximately). The game timer will start as soon as you tap a minefield cell. Winning, losing or tapping the New or Menu buttons will stop the game timer.

The button in the top right will initially have "Show" as its button text. This indicates that you are in Show mode when you tap the grid. Show mode will reveal the cell contents. This will either be blank, a number or a mine. If it is a mine then the game is over. If the cell is blank it means that there are no mines in the neighbouring cells (each cell can have up to eight neighbours depending on its grid position). If the cell contains a number, this represents the number of mines in adjacent cells. You can use these numbers to deduce where the mines are located. If you determine that a cell contains a mine you can flag it by using Flag mode. You enter Flag mode by tapping the Show Button. When in Flag mode this button text will change to "Flag". Tapping it again will return it to Show mode. Tapping any unrevealed cell while in Flag mode will place a flag icon on that cell. This icon can be toggled on and off by tapping the same cell in Flag mode.

If you tap a blank cell in Show mode, the game will also reveal any other adjacent blank cells. This can reveal quite large areas depending on the mine disposition.

You don't have to use flags to indicate suspected mine locations if you don't want to. The game is harder if you don't use flags.

The game ends if you tap a mine (lose) or if you reveal all of the cells without mines (win).


6.2 Download the Code


If you just want to run MineSweeper and play the game then the easiest approach is to download the entire code in one file and paste it into the Main tab of a new project. If you are more interested in reusing the classes and understanding how it works then you are better off downloading the individual classes and pasting them into separate tabs. The order of the tabs can be important, have a look at this article on inherited classes if you want to know why.

We suggest that you use the same tab order as our project. The classes you need can be downloaded using the following links. Use these names for your tabs/class.

1. Main
2. Cell
3. Button
4. RoundBorder
5. SplashScreen
6. Fader
7. Twinkle
8. IconImages

We have already discussed a number of these classes (RoundBorder, SplashScreen, Fader and Twinkle) in previous tutorials so we won't be covering them here. We had previously been using the Button class provided with the Codea sample project "Sounds Plus", but we found this class took too much tweaking to make the various buttons look right. Consequently, we are now using the mesh button class developed by Vega. We tweaked this a little bit, adding callback functionality and push/popStyle to preserve the various graphic variables.

The following tutorial will delve into the MineSweeper Main and Cell classes, which is where most of the action is, but we did want to touch on developing the images.

6.3 Pixel Images in Spritely



The sprite images used to represent the table cells were done in Spritely, the pixel image editor provided with Codea. This was fun but you probably wouldn't want to do a lot of images with it. Drawing every sprite, pixel by pixel using your finger is an exercise in patience. A side effect of this approach is that the MineSweeper code is over 5,000 lines long. Most of this is Spritely images. Each 32x32 pixel image takes over 1,000 lines of code to define it. Now Spritely automatically generates this for you but you need to draw the images first. To make life easy we have included all the code (including images) which you can download from dropbox.


For future games we probably won't use Spritely images. The largest sprite that you can produce is 32x32 pixels. This is just big enough for my little fingers to tap reliably but I imagine some folks could have difficulty. We would have preferred to make the sprites a touch bigger for MineSweeper. The advantage of this method is that all the game assets are in one file which makes it easy to distribute, however this is offset by the 32 pixel size constraint and the long files required to represent each image. On my first generation iPad, editing the IconImages class (which contains the 4 sprites) was very slow and crashed frequently. If you are going to use Spritely, a separate tab for each image is probably the way to go and don't forget to back up your code. The easiest way to backup is to tap and hold on your project icon in the project explorer screen of Codea and then tap copy. We then paste and email the resulting file, clean it up in TextWrangler and finally save it in DropBox.

But this does demonstrate that the iPad is moving from a content consumption device to a content generation device. How amazing is it to be able to code and produce graphics on this fantastic tablet?

Stay tuned for part 2 of the MineSweeper Tutorial...