Looping with buttons


(Faris) #1

Hi,
I might be breaking some rule with my message but please excuse me for my first time login!

After months of studying swift, I thought I'm able to start my first simple app! But seems the reality is different from my thoughts!

What I need to do is a testing app, the interface has 3 buttons, the app will ask a question e.g. 3x6, the right answer goes to one of the buttons in random order, while the other buttons will be filled with other random numbers!

I tried to do it by taking two numbers from two random generators (num1 and num2), take these two numbers to say(3 and 6) and display it in one of the buttons. At this point where I got messing around, first, how to take the last two numbers and fill it in remaining buttons, second, I have a vague idea is to use a loop which representing the buttons where I got lost when I tried it :(

I have struggled with this problem for a few weeks trying different approaches but at the end, I go to the same problems which I mentioned above.

Your help highly appreciated!

Thanks

Faris


(Jeremy David Giesbrecht) #2

Welcome to the forums!

It can be helpful when asking questions to post the code you do have, even if it doesn’t work. It helps the rest of the world understand the question.

But since you haven’t, I’ll just post what I would write if I were trying to accomplish that. Hopefully the answer to your question is in there somewhere. For the sake of your own satisfaction, don’t just copy and paste what I did. Learn from it, but then stick with what you have and adjust it until it works. If you have Xcode, you can watch the following execute by pasting it into a playground:

/// Defines the highest factor we want to ask about.
/// (Since 45 645 648 906 584 × 456 048 564 485 would probably be cruel.)
let maximumFactor = 10

/// The highest product that will ever be correct.
let maximumProduct = maximumFactor * maximumFactor

/// A multiple choice‐option.
struct MultipleChoiceOption {
    /// The number to display as the option.
    let number: Int
    /// Whether or not the option is the correct one.
    let isCorrect: Bool
}

/// A multiplication question.
struct MultiplicationQuestion {
    
    /// The multiplier (first factor).
    let multiplier: Int
    /// The multiplicand (second factor).
    let multiplicand: Int
    /// The product (answer).
    let product: Int
    /// The options to choose from.
    let options: [MultipleChoiceOption]
    
    /// Creates a question from two factors.
    init(multiplier: Int, multiplicand: Int) {
        
        self.multiplier = multiplier
        self.multiplicand = multiplicand
        
        let product = multiplier * multiplicand
        self.product = product
        
        var options: [MultipleChoiceOption] = []
        let correct = MultipleChoiceOption(number: product, isCorrect: true)
        options.append(correct)
        while options.count < 3 { // Keep adding incorrect answers until there are 3.
            let random = Int.random(in: 0 ... maximumProduct)
            if !options.contains(where: { $0.number == random }) { // We don’t want duplicates.
                let incorrect = MultipleChoiceOption(number: random, isCorrect: false)
                options.append(incorrect)
            }
        }
        self.options = options.shuffled()
    }
    
    /// Creates a random question (limited by `maximumFactor`).
    static func random() -> MultiplicationQuestion {
        let multiplier = Int.random(in: 0 ... maximumFactor)
        let multiplicand = Int.random(in: 0 ... maximumFactor)
        return MultiplicationQuestion(multiplier: multiplier, multiplicand: multiplicand)
    }
}

// Say there was an array of 3 buttons somewhere else like this, but with buttons not text:
let buttons: [Any] = ["Button", "Button", "Button"]

/// Displays a question.
func ask(_ question: MultiplicationQuestion) {
    // This just prints it as text output.
    // Your application will need to display these in UI elements.
    print("\(question.multiplier) × \(question.multiplicand)")
    print("")
    
    for (option, button) in zip(question.options, buttons) {
        // You can use the corresponding `button` here,
        // but since ours are just dummies, we won’t.
        print("• \(option.number)?")
    }
}

let question = MultiplicationQuestion.random()
ask(question)

print("")

/// This checks the answer.
///
/// Interface buttons will have to call it with their respective index.
func answer(_ question: MultiplicationQuestion, withOption optionIndex: Int) {
    let option = question.options[optionIndex]
    print(option.number)
    print("")
    
    if option.isCorrect {
        print("\(question.multiplier) × \(question.multiplicand) = \(option.number) ✓")
    } else {
        print("\(question.multiplier) × \(question.multiplicand) ≠ \(option.number) ✗")
        print("\(question.multiplier) × \(question.multiplicand) = \(question.product)")
    }
}

// Buttons would do the following:
// (But because the playground doesn’t have buttons, we’ll always answer with the first one.)
answer(question, withOption: 0)

(Faris) #3

Hi Jeremy,

I really appreciated your effort to send me this code which really explains my needs and I'm sorry to reply you a bit late due to certain circumstances I have in the last few weeks!

I went through the code and I had few issues. first, "type 'Int' has no member 'random'", in any of the following instruction with a fashion of e.g.

    let multiplicand = Int.random(in: 0 ... maximumFactor)"

I have to change it to let multiplicand = Int(arc4random_uniform( UInt32(maximumFactor))), however, hope my change is right :)

