100 Days of SwiftUI Day 22

100 Days of SwiftUI – Day 22 – Guess the Flag Review

19/07/22: I’ve updated this post with some refinements to the original code! These are important, so make sure to check them out. You can find them nearing the end of this post.

It’s day 22 of the 100 days of SwiftUI! Yesterday, we finished the main part of the Guess the Flag project. Today, we’ve reviewed what we’ve learned and go through three coding challenges. The challenges are what I’ll be writing about today. Let’s go!

Guess the Flag challenges in SwiftUI

The first challenge is to add a State property to store the user’s score, modify it when they get an answer right or wrong, then display it in the alert and in the score label. This one was easy enough!

First, I created a new variable:

    @State private var userScore = 0

Next, we update this variable every time an answer is given. We do this in our flagTapped function, as this function is called every time an answer is selected.

func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct!"
            userScore+=100
        } else {
            scoreTitle = "Wrong!"
            userScore-=100
        }

        showingScore = true
    }

The second challenge wasn’t very difficult either: every time someone chooses the wrong flag, tell them their mistake in your alert message – something like “Wrong! That’s the flag of France,” for example.

To accomplish this, we go back into our flagTapped function and add this text to our scoreTitle variable using string interpolation to get the country name from the countries array.

func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct! That's the flag of \(countries[number])"
            userScore+=100
        } else {
            scoreTitle = "Wrong! That's the flag of \(countries[number])."
            userScore-=100
        }

        showingScore = true
    }

The final challenge was the most difficult: make the game show only 8 questions, at which point they see a final alert judging their score and can restart the game.

Paul provided a hint for this challenge, but I went ahead and tried it without reading that hint. The first thing I did was adding a questionCounter variable, that would be incremented every time a new question was asked.

@State private var questionCounter = 1

I added the incrementation for this variable in our askQuestion function, so that every time a new question was asked, the counter would increment.

func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        questionCounter+=1
    }

Next, I created a restartGame function, that would reset the game to it’s initial state. That meant shuffling the array of countries, randomizing the correct answer, setting the userScore to 0 and the questionCounter to 1.

func restartGame() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        questionCounter = 1
        userScore = 0
    }

With all of that logic in place, the final piece of the puzzle was figuring out the correct implementation. The solution I thought up makes use of an if statement that checks whether the questionCounter has reached 8, the final question in our game. If it did, the final alert after choosing an answer had to display the final score, as well as include a button that had our restartGame function as an action.

.alert(scoreTitle, isPresented: $showingScore) {
            if questionCounter != 8 {
                Button("Continue", action: askQuestion)
            } else {
                Button("Restart", action: restartGame)
            }
        } message: {
            if questionCounter != 8 {
                Text("Your score is \(userScore)")
            } else {
                Text("You ended the game with a score of \(userScore). Press the restart button to restart the game.")
            }
        }

I’ve also tried adding another alert, this was what Paul hinted at, but I couldn’t manage to get that working right now. In order to get another solution, I’ve posted on the Hacking with Swift forums to get some help and evaluate my current solution. My full project looks like this at the moment:

import SwiftUI

struct ContentView: View {
    @State private var showingScore = false
    @State private var scoreTitle = ""
    @State private var userScore = 0
    @State private var questionCounter = 1
    
    @State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
    
    @State private var correctAnswer = Int.random(in:0...2)
    
    var body: some View {
        ZStack {
            RadialGradient(stops: [
                .init(color: Color(red: 0.1, green: 0.2, blue: 0.45), location: 0.3),
                .init(color: Color(red: 0.76, green: 0.15, blue: 0.3), location: 0.3)
            ], center: .top, startRadius: 200, endRadius: 700)
            .ignoresSafeArea()
            
            VStack {
                Spacer()
                Text("Guess the Flag")
                    .font(.largeTitle.bold())
                    .foregroundColor(.white)
                Spacer()
                Spacer()
                VStack(spacing: 15) {
                    VStack {
                        Text("Tap the flag of")
                            .foregroundStyle(.secondary)
                            .font(.subheadline.weight(.heavy))
                        Text(countries[correctAnswer])
                            .font(.largeTitle.weight(.semibold))
                    }
                    
                    ForEach(0..<3) { number in
                        Button {
                            flagTapped(number)
                        } label: {
                            Image(countries[number])
                                .renderingMode(.original)
                                .clipShape(Capsule())
                            .shadow(radius: 5)                    }
                    }
                }
                
                .frame(maxWidth: .infinity)
                .padding(.vertical, 30)
                .background(.ultraThinMaterial)
                .clipShape(RoundedRectangle(cornerRadius: 20))
                
                Spacer()
                Spacer()
                
                Text("Score: \(userScore)")
                    .foregroundColor(.white)
                    .font(.title.bold())
                
                Spacer()
                
            }
            .padding()
        }
        .alert(scoreTitle, isPresented: $showingScore) {
            if questionCounter != 8 {
                Button("Continue", action: askQuestion)
            } else {
                Button("Restart", action: restartGame)
            }
        } message: {
            if questionCounter != 8 {
                Text("Your score is \(userScore)")
            } else {
                Text("You ended the game with a score of \(userScore). Press the restart button to restart the game.")
            }
        }
    }
    
