UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 22 - Help with Guess the Flag app

Forums > 100 Days of SwiftUI

Hi!

I would appreciate some input on my solutions for the challenges of day 21. Especially the third challenge was tricky and without reading the hint, I solved it in a different way. I couldn't figure out a way to solve it in the way Paul alludes to with his hint, however.

  1. Add an @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.

  2. When someone chooses the wrong flag, tell them their mistake in your alert message – something like “Wrong! That’s the flag of France,” for example.

@State private var userScore = 0
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
  1. Make the game show only 8 questions, at which point they see a final alert judging their score and can restart the game.

I started with adding a question counter variable.

    @State private var questionCounter = 1

Then, I incremented that counter by 1 every time the askQuestion function was called.

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

I wrote a function called restartGame that resets the game to its initial state.

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

Then, instead of adding a second alert, I used and if statement to control which alert and message where being shown.

        .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.")
            }
        }
    }

This resulted at the final alert before the restart looking like this:

I would love to hear what you think! After reading Paul's hint, I tried creating a second alert that watched a new Boolean property and using an if statement to control which alert was shown, however I could not get that to work so I assume I took the wrong approach somewhere. An example on how to implement a second alert would also be greatly appreciated.

2      

@CodeMaverick slung some code resulting in unintended side effects:

...snip... instead of adding a second alert, I used an if statement to control which alert and message where being shown.
and
... snip... I would love to hear what you think! After reading Paul's hint, I tried creating a second
alert that watched a new Boolean property and using an if statement to control
which alert was shown, however I could not get that to work so
I assume I took the wrong approach somewhere.

Remember, SwiftUI is declarative, not procedural. Think long and hard before inserting "if statements" to control which alert and messages to show. Instead, DECLARE the conditions when an alert should be displayed. You're in charge!

If you were on my coding team, we might share the following with you during a code review:

Keep it simple! You're not getting the correct results? Perhaps you're trying to be too clever? For example:

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

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

Avoid Swiss Army Knife functions

So try not to insert all your alert dialog box options into ONE dialog box. Instead consider having smaller chunks of code that implement one function. That is, have one dialog box that displays the score after tapping a flag. That's all it does. Make that code do ONE JOB and make the code simple to read!

Next, create a second dialog box that displays JUST THE FINAL SCORE and a RESET button. That's all it does. Make that code do ONE JOB and make the code simple to read!

So you might have code like this:

.alert(scoreTitle, isPresented: $showingScore) {
    Button("Continue", action: askQuestion)
}
message: { Text("Your score is \(userScore)") }

.alert("Game Over", isPresented: $gameOver) {
    Button("New Game", action: restartGame) // <- Self explanatory.
}
message: {
    // New Game button is self explanatory.
    // No need to explain: "Press the NEW GAME button to begin a NEW GAME."  
    Text("Your final score is:  \(userScore) out of 1000.") 
}

So, now you need to review your game logic. When do you set the $gameOver variable to true ?

Please report back and let us know how you solved this!

3      

Hi Obelix,

Thank you so much for your extensive reply! After seeing your example, I realize that I was so close to figuring this out myself without if statements the first time arround after reading Paul's hint. I got errors when adding a second alert, where I didn't this time. I couldn't figure it out before, but I think I just had to check my opening and closing braces.

If I remove a brace, I get the same error:

Closure containing a declaration cannot be used with result builder 'ViewBuilder'

Moving on though! Your advice has helped a lot and I think I implemented a better solution now. This is the alert:

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

As for when I set gameOver to true, I do that in the flagTapped function.

    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

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

I have a question about recyling the askQuestion function in the restartGame function. The reason I didn't do that the first time around, was because askQuestion also increments the questionCounter where it is not needed in the restartGame function. Is this a problem? Or does it not matter because restartGame also sets the counter back to 1 right after we call askQuestion?

I've also added a few small refinements to further hone my skills a bit. First, I added a Text view to show at which question number we are:

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

Then, I created a new function called judgeScore. Paul tells us:

Make the game show only 8 questions, at which point they see a final alert judging their score and can restart the game.

So for fun:

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, your close to perfection!"
        } else {
           scoreMessage = "You ended the game with a score of \(userScore). You're a flag master!"
        }
        return scoreMessage
    }

The alert watching gameOver would then look like this:

.alert("Game Over", isPresented: $gameOver) {
            Button("New Game", action: restartGame)
        } message: {
            Text(judgeScore())
        }

I assumed using else if statements in a function like this is fine, because the function does just one thing, but please do correct me if I'm wrong.

Sorry for moving off the original topic a bit by the way. I'm really enjoying the course so in my excitement I just rambled on a bit.

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Day 21?

Please remember: There are no right or wrong answers! If you learned and your code compiles, that's a win! You will pick up tricks and better coding practices along the way. Be sure to revisit your old code when you're near day 70. Then again on day 90. You'll see what I mean.

Keep Coding!

Here's an example that other coders might see, but is a skill you're still developing. (Ha! Unintended programming pun!)

// Try this code in Playgrounds. Compare to your judgeScore function.
// Which is easier to read? Which one would be easier to maintain?

let userScore = Int.random(in: -200...900)  // <- this is just for testing in playgrounds
// Computed property vs function()
var judgeScore: String {
    var scoreMessage = ""
    switch userScore {
        case ...0 :
            scoreMessage = "You scored \(userScore). You can do better."
        case 1...200 :
            scoreMessage = "You scored \(userScore). Not bad. Keep improving!"
        case 201...400 :
            scoreMessage = "You scored \(userScore). Pretty good!"
        case 401...600 :
            scoreMessage = "You scored \(userScore). Well done, you're close to perfection!"
        default :
            scoreMessage = "You scored \(userScore). You're a flag master!"
    }
    return scoreMessage
}

