#12 二十一點亞洲慈善賭神大賽

21點是家喻戶曉的遊戲,2008年的<<決勝21點>>喚醒了無數天才數學家們的夢想,因為可以由計算機率而獲取可觀的報酬。一般來說,跟普通人玩21點可能要很久才會輸完錢,但是跟賭神玩可能一個APP還沒寫完就脫褲子了。

這個程式參考了無數天才的作品,並加上一些規則:

  1. 為了使視力不好的人也能享受下大注的樂趣,用了大按鍵的下注方式
  2. 畫面上方可以挑選你的莊家是一般人(Man)還是賭神(God)
  3. 預設賭金1000元(Chip),最小賭注100元(bet),下注後停止加注
  4. 瞇牌後才會開始算點數
  5. 玩家必須點數超過15後,否則必須繼續叫牌(Hit),最後再瞇牌一次,就能停止叫牌(Stand)
  6. Ace預設是11點,總點數超過21點後會自動降為1點
  7. 賭金降為零可以重新遊戲(Try again)

接下來就來看看如何建立賭神21點吧!

製造牌組陣列

使用土法煉鋼的方式,新建立一個cards.swift

import Foundationlet spades = ["♠︎A","♠︎2","♠︎3","♠︎4","♠︎5","♠︎6","♠︎7","♠︎8","♠︎9","♠︎10","♠︎J","♠︎Q","♠︎K"]
let clubs = ["♣︎A","♣︎2","♣︎3","♣︎4","♣︎5","♣︎6","♣︎7","♣︎8","♣︎9","♣︎10","♣︎J","♣︎Q","♣︎K"]
let hearts = ["♥︎A","♥︎2","♥︎3","♥︎4","♥︎5","♥︎6","♥︎7","♥︎8","♥︎9","♥︎10","♥︎J","♥︎Q","♥︎K"]
let diamonds = ["♦︎A","♦︎2","♦︎3","♦︎4","♦︎5","♦︎6","♦︎7","♦︎8","♦︎9","♦︎10","♦︎J","♦︎Q","♦︎K"]
let cards = spades + hearts + diamonds + clubs

加入IBOutlet

@IBOutlet weak var betLabel: UILabel!
@IBOutlet weak var oneDallorButton: UIButton!
@IBOutlet weak var tenDollarButton: UIButton!
@IBOutlet weak var hundredDollarButton: UIButton!
@IBOutlet weak var chipLabel: UILabel!

@IBOutlet var playerCardImageView: [UIImageView]!
@IBOutlet var bankerCardImageView: [UIImageView]!

@IBOutlet weak var bankerSegment: UISegmentedControl!

@IBOutlet weak var bankerPointLabel: UILabel!
@IBOutlet weak var playerPointLabel: UILabel!
@IBOutlet weak var playerHoldCardButton: UIButton!
@IBOutlet weak var cardDeskView: UIView!
@IBOutlet weak var dealButton: UIButton!

@IBOutlet weak var hitButton: UIButton!
@IBOutlet weak var standButton: UIButton!

@IBOutlet weak var startLabel: UILabel!

這個地方需要注意一下

@IBOutlet var playerCardImageView: [UIImageView]!
@IBOutlet var bankerCardImageView: [UIImageView]!

因為牌組的部分是一對多張牌,要選擇Outlet Collection

玩家與莊家的前兩張牌特別設定

var playerCloseCard = ""
var bankerCloseCard = ""
var playerOpenCard = ""
var bankerOpenCard = ""

設定玩家(pCards)與莊家(bCards)陣列

var pCards = [String]()
var bCards = [String]()

設定隨機選牌屬性

let distrubution = GKShuffledDistribution(lowestValue: 0, highestValue: cards.count - 1)

設定點數初始值

var bankerPoint : Int = 0
var playerPoint : Int = 0

var bankerTotalPoint : Int = 0

var chip : Int = 1000

var bet : Int = 100

設定莊家與玩家的Ace數目

var playerAceCount : Int = 0
var bankerAceCount : Int = 0

以及等一下變化賭神第一張牌的參數

var superNumber : [Int]?
var superNumberRandom : Int?
var whoIsBankerNumber : Int?
var gambleGod : Int?
var generalman : Int?

設定 viewDidLoad

建立桌檯編框顏色,預設隱藏叫牌、停止叫牌、與下注按鈕功能,玩家必須先選完莊家是賭神還是一般人,才能開始遊戲

override func viewDidLoad() {
super.viewDidLoad()
cardDeskView.layer.borderColor = CGColor(red: 1, green: 0.9, blue: 0.5, alpha: 1)
cardDeskView.layer.borderWidth = 3
cardDeskView.layer.cornerRadius = 5
hitButton.isHidden = true
standButton.isHidden = true
dealButton.isHidden = true

}