    func flagTapped(_ number: Int) {
        if number == correctAnswer {
            scoreTitle = "Correct! That's the flag of \(countries[number])"
            userScore+=100
        } else {
            scoreTitle = "Wrong! That's the flag of \(countries[number])."
            userScore-=100
        }

        showingScore = true
    }
    
    func askQuestion() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        questionCounter+=1
    }
    
    func restartGame() {
        countries.shuffle()
        correctAnswer = Int.random(in: 0...2)
        questionCounter = 1
        userScore = 0
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I’ve seen that there are generally multiple ways to program around a certain problem/challenge. My solution works for the challenge and I don’t think there’s anything particularly wrong with it, but it’s not elegant. =I would love to see some other solutions to this challenge to learn from those as well.

Possible additions

In the third challenge, Paul tells us to create an alert judging their score. I wasn’t sure how to interpret that, so I chose to work on implementing the solution in a basic form first. Later on, I did add if else statements to the message to print a different message depending on the score.

This part has now been refined to be much more elegant! Using if statements here isn’t ideal, so instead, we’ve put them into a function. You’ll find the refined version a bit further down.

.alert(scoreTitle, isPresented: $showingScore) {
            if questionCounter != 8 {
                Button("Continue", action: askQuestion)
            } else {
                Button("Restart", action: restartGame)
            }
        } message: {
            if questionCounter != 8 {
                Text("Your score is \(userScore)")
            } else if userScore <= 0 && questionCounter == 8 {
                Text("You ended the game with a score of \(userScore). You can do better.")
            } else if userScore > 0 && userScore <= 200 && questionCounter == 8  {
                Text("You ended the game with a score of \(userScore). Not bad. Keep improving!")
            } else if userScore > 200 && userScore <= 400 && questionCounter == 8 {
                Text("You ended the game with a score of \(userScore). Pretty good!")
            } else if userScore > 400 && userScore <= 600 && questionCounter == 8 {
                Text("You ended the game with a score of \(userScore). Well done!")
            } else if userScore > 600 && questionCounter == 8 {
                Text("You ended the game with a score of \(userScore). You're a flag master!")
            }
        }

A few other possible additions to this iteration of the Guess the Flag app I thought up were a game over alert when your score dips below zero and making the app a bit more difficult by adding more countries to our countries array as well as adding more choices per question.

If I find the time, I will look into implementing these changes and perhaps make a separate post about it. That’s a bit difficult right now, however, because between spending a few hours a day on the course and the recap while working full time, and having other hobbies and obligations, is tough. I’ll do my best though!

Update with new refinements and additions

Thanks to user Obelix from the Hacking with Swift forums, I was able to get some great advice and refine my code. Instead of using an if statement to control what alert was being shown, I’ve now added a second alert with a new Boolean variable that’s being watched.

@State private var gameOver = false
.alert("Game Over", isPresented: $gameOver) {
            Button("New Game", action: restartGame)
        } message: {
            Text("Your final score is: \(userScore).")
        }

The check for whether to set $gameOver to true happens in the flagTapped function.

if questionCounter == 8 {
            gameOver = true
            showingScore = false // If not set to false, an alert pops up in the new game while no answer is selected.
        }

The restartGame function has been refined as well, by calling the askQuestion function instead of recycling code.

    func restartGame() {
        //        countries.shuffle()
        //        correctAnswer = Int.random(in: 0...2)
        askQuestion()
        questionCounter = 1
        userScore = 0
    }

Since I was on a bit of a roll, I added a new Text view to display to the user at which question the are currently.

VStack {
                Spacer()
                Text("Guess the Flag")
                    .font(.largeTitle.bold())
                    .foregroundColor(.white)
                Text("Question \(questionCounter) of 8")
                    .font(.subheadline)
                    .foregroundColor(.white)

}

Finally, I added some much needed refinements to the message for the $gameOver alert.

First, I removed all else if statements from the alert. Then, I created a new function called judgeScore that returned a String and put the else if statements in there. Lastly, I called that function inside the message, to display the correct String. This could also have been done using a Switch statement.

    func judgeScore() -> String {
        var scoreMessage = ""
        if userScore <= 0 {
            scoreMessage = "You ended the game with a score of \(userScore). You can do better."
        } else if userScore > 0 && userScore <= 200 {
            scoreMessage = "You ended the game with a score of \(userScore). Not bad. Keep improving!"
        } else if userScore > 200 && userScore <= 400 {
            scoreMessage = "You ended the game with a score of \(userScore). Pretty good!"
        } else if userScore > 400 && userScore <= 600 {
            scoreMessage = "You ended the game with a score of \(userScore). Well done, you're close to perfection!"
        } else {
           scoreMessage = "You ended the game with a score of \(userScore). You're a flag master!"
        }
        return scoreMessage
    }
        .alert(scoreTitle, isPresented: $showingScore) {
            Button("Continue", action: askQuestion)
        } message: {
            Text("Your score is \(userScore)")
        }
        
        .alert("Game Over", isPresented: $gameOver) {
            Button("New Game", action: restartGame)
        } message: {
            Text(judgeScore())
        }

That’s it for day 22. I hope you enjoyed this extensive recap with my solutions to the Guess the Flag challenges, as well as the updates and refinements I made. Tomorrow, we’ll dive into our third project. So, as always, it’s time to recharge and march on! See you tomorrow!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 99

100 Days of SwiftUI – Day 77

100 Days of SwiftUI – Day 14 – Optionals

100 Days of SwiftUI – Day 79