Two things left for me:

1- let random = Int(arc4random_uniform( UInt32(maximumProduct))) which gave me this warning: "Initialization of immutable value 'random' was never used; consider replacing with assignment to '_' or removing it". I know because there no other place has random to use!

2-****self.options = options.shuffled() "Value of type '[MultipleChoiceOption]' has no member 'shuffled'"

Thanks and kind regards,

Faris


(Jeremy David Giesbrecht) #4

It sounds like you are using an old version of Swift.

My example assumes Swift 4.2 or higher (which is part of Xcode 10 or higher).

Simply updating will be vastly easier than to writing your own stand‐in for the randomization API that 4.2 provides.


(Faris) #5

Thanks, Jeremy! Yep, my version was 4.1 and Xcode was 9+. Both now updated and the playground worked ok.
I will create a new project and see how it goes :)

Thanks again Jeremy,

Faris


(Faris) #6

Hi Jeremy,

I opened a new project in order to translate your code into it, I got this error: "Cannot use instance member 'maximumFactor' within property initializer; property initializers run before 'self' is available" for this instruction**: **let maximumProduct = maximumFactor * maximumFactor!

Does that mean I have to use it within a function or else?

I followed some stackoverview solutions, to be honest, I got lots, I think this is out of my knowledge at this stage :)

If possible can you please explain what is happening?

Your help highly appreciated.

Faris


(Jeremy David Giesbrecht) #7

The following are three minimal examples to illustrate what I think is going on. Note the slight differences in where and how maximumFactor is declared and used.


What I originally wrote:

// These are in the global scope,
// so they are global constants.
let maximumFactor = 10
let maximumProduct = maximumFactor * maximumFactor
// ✓ Global constants can reference each other,
// (but not circularly).

For more details see The Swift Programming Language under “Global and Local Variables”.


What it sounds like you have now:

struct MultiplicationQuestion {
    // These are inside a type declaration,
    // so they are instance properties,
    // which means each question instance stores a separate one.
    let maximumFactor = 10
    let maximumProduct = maximumFactor * maximumFactor
    // ✗ Instance properties cannot reference each other.
    // As the error says,
    // “property initializers run before 'self' is available”,
    // meaning when they are computed, the associated instance question
    // does not exist yet for them to ask it about its other properties.
}

For more details see The Swift Programming Language under “Stored Properties”.


An alternative you may be looking for:

struct MultiplicationQuestion {
    // These use the keyword “static”,
    // which makes them belong to the type itself,
    // not to any particular instance.
    // Static properties work much like a global constants,
    // but they are namespaced under the specific type,
    // in this case “MultiplicationQuestion”.
    static let maximumFactor = 10
    static let maximumProduct = maximumFactor * maximumFactor
    // ✓ Global constants can reference each other,
    // (but not circularly).
}
// If you want to use static properties from a different scope,
// you have to use their namespace.
let x = MultiplicationQuestion.maximumFactor

For more details see The Swift Programming Language under “Type Properties”.


(Faris) #8

Hi Jeremy,

Yep, the static solved the problem!

Regarding the buttons, I tried to follow the idea you mentioned, for somehow I have some error. I though alternatively I can do it in this way:

var button : UIButton = UIButton()

var x = 1

for i in 1...3

{

button = view.viewWithTag(i) as! UIButton

if (i == Int((answer(question, withOption: 0)))){

button.setTitle((answer(question, withOption: 0)), for: .normal)

}

else{

button.setTitle(answer[question], for: .normal)

x = 2

}

The idea is the tag determine the right answer and x looping in order to title the buttons

I got some error which I think something to do with the question index! I keep trying with some other approach.

Thanks, Jeremy.

Faris