#19 撲克牌比大小

撲克牌算是enum的經典範例應用,原先是規劃21點的遊戲,但可能除了撲克牌的資料架構規劃還有21點的邏輯。評估後,先把重點放在撲克牌的資料架構規劃,先實現比大小的功能。

實現功能

  1. 每一局將有整副隨機順序的52牌
  2. 提供兩個玩家可以不照順序抽牌
  3. 第一個抽牌不會先掀牌,第二個抽完牌後才將兩張掀牌並比大小
  4. 比贏的人可以得100分
  5. 52張抽完後可以重洗繼續玩

新增Card.swift

用來宣告撲克牌的資料結構

建立Rank列舉

  • 定義數字的case,case的條件 不能為數字,透過emoji將rawValue轉換成一般我們所熟知的符號跟數字
  • 由於rawValue為整數,enum Rank: 後加上Int
  • 後續要能建立此enum的陣列,enum Rank後面要宣告CaseIterable
enum Rank: Int, CaseIterable {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king

var emoji: String {
switch self {
case .ace: return "A"
case .jack: return "J"
case .queen: return "Q"
case .king: return "K"
default: return String(self.rawValue)
}
}
}

建立 Suit列舉

  • Suit的case與emoji邏輯與Rank相同
  • 新增computing property 的var color,依花色判別紅色跟黑色的牌色
enum Suit: Int, CaseIterable {
case club = 1, diamond, heart, spades

var emoji: String {
switch self {
case .club: return "♣"
case .diamond: return "♦"
case .heart: return "♥"
case .spades: return"♠"
}
}

var color: UIColor {
switch self {
case .club, .spades: return UIColor.black
case .diamond, .heart: return UIColor.red
}
}
}

建立單張牌的結構

每張牌都包含suit跟rank,其屬性分別為剛剛建立的Rank跟Suit的enum

struct Card {
var suit: Suit
var rank: Rank
}

建立整副牌的類別

宣告cardIndex作為卡片陣列的索引值,其值為52 - 1

class Cards {
var cardIndex: Int {
return Suit.allCases.count * Rank.allCases.count - 1
}

接著宣告整副牌的陣列,利用for-in遍歷struct Card裡的suit跟rank,並利用cards.shuffle()將排序打亂

var cards: [Card] {
var cards = [Card]()
for i in 1...Suit.allCases.count {
for j in 1...Rank.allCases.count {
let card = Card(suit: Suit(rawValue: i)!, rank: Rank(rawValue: j)!)
cards.append(card)
}
}
cards.shuffle()
return cards
}
}

新增Game.swift

用來宣告遊戲機制

建立Player類別

class Player用來儲存玩家的卡牌內容,包含當下玩家拿到的牌與累積分數

class Player {
var cardCombol = Card(suit: .club, rank: .ace)
var score = 0

新增判別輸贏的方法:先比較rank大小輸贏,如rank相同則比較suit大小

    func compare(against oppositePlayer: Player) {
if self.cardCombol.rank.rawValue > oppositePlayer.cardCombol.rank.rawValue {
self.score += 100

}else if self.cardCombol.rank.rawValue == oppositePlayer.cardCombol.rank.rawValue {
if self.cardCombol.suit.rawValue > oppositePlayer.cardCombol.suit.rawValue {
self.score += 100
}else{
oppositePlayer.score += 100
}
}else {
oppositePlayer.score += 100
}
}
}

建立左右兩邊玩家的子類別

透過個別可繼承Player的屬性與方法,左右兩邊玩家可儲存個別資料

class RightPlayer: Player {
}
class LeftPlayer: Player {
}

宣告用以判別玩家方向與抽排順序的列舉

enum PlayerDirection: String {
case right
case left
}
enum DrawOrder: Int {
case firstDraw = 0
case secondDraw
}

建立介面元件

  1. 中間待抽取牌的UIView
  2. 左右已抽取的牌的UIView
  3. 顯示剩餘牌數與玩家分數

實例化卡牌與玩家

    var cards = Cards().cards          //實例化卡牌陣列
var cardIndex = Cards().cardIndex //實例化卡牌索引值
var drawOrder = 0

var rightPlayer = RightPlayer() //右邊玩家
var leftPlayer = LeftPlayer() //左邊玩家
var firstDraw = Player() //第一個抽牌者
var secondDraw = Player() //第二個抽牌者

抽牌更新UI的函數

建立currentPlayer的computing property,透過switch判別sender swipe的方向辨識右邊或左邊的玩家,並同時將目前的牌賦值給玩家。

func updateUI(sender: UISwipeGestureRecognizer, currentCard: Card) {
drawOrder %= 2
var currentPlayer: Player {
var player = Player()
switch sender.direction {
case .right:
rightPlayer.cardCombol = currentCard
player = rightPlayer
case .left:
leftPlayer.cardCombol = currentCard
player = leftPlayer
default: break
}
return player
}

建立setPattern()函數:

  • 將抽牌者得到數值顯示於storyBoard的lable上
  • 透過disable sender使抽牌者不能連抽
   func setPattern() {
switch sender.direction {
case .right:
drawACard(dispayPattern: rightRankLabel, rightSuitSmallLabel, rightSuitBigLabel, from: rightPlayer.cardCombol)
rightCardImage.addSubview(rightCardOutside)
case .left:
drawACard(dispayPattern: leftRankLabel, leftSuitSmallLabel, leftSuitBigLabel, from: leftPlayer.cardCombol)
leftCardImage.addSubview(leftCardOutside)
default: break
}
sender.isEnabled = false


func drawACard(dispayPattern rankLabel: UILabel, _ smallSuitLabel: UILabel, _ bigSuitLabel: UILabel, from cardCombol: Card) {

rankLabel.text = cardCombol.rank.emoji
rankLabel.textColor = cardCombol.suit.color
smallSuitLabel.text = cardCombol.suit.emoji
smallSuitLabel.textColor = cardCombol.suit.color
bigSuitLabel.text = cardCombol.suit.emoji
bigSuitLabel.textColor = cardCombol.suit.color
setCardStackRadious(transRadiousFrom: cardIndex)
}
}

建立switch判別抽牌的優先順序:

  1. 第一個抽牌者,執行setPattern()的函數
  2. 第二個抽牌者,除了執行setPattern()的函數,還要與第一個抽牌者比較大小,並enable sender
   switch DrawOrder(rawValue: drawOrder){
case .firstDraw:
firstDraw = currentPlayer
setPattern()
case .secondDraw:
secondDraw = currentPlayer
setPattern()
secondDraw.compare(against: firstDraw)
swipeGuesture[0].isEnabled = true
swipeGuesture[1].isEnabled = true

...

建立swipe 的@IBAction

執行updateUI(),currentCard為cards[cardIndex]目前的卡片

@IBAction func swipe(_ sender: UISwipeGestureRecognizer) {

updateUI(sender: sender, currentCard: cards[cardIndex])
setCardCount()
drawOrder += 1

當剩餘牌數為0時,將出現繼續遊戲的按鈕

         if cardCountLabel.text == "0" {
cardOutside.isHidden = true
leftArrowImage.isHidden = true
rightArrowImage.isHidden = true

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.keepPlayButton.isHidden = false
}
}
}

--

--