judgeScore  // What does Playground calculate?
// Also note:  You're <not your> close to perfection.

3      

@CodeMaverick is really digging in...

I have a question about recyling the askQuestion function in the restartGame function.
The reason I didn't do that the first time around, was because askQuestion also increments
the questionCounter where it is not needed in the restartGame function. Is this a problem?

If your code compiles and the game works, then it's not a problem.

But, that's not to say when you join a team of programmers you'll have a comment-free code review!

See my previous comment about Swiss Army Knife functions. Then have a discussion with yourself. What is the main user story behind the askQuestion function?

Maybe incrementing the number of questions asked is not a key detail in the askQuestion function? If not there, where would be a good place to increment the question counter? If you answer this question, it may also answer your question about including the function in the restartGame function. Also a peer review might add these comments: Maybe askQuestion isn't the best name for this function? You're really picking three random flags, right? You're setting up the next question? Consider a more descriptive name, perhaps?

See how these are related? You may have overloaded a function with code that belongs somewhere else. Where in your game do you take some action that indicates you've finished a round? Something to think about, no?

3      

That's my bad, it's supposed to be day 22. Just edited it.

I appreciate the food for thought, both for using a computed property with switch and how to not overload a function so that it only serves a single purpose. That is definitely something I'll be taking a look at for this and upcoming projects!

I've credited you for the refinements to my code on my blog (which no one reads, it's just where I keep track of my progress). I hope that's okay, otherwise I'll remove it.

I'm sure I'll be back on these forums soon looking at the upcoming days, so hopefully we can engage more, it was extremely helpful and I learned a lot. Thank you!

2      

A new coders asks:

Is there anyone alive to help, guys?

First, thanks for asking.

Second, while related to the original thread, this feels like it should be its own topic. Primarily, so you can mark another's response as Solved, if it indeed answers your question.

I have prepared an answer, and some clarifying code. Create a new post, if you please!

3      

Second, while related to the original thread, this feels like it should be its own topic. Primarily, so you can mark another's response as Solved, if it indeed answers your question.

I have prepared an answer, and some clarifying code. Create a new post, if you please!

No offence @Obelix. I thought it makes no sense to make a new topic while this already exists that's why I decided just to continue it with my questions. Although if there's no problem about creating new "HELP" threads about same tasks - sure I can make a separate post. Just didn't want to face with negative feedbacks. My bad.

2      

You had multiple questions in one post.

The original post was already answered, and the answers to your post might be different. This was only a suggestion on my part to create a new post. You don't have to listen to me!

2      

There's something that I couldn't do here; The flags, once the program start with three flags those flags keep showing at the same order, the only thing that is changing is the question, it needs to restart the program to change the three flags.

I tryed to add an action: restartGame to the alert but it restarted all the program, including the score:

.alert(scoreTitle, isPresented: $showingScore) {

        Button("Continue", action: askQuestion)

        Button("Restart", action: restartGame)

    } message: {

        Text("Your score is \(userScore)")

    }

I also tried to make a new alert but it didn't work.

Now I think I should add something here, but I don't know exactly what it is:

ForEach(0..<3) { number in

                Button {

                    flagTapped(number)

                } label: {

                    Image(countries[number])

                        .renderingMode(.original)

                        .clipShape(Capsule())

                        .shadow(radius: 20)

                }

            }

2      

After a flag is tapped, you should have a function called something like askQuestion() that handles asking the new question. When the game first starts, we are shuffling our array to make a random three end up at the first 3 positions of the array. But if you don't shuffle the array again in the askQuestion() function, then the same 3 flags will be in the first 3 positions in the array.

3      

I finally succeeded, I did things slightly different, using 2 functions and 2 variables and 8 @state private variables. It's working as it's supposed to, but even though, I still can't understand one thing: When a question is being asked, for example: Guess the flag of Monaco then 3 flags are shown underneath, how does the computer understand that one of the flags must be the flag of Monaco? I mean what exactly did we write to make sure it doesn't bring a name from the list that is not necessary one of those three pictures, why it isn't making this mistake? What to write to make him do this mistake? Somebody please help me understand that part.

2      

@Hashem is mixed up about mixing up flags.

This is a simplified version of the flag game.

var nordicGods = ["Thor", "Loki", "Odin", "Frigg", "Freya"]

let answer = 3

print("\( nordicGods[answer]) is the correct God."). // <- prints "Frigg is the correct God."

Guessing the God in this Game would Get boring. Greatly boring.

Instead, first mix the Gods into a random order!

// Run this in Playgrounds to shuffle and see the new order
let gameGods      = nordicGods.shuffle() // <- Mix them into random order.
print(gameGods) // <- New shuffled order
let correctAnswer = Int.random(in: 0...2)  // <- Pick a number 0, 1, or 2

// Now show JUST THE FIRST THREE in your guessing game board.
ForEach( 0..<3, id:\self) { choice
      Text( "\(gameGods[choice])")  //  <- simple text naming the God 
}
print("Game selected random number: \(correctAnswer)") // <- Random number
print("God at this index is: \(gameGods[correctAnswer]") // <- Grab from shuffled Gods array.

Your correctAnswer constant only PICKs one of the first three Gods. And remember, you are only showing the first three Gods from the shuffled array.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.