WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: How do I create random answers for the edutainment app?

Forums > 100 Days of SwiftUI

I'm currently working through Day 35 for the edutainment app. To me this app feels like it could be really similar to GuessTheFlag when it comes down to the game logic, so I'm working in that direction. However, I'm having a hard time translating some of the logic from GuessTheFlag to this project for a number of reasons. So far, this is what I'm trying to accomplish:

    VStack(spacing: 20) {
            Text("What is \(selectedTable) x \(timesTable)")
                .padding()
                .font(.largeTitle)

            HStack(spacing: 10) {
                ForEach(0..<3) { answer in
                    Button {
                        // code
                    } label: {
                        Rectangle()
                            .stroke(Color.blue, lineWidth: 3)
                            .frame(maxWidth: 100, maxHeight: 100)
                            }
                    }
                }
            }
            .padding(.horizontal)

My goal is to generate three random answers for a user to choose from (they each appear in a blue box), while only one of them is the correct answer. Again this is super similar to GuessTheFlag but I'm stuck on implementing it. How do I generate both the random answers and the correct answer for a user to select?

1      

@swiftdoc is mixing what the program does with what the program shows....

I'm having a hard time translating some of the logic from GuessTheFlag to this project
My goal is to generate three random answers for a user to choose from
while only one of them is the correct answer

Take your big problem and break it down into several, smaller solvable problems. To make it easier, put each problem in a separate room in your house, and only solve the problem in that room. Don't try to solve two problems at once.

Problem #1

You have two numbers, selectedTable and timesTable. You want to multiply these two numbers and have a correct answer. First observation is that in maths, you typically multiply operands together. So consider renaming your variables to operand1 and operand2. Just a suggestion that may help clarify your logic.

Rather than having a bunch of logic polluting your View (what you see) consider moving all the logic for your answer to brand new struct!

Game Logic

Your game logic can all be neatly packaged into a convenient struct. When you create a new struct it will generate two random operands, a display string, and three random guesses. All the logic for your quiz is in this struct.

// Put your game play LOGIC in a separate struct!
// Keep your game play logic OUT of the view.
struct Problem {
    private var operand1 = 0  // These are private and generated
    private var operand2 = 0  // by a new game function.

    // Your solution may need different logic here
    mutating func newProblem() {
        operand1 = Int.random(in: 3...12)
        operand2 = Int.random(in: 3...12)
    }
    // Only possible correct answer
    var correctAnswer: Int { operand1 * operand2 }

    var problemString: String {  // Convenience string
        String("\(operand1) X \(operand2)")
    }

    // Array of guesses
    // You can make this more challenging
    var guesses: [Int] {
        [
        correctAnswer + 1,
        correctAnswer,
        correctAnswer - 1,
        Int.random(in: correctAnswer-5...correctAnswer+5)
        ].shuffled()
    }
}

Problem #2

Now that you've created a struct you have to grab the operands, solution, and possible guesses and use them in a View. I will leave this to you to noodle through.

// One function creates two new random operands.
// The computed variable calculates the only correct answer
/// Your solution may need different logic here
    mutating func newProblem() {
        operand1 = Int.random(in: 3...12)
        operand2 = Int.random(in: 3...12)
    }
    // Only possible correct answer
    var correctAnswer: Int { operand1 * operand2 }

Problem #3

Now that you have a struct initialised with two operands, it's time to build the ProblemView. You'll want to display the two operands as a proper multiplication problem. Then you'll show a Picker with the possible choices.

Possibly you'll want to show a success message when the player selects the correct answer.

Maybe the New Problem button is inactive until the correct answer is found? Your call.

What did you learn?

First, pull your logic into structs whenever possible. This keeps your logic separate from what you want to display.

Second remember that SwiftUI is declarative. Re-read that statement. It's declarative.

Declare what you WANT TO SEE.

I want to see three options from this array of possibleGuesses.
Don't write logic in the View code saying print the value of operand1 times operand2. Declare a computed variable named correctAnswer and tell SwiftUI to display the correctAnswer! Declare your intentions.
I want to verify the user's guess matches the one and only correctAnswer.

Don't multiply the two operands in your View structure. The View should just display data. Do the calculations in your Problem struct. Use the power of structs to encapsulate your game logic. Keep it separate from your view logic.

Full Example

struct ProblemView: View {
    @State private var gameProblem = Problem()
    @State private var playerGuess = 0

    private var isCorrectResponse: Bool {
        playerGuess == gameProblem.correctAnswer
    }

    var body: some View {
        VStack {
            Text(gameProblem.problemString)
            Picker(selection: $playerGuess) {
                ForEach( gameProblem.guesses, id:\.self) { guess in
                    Text("\(guess)").tag(guess)
                }
            } label: {
                Text("Your answer:")
            }.pickerStyle(.segmented)
            if isCorrectResponse {
                Text("That's the right answer!").font(.largeTitle)
            }
        }
        .padding()
        Button("New Problem") {
            gameProblem.newProblem() // Ask logic struct for a new problem
        }.buttonStyle(.borderedProminent).padding(.top)
            .disabled(!correctResponse)
    }
}

// ======================================= Problem Game Logic
// Put your game play LOGIC in a separate struct!
// Keep your game play logic OUT of the view.
struct Problem {
    private var operand1 = 0  // these are passed in from the
    private var operand2 = 0  // user interface

    mutating func newProblem() {
        operand1 = Int.random(in: 3...12)
        operand2 = Int.random(in: 3...12)
    }
    // Only possible correct answer
    var correctAnswer: Int { operand1 * operand2 }

    var problemString: String {
        String("\(operand1) X \(operand2)")
    }

    // Array of guesses
    // You can make this more challenging
    var guesses: [Int] {
        [
        correctAnswer + 1,
        correctAnswer,
        correctAnswer - 1,
        Int.random(in: correctAnswer-5...correctAnswer+5)
        ].shuffled()
    }
}

Keep Coding

2      

Homework

There's a bug in the Picker logic!

How can you solve it?? Please return here with your solution.

2      

Homework #2

The ProblemView should just reflect the variables that are in the Problem struct.

But in the sample code above, I created a computed variable in the View to determine if the user's tapped selection is the correct answer.

This violates one of the lessons from above namely, keep your game logic in a separate struct.
Use the ProblemView to simply interact with the user.

Can you think of a way to move the isCorrectResponse calculation OUT of the view and into the Problem struct?

Keep Coding

Please return here and let us know how you solved this.

2      

Thank you!

1      

The solution for the picker bug was using an onAppear modifier that performed problem.newProblem() to refresh the UI so that a new problem would be presented to the user. Otherwise the game would always start with "What is 0 X 0?"

Further, the @State private var playerGuess variable was stuck on = 0, which affected how the user saw the picker selctions. The above solution solved that as well.

1      

To get the isCorrectResponse computed property out of the ProblemView, I had to first copy and paste the computed property into the Problem struct. Once I did that, I created the variable var playerGuess = 0 so that it could hold the player's initial selection when a new game started. In this case the selection would be nothing.

Another step was to cut out the gameProblem part of the property because it was no longer a part of the ProblemView struct. The property would now read as var isCorrectResponse: Bool { playerGuess == correctAnswer }. Given that everything was now placed inside the Problem struct, I no longer needed the gameProblem variable.

To have this reflect in the UI, I added problem.isCorrectResponse under the picker label and created a binding to the Problem struct inside the Picker(selection:.

I may have missed something here, but your responses were very helpful and I now have a much better development process :)

1      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Reply to this topic…

You need to create an account or log in to reply.

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.