Stuart Breckenridge

Dev Notes - Questions and Answers

As a little side project, I am rebuilding my long forgotten app Amazing Flag Quiz. I don’t have the original source code, so it’s all being done from scratch. My current task is working on the question and answer model. There are few things it needs to do:

  • generate an array of questions (each element a Question) based on the user’s selected continent (e.g. Europe, Africa…)
  • each Question must contain the correct answer and three wrong answers
  • the array of questions and each Question’s answers must be randomised.

Before getting into coding the app properly, I’ve thrown the below together in a playground.

I have an array of all countries:

let allCountries = ["Afghanistan", "Akrotiri", "Albania", "Algeria",...]

My Question class contains a correct answer and three wrong answers, held together in an Array:

final class Question {
    let correctAnswer:String
    var questionOptions = [String]()
    
    init(cAnswer:String, qOptions:[String]) {
        self.correctAnswer = cAnswer
        self.questionOptions = qOptions
    }
}

To track which game option the user picks, I’ve created an enum:

public enum GameOptions
{
    case All, Africa, Americas, Asia, Australasia, Europe, BritishEmpire, USStates
}

Finally, in order to create a list of questions, I’ve created a protocol with a default implementation:

protocol Questionable
{
    func questionsForOption(option:GameOptions) -> [Question]
}
extension Questionable
{
    /*
     Based on option, will generate and return an array of Question objects.
     
     - parameter option: GameOptions selected by user.
     
     - returns: [Question]
     */
    func questionsForOption(option:GameOptions) -> [Question]
    {
        switch option
        {
        case .All:
            var allQuestions = [Question]()
            for q in allCountries
            {
                allQuestions.append(Question(cAnswer: q, qOptions: populateOptions(q)))
            }
            
            // Shuffle 
            return GKRandomSource().arrayByShufflingObjectsInArray(allQuestions) as! [Question]
            
            /* ... */
        default:
            return []
        }
        
    }
    
    /*
     Populate answers for question.
     
     - parameter correctAnswer: String
     
     - returns: [String]
     */
    func populateOptions(correctAnswer:String) -> [String]
    {
        var countrySet = Set<String>()
        countrySet.insert(correctAnswer)
        
        // Get four unique random answers
        repeat {
            let i = GKRandomDistribution(lowestValue: 0, highestValue: allCountries.count - 1).nextIntWithUpperBound(allCountries.count - 1)
            countrySet.insert(allCountries[i])
        } while countrySet.count < 4
        
        // Add answers to an array
        var options = [String]()
        for c in countrySet{
            options.append(c)
        }
        
        // Shuffle answers
        return GKRandomSource().arrayByShufflingObjectsInArray(options) as! [String]
    }
}

In terms of usage, I need to generate questions and answers when my GameViewController appears on screen, as part of viewDidLoad, as shown below:

class GameViewController:UIViewController, Questionable {
    
    var list = [Question]()
    
    /* ... */
    
    override func viewDidLoad() {
        super.viewDidLoad()
        list = questionsForOption(.All)
    }
}

I’m satisfied with this solution: it’s less than 100 lines of code and is easy to follow.

I seem to remember this being much harder when I wrote Amazing Flag Quiz originally and I’m mindful of what Henri Cartier-Bresson once said:

Your first 10,000 photographs are your worst.

That applies to your first 10,000 lines of code as well.

Next up: presentation logic.


— Supported by —