我猜我猜我猜猜猜之Bulls & Cows
[ iOS App Development]使用Swift進行數學運算
以前的小孩沒有什麼3C也沒有隨手可得的網路,只有象棋、猜數字和踩地雷這些簡單燒腦的遊戲,不僅能殺時間還可以拿來訓練邏輯。
遊戲規則如下:
- 答案為4位不重複0~9整數,但user猜測時可以重複數字。
- 一次遊玩可以猜10次,猜10次都未猜中遊戲自動結束。
- 每次猜測都會紀錄猜測紀錄(user猜測的數字和?A?B)供user邏輯推理。
- 猜中答案(4A0B)和用完次數都會遊戲結束,並且詢問user是否要重玩。
除了規則之外,這個練習我給自己額外的挑戰是將四個數字拆成四個TextField,類似一些帳號登入時OTP(One-Time Password)驗證的介面;輸入上也結合了信用卡號碼輸入的巧思,輸入一個數字後會自動判斷並跳到下一格,使用者不需要手動去點下一個TextField才能編輯。
介面規劃
介面規劃和各個outlet的佈局請參考下圖:
前置作業
・使用struct來建立猜測紀錄類別:因為要顯示10筆猜測紀錄,所以練習使用struct類別物件來儲存紀錄,並作為更新介面的資料來源。
struct GuessingRecord {
var numberGuessed = [String]()
var countOfBMatch = Int()
var countOfAMatch = Int()
}
每次猜測後都會生成一個GuessingRecord類別物件,並且被賦予三個屬性:
numberGuessed — user猜的四個數字。
countOfBMatch — 與答案比對後符合B的個數。
countOfAMatch — 與答案比對後符合A的個數。
・全域變數和outlet
var answer = [String]()
var userInputs = [String]()
var timesOfTry = 0
var isPlaying = true
@IBOutlet var inputTextFields: [UITextField]!
@IBOutlet weak var guessButton: UIButton!
@IBOutlet var recordLabels: [UILabel]!
@IBOutlet weak var resultMessageLabel: UILabel!@IBOutlet weak var newGameButton: UIButton!
answer和userInputs都是字串的陣列,方便從TextField後直接比對;timesOfTry紀錄使用者猜的次數,isPlaying為user是否能繼續猜測,outlet可以參考前面的介面佈局。
・函式說明
generateAnswer():生成新答案的函式,遊戲每次開始都會呼叫。
check(userGuess: [String], answer: [String]) -> GuessingRecord:將user猜的數字和答案比對並產生出猜測紀錄的函式。
setGradientBackground():加上漸層背景,另外推薦給大家一個可以無腦挑出漸層色彩的配色網站。
setTextFieldBorderBottom(_ textField: UITextField):為指定的TextField加上底線,這是自己從其他地方找到一個不錯又不難理解的practice。
關鍵是將border的範圍全部壓在TextField的底部,所以座標y起點是整的TextField高度減去border width,然後整個layer的寬度會等於TextField的寬度,高度則是等於border width。
showResult(trial: Int, input: String, aMatch: Int, bMatch: Int ):將儲存的猜測紀錄顯示出來,參數包含trial猜測次數、input使用者猜測的所有數字、aMatch符合A的個數、bMatch符合B的個數。這邊希望做出來的text中”?A?B是紅色的,所以使用了NSMutableAttributedString來製作。
@objc func closeNumberPad():收鍵盤的函式,用在點選非view中TextField的區塊時會結束編輯,會在後面的viewDidLoad中新增的UITapGestureRecognizer用到。
viewDidLoad
viewDidLoad初始設定:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//設定漸層背景
setGradientBackground()
//生成答案
generateAnswer()
//將所有text設底線
for textField in inputTextFields {
setTextFieldBorderBottom(textField)
}
//新增tapRecognizer到view上
let tapRecognizer = UITapGestureRecognizer()
tapRecognizer.addTarget(self, action: #selector(self.closeNumberPad))
self.view.addGestureRecognizer(tapRecognizer)}
IBAction說明
・inputNumberTextField
因為數字有四位,故TextField也有四個,為了讓輸入時比較友善,增加以下功能:
- 1個TextField只能輸入一個數字,輸入完會自動跳到下一格編輯(如果有),且鍵盤不會收起來。
- 刪除某一格TextField的數字時會自動跳回前一格(如果有),且鍵盤不會收起來。
- 可以點選特定一個TextField跳著編輯。
可以先看一下demo畫面就大概知道道上述具體是什麼意思,過程中都只依賴鍵盤輸入來移動游標不用手指去逐一點選TextField。
4個TextField都是連到相同的IBAction程式碼:
分別介紹一下有用到的兩個方法:一個是字串的.suffix(n),會將字串從最後數過來n個字元擷取出來;另外是TextField的becomeFirstResponder()會使游標跳到指定的TextField上開始編輯。
・submitNumbersButton
這個Action的主要功能就是user按了”Guess”按鈕後就會收集並整理user的答案,並觸發比對函式進而更新此次猜測紀錄到到介面上。程式碼大綱為:
- 更新userInputs。
- 與答案比對生成GuessingRecord類別物件。
- 將GuessingRecord的資料顯示到介面上。
- 根據猜測次數判斷遊戲是否結束並更新介面。
・anotherGameButton
如果user猜中答案或者用完猜測次數就會遊戲結束,並顯示”Try again“的按鈕,按下後可以再玩一次。
DEMO
・猜對操作
・用完猜測次數操作