How To Make a Letter / Word Game with UIKit: Part 2/3

In this second part of the tutorial series, you’ll aim for developing a fully playable version of the game. When you’re finished, the user will be able to drag the tiles and drop them on the correct targets, where they will “stick” to the spot. By Marin Todorov.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 6 of this article. Click here to view the first page.

Adding a Level Timer

Implementing the game timer is incredibly simple. Remember that you already load the total game time in seconds from the level config file. You haven’t used this data yet, but now you will.

Add the timer to the game controller. Go to GameController.m and add the following two private variables in the same section where you declared _targets and _tiles:

    //stopwatch variables
    int _secondsLeft;
    NSTimer* _timer;

_secondsLeft will store the number of seconds left to complete the level. It will be decreased every second by _timer.

Now you need three new helper methods:

  1. One to start the timer when the tiles are displayed on the board.
  2. One to stop the timer in case the player solves the puzzle or the time is up.
  3. One to fire each second and update the HUD.

Add them one at a time to the game controller. First add the following method to initialize the new variables and start the timer:

-(void)startStopwatch
{
    //initialize the timer HUD
    _secondsLeft = self.level.timeToSolve;
    [self.hud.stopwatch setSeconds:_secondsLeft];
    
    //schedule a new timer
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                                     target:self
                                   selector:@selector(tick:)
                                   userInfo:nil
                                    repeats:YES];
}

You initialize _secondsLeft with the initial remaining time stored in the level, and update the stopwatch label to display this time. You also schedule a new NSTimer that will invoke tick: each second (you haven’t added this method yet).

Add stopStopwatch to stop the timer:

//stop the watch
-(void)stopStopwatch
{
    [_timer invalidate];
    _timer = nil;
}

Calling invalidate on the timer stops it, and then you set it to nil because it is no longer needed.

Now define tick:, which will be called once a second while _timer is running.

//stopwatch on tick
-(void)tick:(NSTimer*)timer
{
    _secondsLeft --;
    [self.hud.stopwatch setSeconds:_secondsLeft];
    
    if (_secondsLeft==0) {
        [self stopStopwatch];
    }
}

It does nothing more than decrease the amount of seconds left by one, then update the stopwatch label with the new value. If the number of seconds remaining hits 0, it calls stopStopwatch. That’s all!

The only step left is to actually start the timer when the game starts! At the end of dealRandomAnagram, add:

//start the timer
[self startStopwatch];

All right, another feature is in! Build and run again to see the timer ticking down when the game starts:

Wait, don’t forget that you need to stop the timer if the player completes the phrase. At the end of checkForSuccess, add the following call:

//stop the stopwatch
[self stopStopwatch];

Hmm, what else can you add to your fancy new HUD?

Keeping Score, Fair and Square

When making a game you want to have a separate class just to keep the game data for the current player session. This class will store such things as the player’s current score, remaining number of lives and progress through the game. In a more complicated game, you could have this data also sorted per user, which might require a simple database of some sort.

For this tutorial, your game will have a simple class to keep the score between games, but not between app restarts.

Create a new class in Anagrams/Classes/models named GameData that subclasses NSObject. Inside GameData.h, add the following property to the interface:

//store the user's game achievement
@property (assign, nonatomic) int points;

The points property will store the player’s current score.

Inside GameData.m, add the following method:

//custom setter - keep the score positive
-(void)setPoints:(int)points
{
    _points = MAX(points, 0);
}

A setPoints: method would have been created automatically when the compiler synthesized the points property, but you want a custom version that ensures the value is never negative. You don’t want the player to have fewer than zero points, right? So you pass points and 0 to the MAX macro, which returns the greater value of its two arguments. Thus, if you pass setPoints: a negative number, it will assign 0 to _points.

That’s all you need for your simple GameData class. But if your game evolves, this class will also become more complicated.

OK, it’s time to decide where in the game the player will receive or lose points.

There are two point-events so far in the gameplay:

  • When a tile is dropped on a correct target, award the player with points.
  • When a tile is dropped on a wrong target, subtract some points from the current score.

This way, to get the most points possible out of the current anagram, the player will have to solve it without making any mistakes.

Challenge: You can also award points upon puzzle completion, and subtract points when the player fails to complete a puzzle in time. How would you implement that yourself?

Challenge: You can also award points upon puzzle completion, and subtract points when the player fails to complete a puzzle in time. How would you implement that yourself?

Hook the score-keeping into the game controller logic by adding the following import and property inside GameController.h:

// with the other imports
#import "GameData.h"

// with the other properties
@property (strong, nonatomic) GameData* data;

This gives you access to a GameData object to store the score.

Now switch to GameController.m. You need to initialize the self.data property. To do that, add the following custom init:

//initialize the game controller
-(instancetype)init
{
    self = [super init];
    if (self != nil) {
        //initialize
        self.data = [[GameData alloc] init];
    }
    return self;
}

For now, this just initializes the GameController and assigns a new GameData object to its data property. You will add more here in Part 3 of this tutorial.

In the same file, add the following code inside tileView:didDragToPoint:, just below the comment that reads “//more stuff to do on success here”:

//give points
self.data.points += self.level.pointsPerTile;

Luckily the level config file already includes the points per title for every level of difficulty, so all you do here is increment the current score by that number.

To handle errors, add the following code in the same method, just below the comment that reads “//more stuff to do on failure here”:

//take out points
self.data.points -= self.level.pointsPerTile/2;

Here, you do the opposite and subtract points. But there’s no point (ha, ha) in being too tough on the player – if they drop the tile on the wrong target, only half of what they receive for a matching one is subtracted from the current score.

All right! Keeping score – check.

Note: The GameData class is quite naïve as it is. Your own UIKit game will likely have some other game data you need to track, requiring you to adjust the class as you wish. A few possible features deserve an honorable mention:

  • You might want to add Game Center integration to your game, which will forward the score from GameData to Apple’s Game Center. More on Game Center.
  • You might want to persist the score by saving it to the NSUserDefaults or a keychain. More on secure storage of data with a keychain.
  • If you would like to store the game data to a keychain or persist it to a file, you might find using the JSONModel library super useful. It allows you to convert your model data to a JSON string, which is easily storable in a keychain or a text file.

Note: The GameData class is quite naïve as it is. Your own UIKit game will likely have some other game data you need to track, requiring you to adjust the class as you wish. A few possible features deserve an honorable mention:

  • You might want to add Game Center integration to your game, which will forward the score from GameData to Apple’s Game Center. More on Game Center.
  • You might want to persist the score by saving it to the NSUserDefaults or a keychain. More on secure storage of data with a keychain.
  • If you would like to store the game data to a keychain or persist it to a file, you might find using the JSONModel library super useful. It allows you to convert your model data to a JSON string, which is easily storable in a keychain or a text file.

Contributors

Over 300 content creators. Join our team.