羽球計分板
[iOS App Development]if-else條件練習
去年奧運真的很精彩,許多選手表現得可圈可點,也透過運動凝聚了國人的心。尤其小戴最後幾輪的比賽簡直是在測試我的心血管是否健康,前陣子也跟風去學了一點羽球,所以決定來做羽球計分板囉!
再仔細研究了一下羽球的計分和判發球規則整理如下:
- 三局兩勝制。
- 單局先取得21分者可拿下一局,但如果雙方同時達到20分會進入平分(deuce)狀態,任一方必須領先另一方2分才能拿下該局;如果一直未能有人能領先另外一方2分,則到雙方皆29分時會取消平分狀態,如果有人先達到30分就可拿下該局。
- 第一局開始會先擲硬幣決定發球方,之後只要得方就能保有/爭奪發球權,第二局開球方以拿下上一局的一方取得發球權。
而這個計分板除了符合上述的計分規則外,還有以下功能:
- 比賽開始前可以編輯雙方的名稱,比賽開始後一直比出勝負為止就不能再更改。
- 比賽計時,與比賽相關的計時功能有:Start(開始計時)、Pause(暫停計時)、Resume(繼續計時)。
・Start僅能於開始比賽時使用,計時將於新的比賽會歸零。
・Pause在比賽開始時可隨時使用,暫停後可以再按一次Resume繼續計時。
・單局結束後會自動暫停,換邊後開始下一局必須自行手動繼續計時。 - 單局結束後可換邊,其他時間不可換邊。
- 每一邊的計分牌可以在比賽開始後加分或減分,但單局結束後就不能再操作,必須換邊後才能再開始操作加減分。
- 若需要倒回前一局的最後一分則需使用Rewind功能,Rewind功能僅能於單局回合結束後使用,在局內時分數調整使用分數牌上的加減按鈕調整。
- 顯示該局局數。
- 全部比賽結束後顯示哪一方勝出,並且可以按New Match重新另外一場比賽。
介面佈局
比賽前修改名稱
在還沒有按START之前,預設名稱分別為Player 1和Player 2,這邊有宣告兩個變數player1Name和player2Name來儲存雙方的名稱。點擊編輯icon就會出現text field直接帶入目前player1Name和player2Name的值,編輯完成後再按一下完成的icon,就會將text field的value儲存到player1Name或player2Name。
如果Text field沒有輸入任何內容就會直接將player1Name和player2Name還原回“Player 1”或“Player 2”。
加減分與發球權判定
遵循上述的規則,只要得分就能取得發球權。
誤判時扣回分數發球權重回原本持有發球權的一方。以下面例子來看,右邊選手原本持有發球權,左邊選手因誤判得一分,經解決爭議後扣一分,發球權重回右邊選手。
為了實現此功能必須宣告player1Score和player2Score變數來儲存雙方的分數,然後加減分按鈕來操控兩個變數的增減,以及哪一邊的Serve應該顯示。此外為了能回復到誤判前的發球方,另外以serveSequence陣列來紀錄每次取得發球權的順序(例如player1得分就append上一個”player1"),才能確認扣分後應該是由哪一方發球。
另外一回合結束後拿下該局可以獲得該局的發球權也可以依照這個邏輯去判斷,只要在回合開始時將發球權設定給serveSequence的最後一個player,並再搭配回合數的條件確認選手是在左邊還是右邊。
以下是回合結束後換邊執行後用以確認下一局發球方的程式碼:
//將目前serveSequence最後一個player彈出來存到newRoundServe
let newRoundServe = serveSequence.popLast()!
//清空serveSequence為下一局準備
serveSequence.removeAll()
//再append該局第一球發球的player
serveSequence.append(newRoundServe)
//用另外一個函式以回合數(奇數局或偶數局)和發球方來確定要在哪邊顯示serve
identifyServe(player: newRoundServe, round: roundNumber)
單局結束後會更新回合數、回合比分並可換邊
當一方在沒有平手的狀況下分數先到達21時就會判定拿下該局,單局結束後會更新回合數、回合比分並可以執行換邊,將雙方分數歸零,回合比分交換。
在這邊的設計稍微比較複雜,多增加roundNumber來紀錄目前回合數以及player1RoundScore和player2RoundScore來紀錄雙方比數。另外有一方得分都必須以一個獨立的evaluation()的函式搭配比分狀態、deuce變數(Bool type)來判定回合是否結束或者是否滿足deuce條件。
到達deuce後顯示deuce
如果雙方都輪流到達20分就會進入deuce狀態,在回合數下方會顯示deuce。
如果雙方一直到29都未能分出勝負,亦即分數差別小於2分,那分別到達29分後就不再以deuce規則判別勝負(deuce label會隱藏),有任一方先到達30分即可拿下該局。
如同上面說明每次有選手得分時,都必須進行評估是否滿足deuce,如果滿足後deuce(預設false)會變為true,並且要改以誰首先領先2分就獲勝的規則來判定勝負。若是到達29:29時,deuce又會再度變為false,此時就是誰先到達30分來判定勝負。
單局或比賽結束後可以Rewind
在比賽中如果有誤判可以直接扣分,但如果剛好是該回合最後一球的話就不能直接扣分了。這個計分板設計是如果剛好是該回合最後得分那一球誤判要再回復到上一球就必須使用Rewind的功能,回合結束或比賽結束後Rewind button會變成enable的狀態,點擊後比分就會回復到回合結束前的最後一球。但如果已經確認該局誰拿下也執行換邊後就無法再回到上一局了。
Rewind只有在判定回合結束後才會enable,但要知道Rewind是扣哪一方的分數,所以一樣必須參考serveSequence的資料,確認最後一球是誰得到的。以下是Rewind button IBAction func的部分程式碼:
//回合數減1
roundNumber -= 1 //將serveSequence最後一個彈出存到whoGetsTheLastPoint
let whoGetsTheLastPoint = serveSequence.popLast()!//如果最後一個取得發球權的是player1表示最後一分是player1拿到的
//所以要扣player1的分數和回合比數
if whoGetsTheLastPoint == "player1" {
player1Score -= 1
player1RoundScore -= 1
} else {
player2Score -= 1
player2RoundScore -= 1
}//因為回合數有變動必須再呼叫判斷發球顯示在哪一方的函式
identifyServe(player: whoGetsTheLastPoint, round: roundNumber)
New Match重新開始比賽
比賽結束,亦即有一方比數拿到2,整個比賽就會結束並顯示勝方。
這邊的判斷比較單純,每個回合結束後都會執行roundEnd函式來更新相關的介面,故roundEnd函式裡面會判斷比分是否已經滿足比賽結束的條件,如果有一方的RoundScore到達2就會比賽結束。
計時器
另外每次按START就會開始計時,暫停或回合結束會停止計時,這部分的程式碼也貼上供參考。
//START button IBAction內建立計時器
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
參數說明:
timeInterval = 間隔多久計一次
target=執行函式的位置
selector=要執行的函式
userInfo=夾帶到函式內的資料
repeats=是否要重複
其中selector必須在viewDidLoad之前以@objc來標註,這個每秒執行一次的updateTime函式就是每次counter都要加1,表示秒數,然後再格式化成計時器的樣式設定到計時器label的text。
@objc func updateTime() {
counter += 1
timeLabel.text = formatedTime(counter)
}
停止計時比較簡單,直接呼叫timer的invalidate()方法即可停止計時。
timer.invalidate()