#27 Blackjack 21 🃏🃏🃏🃏🃏

做這種卡牌遊戲根本就是邏輯大考驗,寫到最後發現自己竟然寫了四層 if ,真的是很燒腦,昨天寫完決定先放空一下,今天再來上傳 Medium。先來看看成品動畫~

Game rules of this version

  1. 莊家與玩家一開始都各有一張暗牌與明牌,玩家無法得知莊家的總點數。
  2. 在還沒 Hit or Stand 之前可以先決定這回合的 Bet,由左方的 UISegmentedControl 和 UIStepper 來控制。
  3. 決定後如果玩家要繼續拿牌,點選 Hit,莊家會發一張明牌,玩家點數會自動加總顯示在 UILabel 上。
  4. 若玩家總點數超過 21 點,則莊家獲勝,Chip 將扣掉 Bet。
  5. 若決定不繼續拿牌,點選 Stand ,此時莊家會公佈底牌,若莊家點數小於等於 16 或 小於玩家總點數,莊家會強迫拿牌。
  6. 若莊家總點數大於玩家且不超過 21 點,莊家獲勝,Chip 將扣掉 Bet。
  7. 若莊家總點數超過 21 點,玩家獲勝,Chip 將加上 Bet。
  8. 跳出輸贏的 Alert 告知玩家訊息,點選 Next round,將自動進入下一局。
  9. 若玩家總 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 !基本上邏輯如下:

  1. 如果莊家的點數小於等於 16 或莊家的點數小於等於玩家且莊家點數不等於 21 時,莊家必須依序再開一張牌,然後進入下一層的判斷式。
  2. 如果莊家點數已經大於玩家且點數小於等於 21 的話,代表莊家獲勝,公開所有底牌並呼叫 loseMessageAlert()
  3. 如果莊家點數與玩家點數都剛好等於 21 時,則和局,公開所有底牌並呼叫 tieMessageAlert()
  4. 其他狀況則是玩家獲勝,公開所有底牌並呼叫 winMessageAlert()

Free talk

阿~我的腦細胞已死~好像又回到學生時代在解題的感覺,雖然累但滿痛快的就是了~

--

--