#27 Blackjack 21 🃏🃏🃏🃏🃏
做這種卡牌遊戲根本就是邏輯大考驗,寫到最後發現自己竟然寫了四層 if ,真的是很燒腦,昨天寫完決定先放空一下,今天再來上傳 Medium。先來看看成品動畫~
Game rules of this version
- 莊家與玩家一開始都各有一張暗牌與明牌,玩家無法得知莊家的總點數。
- 在還沒 Hit or Stand 之前可以先決定這回合的 Bet,由左方的 UISegmentedControl 和 UIStepper 來控制。
- 決定後如果玩家要繼續拿牌,點選 Hit,莊家會發一張明牌,玩家點數會自動加總顯示在 UILabel 上。
- 若玩家總點數超過 21 點,則莊家獲勝,Chip 將扣掉 Bet。
- 若決定不繼續拿牌,點選 Stand ,此時莊家會公佈底牌,若莊家點數小於等於 16 或 小於玩家總點數,莊家會強迫拿牌。
- 若莊家總點數大於玩家且不超過 21 點,莊家獲勝,Chip 將扣掉 Bet。
- 若莊家總點數超過 21 點,玩家獲勝,Chip 將加上 Bet。
- 跳出輸贏的 Alert 告知玩家訊息,點選 Next round,將自動進入下一局。
- 若玩家總 Chip 歸零時,點選 Next round 後會再跳出另一個 Alert 來告知玩家已經輸光所有錢,點選 Try again 將自動回到初始狀態。
Process
看到這種題目,第一個想法是建立兩個 Array ,一個裝卡牌,一個裝對應的點數,然後設定一個亂數變數,從 Array 中取資料出來。
但是!
我要怎麼讓他不重複呢?牌桌上萬一出現兩張黑桃 Ace 會被認為是出老千哪!於是開始 google 要怎麼不重複取得 Array 的資料,或是就算重複了也可以再亂數一次,於是我找到了 repeat {} while 的用法。
repeat {playerCard1Index = Int.random(in: 0...51)} while playerCard1Index == bankerCard1Index
repeat 的 {} 裡面寫欲重複執行的程式碼,後面緊接 while,並寫上條件。這樣寫,如果寫到了最後一張牌,會是什麼狀況?
repeat {bankerCard5Index = Int.random(in: 0...51)} while bankerCard5Index == bankerCard1Index || bankerCard5Index == playerCard1Index || bankerCard5Index == bankerCard2Index || bankerCard5Index == playerCard2Index || bankerCard5Index == playerCard3Index || bankerCard5Index == playerCard4Index || bankerCard5Index == playerCard5Index || bankerCard5Index == bankerCard3Index || bankerCard5Index == bankerCard4Index
沒錯,會有一~~~堆條件哪!所以我才說寫卡牌遊戲的 APP 根本就是邏輯大考驗 zzz
好,進入正題,我會先介紹幾個比較重要的功能,如果想直接參考完成的程式碼請到文章最下方。這遊戲我寫了四個 func
分別是backToDefault()
、loseMessageAlert()
、 winMessageAlert()
、 tieMessageAlert()
和 。
By the way, 我的卡牌是使用 Wei 的設計,附上連結如下。
func backToDefault () {bankerPoint = 0playerPoint = 0bet = 0betLabel.text = "\(bet)"betSegmentedControl.selectedSegmentIndex = 0betStepper.value = 0betStepper.isEnabled = trueplayerCard1ImageView.image = nilplayerCard2ImageView.image = nilplayerCard3ImageView.image = nilplayerCard4ImageView.image = nilplayerCard5ImageView.image = nilbankerCard1ImageView.image = nilbankerCard2ImageView.image = nilbankerCard3ImageView.image = nilbankerCard4ImageView.image = nilbankerCard5ImageView.image = nilplayerCard3ImageView.isHidden = trueplayerCard4ImageView.isHidden = trueplayerCard5ImageView.isHidden = truebankerCard3ImageView.isHidden = truebankerCard4ImageView.isHidden = truebankerCard5ImageView.isHidden = truebankerCard1Index = nilbankerCard2Index = nilbankerCard3Index = nilbankerCard4Index = nilbankerCard5Index = nilplayerCard1Index = nilplayerCard2Index = nilplayerCard3Index = nilplayerCard4Index = nilplayerCard5Index = nilbankerCard1Index = Int.random(in: 0...51)playerCard1Index = Int.random(in: 0...51)bankerCard2Index = Int.random(in: 0...51)playerCard2Index = Int.random(in: 0...51)bankerCard1ImageView.image = UIImage(named: "Image")repeat {playerCard1Index = Int.random(in: 0...51)} while playerCard1Index == bankerCard1IndexplayerCard1ImageView.image = UIImage(named: card[playerCard1Index!])playerHoleCardButton.setImage(playerCard1ImageView.image, for: .highlighted)playerHoleCardButton.isHidden = falseplayerPoint += cardPoint[playerCard1Index!]playerPointLabel.text = "\(playerPoint)"repeat {bankerCard2Index = Int.random(in: 0...51)} while bankerCard2Index == bankerCard1Index || bankerCard2Index == playerCard1IndexbankerCard2ImageView.image = UIImage(named: card[bankerCard2Index!])bankerPoint += cardPoint[bankerCard2Index!]bankerPointLabel.text = "\(bankerPoint)"bankerPointLabel.textColor = UIColor.blackrepeat {playerCard2Index = Int.random(in: 0...51)} while playerCard2Index == bankerCard1Index || playerCard2Index == playerCard1Index || playerCard2Index == bankerCard2IndexplayerCard2ImageView.image = UIImage(named: card[playerCard2Index!])playerPoint += cardPoint[playerCard2Index!]playerPointLabel.text = "\(playerPoint)"playerPointLabel.textColor = UIColor.black}
backToDefault()
主要功能就是當跳出輸贏或平手的 Alert 時,按下 Next round 可以回到初始狀態。
func loseMessageAlert() {let alert = UIAlertController(title: "YOU LOSE", message: "Your point is \(playerPoint)", preferredStyle: .alert)let action = UIAlertAction(title: "Next round", style: .default, handler: {action inself.chip -= self.betself.chipLabel.text = "$ \(self.chip)"self.backToDefault()if self.chip == 0 {let loseAllMoneyAlert = UIAlertController(title: "OMG", message: "YOU LOSE ALL MONEY", preferredStyle: .alert)let action = UIAlertAction(title: "TRY AGAIN", style: .default, handler: {action inself.chip = 1000self.chipLabel.text = "$ \(self.chip)"})loseAllMoneyAlert.addAction(action)self.present(loseAllMoneyAlert, animated: true, completion: nil)}})alert.addAction(action)present(alert, animated: true, completion: nil)}
輸贏或平手的 Alert 其實長得差不多,只是輸的 Alert 有一個比較特別地方就是多了個 if 去偵測玩家的 chip 是否歸零。
這邊再介紹遊戲中兩個最重要的按鈕,分別是 Hit Button 和 Stand Button。
Hit Button
這兩個 Button 階層比較複雜一點,所以我直接貼 Gist 連結,這樣比較好懂。 Hit Button 主要是控制玩家端的牌。使用各個 playCardImageView 的 isHidden 來當作翻牌順序的條件。
亂數產生 Index 後去取得 card[]
和 cardPoint[]
的資料後,把卡牌顯示出來。如果 playerPoint
已經超過 21 ,就會呼叫 loseMessageAlert()
,並把底牌公開結束這回合。
Stand Button
就是這個 standButton,總共寫了四層的 if !基本上邏輯如下:
- 如果莊家的點數小於等於 16 或莊家的點數小於等於玩家且莊家點數不等於 21 時,莊家必須依序再開一張牌,然後進入下一層的判斷式。
- 如果莊家點數已經大於玩家且點數小於等於 21 的話,代表莊家獲勝,公開所有底牌並呼叫
loseMessageAlert()
。 - 如果莊家點數與玩家點數都剛好等於 21 時,則和局,公開所有底牌並呼叫
tieMessageAlert()
。 - 其他狀況則是玩家獲勝,公開所有底牌並呼叫
winMessageAlert()
。
Free talk
阿~我的腦細胞已死~好像又回到學生時代在解題的感覺,雖然累但滿痛快的就是了~