知識王4ni?
[iOS App Development]練習製作選擇題小遊戲
這次練習的題目是多重選項選擇題,題庫來源為巴哈上由熱心網友們所整理的知識王題庫,難度從小學生到博士生才會的都有,全部挑了80題,答案也是由網友悉心研究出來的。
遊戲規則:
- 每次作答題目會隨機送80題中挑選10題,故整體難易度也會是隨機的。
- 簡單的題目答對得3分,中等得5分,最難的得10分,由於題目隨機挑選,所以每次作答的滿分不同。
- 每題有10秒鐘時間可以作答,若10秒倒數完沒有回答會自動判定答錯。
- 全部作答完畢後計算總分。
介面規劃
Demo
影片內容包含答對、答錯、超時以及再玩一次的示範。
前置作業
・題目匯入:因為題庫有80題,先在Airtable上建好檔後再匯入。詳細的教學可以參考以下連結。
因為只是選擇題,資料結構相對單純,不至於使用到陣列來匯入。欄位和資料型態請參考下圖,可以看到question、choices和answer都是text,只有point是number:
在choices部分我用,先分隔所有選項,待資料匯入後再用字串分割的方式轉為陣列,方便之後顯示選項時每次都是隨機排列。
建立好table之後從Airtabel匯出csv,再貼到專案的Asset裡面,將資料準備好。
・建立Question struct: csv資料準備好之後,新增question檔案,並且匯入CodableCSV套件,這個套件在於能將csv轉換成開發使用的資料。
下半部extension是解析csv的程式,上半部struct則是希望轉換之後每一筆資料對應csv欄位的型態。
・全域變數:接著可以回到ViewController建立全域變數。
//將問題從Question中整理出來,是一個由Question struct組合成的陣列
var questions = Question.data//題目順序
var index = 0//得分
var score = 0//計時器
var timer = Timer()//倒數的起點
var counter = 1000
counter變數之所以設定為1000是因為希望顯示剩餘時間的progress bar是每0.01秒就更新一次;亦即本來是1秒才會變動一次,為了使progress bar能做到連續的變動,變成1秒會變動100次。
・IBOutlet:可以搭配前面的介面規劃和Demo影片來理解。
//問題題目label
@IBOutlet weak var questionLabel: UILabel!//分數label
@IBOutlet weak var scoreLabel: UILabel!//包含顯示問題、題數、倒數bar、選項按鈕、下一題按鈕的view
@IBOutlet weak var contentView: UIView!//倒數bar
@IBOutlet weak var counterProgressBar: UIProgressView!//題號label
@IBOutlet weak var questionNumberLabel: UILabel!//選項buttons拉成outlet collections
@IBOutlet var choiceButtons: [UIButton]!//作答完的訊息label
@IBOutlet weak var messageLabel: UILabel!//下一題button
@IBOutlet weak var nextButton: UIButton!//開始測驗button
@IBOutlet weak var newGameButton: UIButton!//再玩一次button
@IBOutlet weak var playAgainButton: UIButton!
介面規劃沒有顯示出來的有newGameButton和nextButton,前者是只有在進入App時第一次遊玩開始前才會顯示在螢幕正中央,而後者是每次答完一題後會顯示在螢幕的右下角。
函式說明
showQuestion(_ index: Int):主要功能在於顯示題號、題目和選項。細部說明如下程式碼註解。
func showQuestion(_ index: Int) {
//取出問題
let question = questions[index]
//取出將選項從字串分割為array
let choicesSubstring = question.choices.split(separator: ",")
//因為分割後型別為Substring要再分別轉為String才可用
var choices = [String]()
for choice in choicesSubstring {
choices.append(String(choice))
}
//將選擇隨機排列一次
choices.shuffle()
//依序設定選擇button的title
questionLabel.text = question.question
for i in 0...3 {
choiceButtons[i].setTitle(choices[i], for: .normal)
}
//更新最上面的題號label
questionNumberLabel.text = "第\(index + 1)/10題"
}
showAnswer(_ index: Int):主要功能為答錯或超時時會顯示該題的正確答案,細部說明請參考下面程式碼註解。
func showAnswer(_ index: Int) {
//查詢目前題目的答案
let answer = questions[index].answer
//找到答案的button加上紅色粗外框,並且所有選項button都不能按
for choiceButton in choiceButtons {
if choiceButton.title(for: .normal) == answer {
choiceButton.configuration?.background.strokeWidth = 3
choiceButton.configuration?.background.strokeColor = UIColor.red
}
choiceButton.isUserInteractionEnabled = false
}
}
這邊按鈕使用isUserInteractionEnabled的屬性來控制是因為不希望樣式受到影響,如果使用isEnable為false來控制button的外觀會改變。
initialGame():遊戲初始化,主要是重置變數並且將畫面恢復到可以操作的狀態。
updateTime():計時器selector使用的函式,作用為更新倒數bar的progress位置。
@objc func updateTime() {
//每次更新counter都減1
counter -= 1
//progress bar的progress單位切分到小數點後3位才能做出連續性的變化
counterProgressBar.progress = Float(counter) / 1000
//如果counter倒數到0計時器就停止計時,並顯示正確答案
//判斷是否為最後一題來更新介面
if counter == 0 {
timer.invalidate()
showAnswer(index)
messageLabel.isHidden = false
if index != 9 {
messageLabel.text = "超過作答時間"
nextButton.isHidden = false
} else {
messageLabel.text = "答題結束,恭喜您得了\(score)分!"
contentView.isUserInteractionEnabled = false
playAgainButton.isHidden = false
}
}
}
IBAction
有三個地方需要設計IBAction,分別是選項的chooseButton、還有切換到下一題的nextQuestionButton以及開始測驗與再玩一次的playButton。