How To Make a Letter / Word Game with UIKit and Swift: 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 Caroline Begbie.

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 each level already has a corresponding maximum time in seconds in the level config file. You haven’t used this data yet, but now you will.

Open to GameController.swift and add the following two properties to the class:

//stopwatch variables
private var secondsLeft: Int = 0
private var timer: NSTimer?

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:

func startStopwatch() {
  //initialize the timer HUD
  secondsLeft = level.timeToSolve
  hud.stopwatch.setSeconds(secondsLeft)
    
  //schedule a new timer
  timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector:"tick:", userInfo: nil, repeats: true)
}

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 function yet).

Next, add stopStopwatch to stop the timer:

func 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.

@objc func tick(timer: NSTimer) {  
  secondsLeft--
  hud.stopwatch.setSeconds(secondsLeft)
  if secondsLeft == 0 {
    self.stopStopwatch()
  }
}

Note the @objc attribute. The GameController class does not inherit from NSObject, so tick() would normally not be visible from Objective-C. Since NSTimer is an Objective-C class, you need to mark tick() with @objc to make it usable in Objective-C land.

This method 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!

Now you need to actually start the timer when the game starts! At the end of dealRandomAnagram(), add:

//start the timer
self.startStopwatch()

And of course 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()

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

Clock is ticking

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 file in Anagrams/Classes/Models named GameData.swift. Add the following code:

class GameData {
  //store the user's game achievement
  var points:Int = 0 {
		didSet {
			//custom setter - keep the score positive
			points = max(points, 0)
		}
  }
}

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

The didSet property observer on the points variable 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 function, which returns the greater value of its two arguments. Thus, if you try to store a negative number into points, 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?

Add the following property to the GameController class in GameController.swift:

private var data = GameData()

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

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
data.points += 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
data.points -= 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.