設定 IBAction

設計瞇牌功能,玩家如果前兩張牌加起來21點就禁止加牌(Hit),超過15點才能停止叫牌(Stand),這兩個IBAction同時連到玩家的第一張牌上面覆蓋的Button

@IBAction func closeCardClicked(_ sender: UIButton) {
playerCardImageView[0].isHighlighted = false
UIView.transition(with: playerCardImageView[0], duration: 0.2, options: .transitionFlipFromLeft, animations: nil, completion: nil)
if playerPoint == 21 && pCards.count == 2 {
hitButton.isEnabled = false
}
}

@IBAction func seeCardClicked(_ sender: UIButton) {
playerCardImageView[0].isHighlighted = true
playerPointLabel.isHidden = false
UIView.transition(with: playerCardImageView[0], duration: 0.2, options: .transitionFlipFromLeft, animations: nil, completion: nil)
hitButton.isEnabled = true
if playerPoint >= 15 {
standButton.isEnabled = true
}
}

建立下注DEAL功能

下注完就隱藏三個加注按鈕,並打開叫牌(hitButton)與停止叫牌(standButton)按鈕。

@IBAction func dealButtonClicked(_ sender: UIButton) {

hitButton.isHidden = false
standButton.isHidden = false
oneDallorButton.isHidden = true
tenDollarButton.isHidden = true
hundredDollarButton.isHidden = true

莊家的第一張牌是由segment決定,其他的是利用隨機取碼決定

if chip >= bet {
bankerCloseCard = cards[whoIsBankerNumber!]
playerCloseCard = cards[distrubution.nextInt()]
playerOpenCard = cards[distrubution.nextInt()]
bankerOpenCard = cards[distrubution.nextInt()]
pCards += ["\(playerCloseCard)", "\(playerOpenCard)"]
bCards += ["\(bankerCloseCard)", "\(bankerOpenCard)"]

將選取到的排列入相關ImageView中,並加入點數

bankerCardImageView[0].isHidden = false
playerCardImageView[0].image = UIImage(named: "Image")
playerCardImageView[0].highlightedImage = UIImage(named: playerCloseCard)
playerCardImageView[0].isHidden = false
playerCardImageView[1].image = UIImage(named: playerOpenCard)
playerCardImageView[1].isHidden = false
bankerCardImageView[1].image = UIImage(named: bankerOpenCard)
bankerCardImageView[1].isHidden = false
playerPoint += pointCount(card: playerOpenCard)
playerPoint += pointCount(card: playerCloseCard)

判斷玩家是否有Ace,並將計算好的點數呈現在相關Label上,此時莊家的點數還是打開的那一張的點數

if (playerOpenCard.contains("A") || playerCloseCard.contains("A")) && playerPoint > 21 {
playerPoint -= 10
}

playerPointLabel.text = "\(playerPoint)"

bankerPoint += pointCount(card: bankerOpenCard)
bankerPointLabel.text = "\(bankerPoint)"
bankerPointLabel.isHidden = false

dealButton.isEnabled = false
playerHoldCardButton.isEnabled = true

bankerTotalPoint = bankerPoint + pointCount(card: bankerCloseCard)

計算莊家的總點數,若有Ace則總點數減10

if (bankerOpenCard.contains("A") || bankerCloseCard.contains("A")) && bankerTotalPoint > 21 {
playerPoint -= 10
}

計算莊家與玩家的Ace數目

for pCard in pCards {
if pCard.contains("A") {
playerAceCount += 1
}
}
for bCard in bCards {
if bCard.contains("A"){
bankerAceCount += 1
}
}

建立叫牌 hitButtonClicked 功能

新增叫牌變數,延續隨機取牌,並將玩家隱藏牌打開,且將新牌列入玩家卡牌陣列

var addCard = ""
addCard = cards[distrubution.nextInt()]
playerCardImageView[playHitCount].isHidden = false
playerCardImageView[playHitCount].image = UIImage(named: addCard)
UIView.transition(with: playerCardImageView[playHitCount], duration: 0.8, options: .transitionCurlDown, animations: nil, completion: nil)
pCards += ["\(addCard)"]

若拿到Ace增加數目,計算玩家點數,如果拿到Ace,總點數減10

if addCard.contains("A"){
playerAceCount += 1
}
playerPoint += pointCount(card: addCard)for pCard in pCards {
if pCard.contains("A") && playerPoint > 21 {
playerPoint -= 10
pCards = pCards.filter({(card : String) -> Bool in return !card.contains("A")})
}
}

如果拿到兩張以上Ace,將結果加入玩家卡牌陣列,並秀出玩家點數

if addCard.contains("A") && playerAceCount >= 2 {
pCards += ["\(addCard)"]
}
playerPointLabel.text = "\(playerPoint)"

如果玩家總點數超過15點,可以停止叫牌

if playerPoint >= 15 {
dealButton.isEnabled = true
}

如果玩家點數等於21點,停止叫牌

if playerPoint == 21 {
hitButton.isEnabled = false
}

玩家總拿牌數加1

playHitCount += 1

如果玩家拿了五張牌,沒有爆掉,並且莊家是21點,莊家贏;或是玩家超過21點,莊家贏

if (playHitCount == 5 && playerPoint <= 21) && (bankerPoint == 21) {
loseMessageAlert()
}else if playerPoint > 21 {
loseMessageAlert()
}

建立停止叫牌及莊家加牌系統 standButtonClicked

打開莊家第一張牌,並且計算兩張牌的點數,並將算牌數重歸為2張牌

@IBAction func standButtonClicked(_ sender: UIButton) {

playerCardImageView[0].image = UIImage(named: playerCloseCard)
UIView.transition(with: playerCardImageView[0], duration: 0.5, options: .transitionFlipFromRight, animations: nil, completion: nil)
bankerCardImageView[0].image = UIImage(named: bankerCloseCard)
UIView.transition(with: bankerCardImageView[0], duration: 0.5, options: .transitionFlipFromRight, animations: nil, completion: nil)
bankerPointLabel.text = "\(bankerTotalPoint)"
playHitCount = 2

增加莊家加牌變數

var bankerAddCard = ""

判斷當莊家總點數小於17並且拿牌張數小於6,或是莊家總點數等於17並且拿到Ace並且玩家沒有加牌時,莊家會加牌,並且列入莊家牌組中,並且計算莊家的點數,呈現出來。

如果拿到Ace,莊家總點數減10,莊家總拿牌數加1

while (bankerTotalPoint < 17 && playHitCount < 6) || (bankerTotalPoint == 17 && bCards.contains("A") && playHitCount == 2){
bankerAddCard = cards[distrubution.nextInt()]
bankerCardImageView[playHitCount].isHidden = false
bankerCardImageView[playHitCount].image = UIImage(named: bankerAddCard)
UIView.transition(with: bankerCardImageView[playHitCount], duration: 0.8, options: .transitionCurlDown, animations: nil, completion: nil)
bCards += ["\(bankerAddCard)"]
bankerTotalPoint += pointCount(card: bankerAddCard)
for bCard in bCards {
if bCard.contains("A") && bankerTotalPoint > 21 {
bankerTotalPoint -= 10
bCards = bCards.filter({(card : String) -> Bool in return !card.contains("A")})
}
}
if bankerAddCard.contains("A") && bankerAceCount >= 2 {
bCards += ["\(bankerAddCard)"]
}

bankerPointLabel.text = "\(bankerTotalPoint)"
playHitCount += 1

如果莊家一開始就拿到21點,莊家贏

if bankerTotalPoint == 21 && bCards.count == 2 {
bankerPointLabel.text = "\(bankerTotalPoint)"
loseMessageAlert()
}

如果玩家一開始拿到21點,玩家贏

else if playerPoint == 21 && pCards.count <= 2{
winMessageAlert()
}

如果莊家爆了,或是玩家點數大於莊家,玩家贏

else if bankerTotalPoint > 21 || playerPoint > bankerTotalPoint {
winMessageAlert()

莊家點數大於玩家,莊家贏

else if bankerTotalPoint > playerPoint {
loseMessageAlert()

點數相同平手

else if bankerTotalPoint == playerPoint {
tieMessageAlert()

建立玩家輸(莊家贏)警告程序 loseMessageAlert

會跳出警告頁面,並且計算相關分數,將玩家總點數減去賭注,並且可以按下”Next round”會進入下一輪。如果玩家點數歸零,則可選擇重新遊戲”Try Again”

func loseMessageAlert(){
playerCardImageView[0].image = UIImage(named: playerCloseCard)
UIView.transition(with: playerCardImageView[0], duration: 0.2, options: .transitionFlipFromRight, animations: nil, completion: nil)
let alert = UIAlertController(title: "YOU LOSE", message: "Your point is \(playerPoint)", preferredStyle: .alert)
let action = UIAlertAction(title: "Next round", style: .default, handler: {
action in
self.chip -= self.bet
self.chipLabel.text = "$ \(self.chip)"
self.nextRound()
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 in
self.chip = 1000
self.chipLabel.text = "$ \(self.chip)"
})
loseAllMoneyAlert.addAction(action)
self.show(loseAllMoneyAlert, sender: nil)
}
})
alert.addAction(action)
show(alert, sender: nil)
}

建立玩家贏(莊家輸)警告程序 winMessageAlert

重點是將賭注加入玩家總點數,並且可以按下”Next round”會進入下一輪。

func winMessageAlert () {
let alert = UIAlertController(title: "YOU WIN", message: "Your point is \(playerPoint) and Banker's point is \(bankerTotalPoint)", preferredStyle: .alert)
let action = UIAlertAction(title: "Next round", style: .default, handler: {
action in
self.chip += self.bet
self.chipLabel.text = "$ \(self.chip)"
self.nextRound()
})
alert.addAction(action)
show(alert, sender: nil)
}

建立平手程序 tieMessageAlert

沒有扣分,按下”Next round”會進入下一輪。

func tieMessageAlert () {
let alert = UIAlertController(title: "TIE", message: "Your point is \(playerPoint) and Banker's point is \(bankerTotalPoint)", preferredStyle: .alert)
let action = UIAlertAction(title: "Next round", style: .default, handler: {
action in
self.nextRound()
})
alert.addAction(action)
show(alert, sender: nil)
}

建立1元、10元、與100元加碼程序

@IBAction func oneDollarAdded(_ sender: UIButton) {
if (chip - bet) >= 1 {
bet += 1
betLabel.text = "\(bet)"
}else {
bet += 0
}
}
@IBAction func tenDollarAdded(_ sender: UIButton) {
if (chip - bet) >= 10 {
bet += 10
betLabel.text = "\(bet)"
}else {
bet += 0
}
}
@IBAction func hundredDollarAdded(_ sender: UIButton) {
if (chip - bet) >= 100 {
bet += 100
betLabel.text = "\(bet)"
}else {
bet += 0
}
}

建立下一輪 nextRound 程序

func nextRound () {
playerCardImageView[0].isHidden = true
playerCardImageView[1].isHidden = true
playerCardImageView[2].isHidden = true
playerCardImageView[3].isHidden = true
playerCardImageView[4].isHidden = true
bankerCardImageView[0].isHidden = true
bankerCardImageView[1].isHidden = true
bankerCardImageView[2].isHidden = true
bankerCardImageView[3].isHidden = true
bankerCardImageView[4].isHidden = true

bankerCardImageView[0].image = UIImage(named: "Image")

bankerPoint = 0
bankerTotalPoint = 0
playerPoint = 0
bankerPointLabel.text = "0"
playerPointLabel.text = "0"
bankerPointLabel.isHidden = true
playerPointLabel.isHidden = true
pCards.removeAll()
bCards.removeAll()

dealButton.isEnabled = true
hitButton.isEnabled = false
standButton.isEnabled = false
playerHoldCardButton.isEnabled = false

playerCloseCard = ""
playerOpenCard = ""
bankerCloseCard = ""
bankerOpenCard = ""
playHitCount = 2
playerAceCount = 0
bankerAceCount = 0

bet = 100
betLabel.text = "\(bet)"
oneDallorButton.isHidden = false
tenDollarButton.isHidden = false
hundredDollarButton.isHidden = false
}

建立卡牌對照點數 pointCount 程序

Ace建立初始值是11點,遇到點數爆掉的時候,才會在其他程式中修改為1點。JQK都設定為10點。

func pointCount (card: String) -> Int{
var point = 0
if card.contains("A") {
point = 11
}else if card.contains("2") {
point = 2
}else if card.contains("3") {
point = 3
}else if card.contains("4") {
point = 4
}else if card.contains("5") {
point = 5
}else if card.contains("6") {
point = 6
}else if card.contains("7") {
point = 7
}else if card.contains("8") {
point = 8
}else if card.contains("9") {
point = 9
}else if card.contains("10") {
point = 10
}else if card.contains("J") {
point = 10
}else if card.contains("Q") {
point = 10
}else if card.contains("K") {
point = 10
}
return point
}

建立 segment control 選擇與賭神決戰還是一般人打打小牌 bankerChangeSegment 程序

選擇完畢會先關掉提醒標籤(startLabel),並打開下注按鈕(dealButton)

賭神的第一張(隱藏)牌,是由四張Ace隨機選出

@IBAction func bankerChangeSegment(_ sender: UISegmentedControl) {
startLabel.isHidden = true
dealButton.isHidden = false

whoIsBankerNumber = sender.selectedSegmentIndex

superNumber = [0, 13, 26, 39]
superNumberRandom = Int.random(in: 0...3)

gambleGod = self.superNumber![self.superNumberRandom!]
generalman = distrubution.nextInt()

switch whoIsBankerNumber {
case 0: gambleGod
case 1: generalman
default: break
}
}

執行結果發現,其實賭神也沒有很厲害,因為第一張拿到Ace也很難說一定會贏

參考資料

Github

--

--