5. A Fully Working Game Written by Joey deVilla

You’ve made a lot of progress on the game, and the to-do list is getting shorter! You have a basic version of the game running, where you can generate and display the target value, and you can also calculate and show the player the number of points they’ve scored in the current round.

It’s now time to make a fully-working game, where the player can play multiple rounds and the game keeps a running score. We’ll also give the player the ability to start a new game.

This chapter covers the following:

• Improving the `pointsForCurrentRound()` algorithm: Simplifying how the the number of points awarded to the player is calculated.
• What’s the score?: Calculate the player’s total score over multiple rounds and display it onscreen.
• One more round…: Implement updating the round count and displaying the current round on screen.
• Key points: A quick review of what you learned in this chapter.

Improving the `pointsForCurrentRound()` algorithm

Let’s do a little more refactoring of `pointsForCurrentRound()`, the method that calculates how many points to award to the player based on the difference between the target value and where they put the slider. Here’s its code at the moment:

``````func pointsForCurrentRound() -> Int {
let difference: Int
if self.sliderValueRounded > self.target {
difference = self.sliderValueRounded - self.target
} else if self.target > self.sliderValueRounded {
difference = self.target - self.sliderValueRounded
} else {
difference = 0
}
return 100 - difference
}
``````

Most of the code in this method is devoted to making sure that `difference` — the difference between the slider value and the target value — is always positive. This is done by making sure that the smaller value is always subtracted from the larger value.

“Absolute” power

There’s a simpler way to do this, and it comes from one of the many math functions built into the Swift Standard Library: The `abs()` function. Given a number, which can be an `Int`, a `Double` or any other Swift data type that represents a number, it returns the absolute value of that number, which is the value of that number, but ignoring the sign.

``````func pointsForCurrentRound() -> Int {
let difference = abs(self.sliderValueRounded - self.target)
return 100 - difference
}
``````

Removing a “magic number”

Here’s the current code for `pointsForCurrentRound()`:

``````func pointsForCurrentRound() -> Int {
let difference = abs(self.sliderValueRounded - self.target)
return 100 - difference
}
``````
``````func pointsForCurrentRound() -> Int {
let maximumScore = 100
let difference = abs(self.sliderValueRounded - self.target)
return maximumScore - difference

``````

What’s the score?

Now that you have a lean, mean `pointsForCurrentRound()` and know how far off the slider is from the target, it’s time to keep track of the player’s score.

``````// User interface views
@State var sliderValue = 50.0
@State var target = Int.random(in: 1...100)
var sliderValueRounded: Int {
Int(self.sliderValue.rounded())
}
@State var score = 0
``````
``````// Button row
Button(action: {
print("Points awarded: \(self.pointsForCurrentRound())")
}) {
Text("Hit me!")
}
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!")))
}
``````
``````print("Points awarded: \(self.pointsForCurrentRound())")
``````
``````// Button row
Button(action: {
print("Points awarded: \(self.pointsForCurrentRound())")
self.score = self.score + self.pointsForCurrentRound()
}) {
Text("Hit me!")
}
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!")))
}
``````
``````// Score row
HStack {
Button(action: {}) {
Text("Start over")
}
Spacer()
Text("Score:")
Text("\(self.score)")
Spacer()
Text("Round:")
Text("999")
Spacer()
Button(action: {}) {
Text("Info")
}
}
``````
``````Text("999999")
``````
``````Text("\(self.score)")
``````

One more round…

Once the player has tapped Hit me! and been awarded their points, the game should present the player with a new target. This means coming up with a new random value for `target`. That part is easy:

``````self.target = Int.random(in: 1...100)
``````
``````// Button row
Button(action: {
print("Button pressed!")
self.score = self.score + self.pointsForCurrentRound()
self.target = Int.random(in: 1...100)
}) {
``````

Asynchronous code execution

You probably know that computers — your iOS device included — can perform several tasks at the same time, either by actually performing tasks simultaneously, or combining careful scheduling with their millisecond speed to make it appear as if they’re multitasking.

``````// Button row
Button(action: {
print("Points awarded: \(self.pointsForCurrentRound())")
self.score = self.score + self.pointsForCurrentRound()
self.target = Int.random(in: 1...100)
}) {
``````

``````// Button row
Button(action: {
}) {
Text("Hit me!")
}
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!"))
)
}
``````

Finding a better place to start a new round

The problem with our first approach is that it tried to start a new round in response to the player tapping Hit me!, which is before the alert pop-up gets displayed. The new round should start in response to the player dismissing the alert pop-up, which happens when they tap the pop-up’s Awesome! button.

``````Alert(title: Text("Hello there!"),
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!")))
``````
``````// Button row
Button(action: {
print("Button pressed!")
}) {
Text("Hit me!")
}
message: Text(self.scoringMessage()),
dismissButton: .default(Text("Awesome!")) {
self.score = self.score + self.pointsForCurrentRound()
self.target = Int.random(in: 1...100)
}
)
}
``````

Showing the current round

Just as there’s a designated place to store the score, there also needs to be a place to store the number of the current round.

``````// User interface views
@State var sliderValue = 50.0
@State var target = Int.random(in: 1...100)
var sliderValueRounded: Int {
Int(self.sliderValue.rounded())
}
@State var score = 0
@State var round = 1
``````
``````Alert(title: Text("Hello there!"),
message: Text(scoringMessage()),
dismissButton: .default(Text("Awesome!")) {
self.score = self.score + self.pointsForCurrentRound()
self.target = Int.random(in: 1...100)
self.round = self.round + 1
}
)
``````
``````// Score row
HStack {
Button(action: {}) {
Text("Start over")
}
Spacer()
Text("Score:")
Text("\(self.score)")
Spacer()
Text("Round:")
Text("\(self.round)")
Spacer()
Button(action: {}) {
Text("Info")
}
}
``````
``````Text("999")
``````
``````Text("\(self.round)")
``````

Key points

You’ve got a mostly-working game; feel free to take a victory lap.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.