#19 撲克牌比大小
Published in
10 min readOct 23, 2021
撲克牌算是enum的經典範例應用,原先是規劃21點的遊戲,但可能除了撲克牌的資料架構規劃還有21點的邏輯。評估後,先把重點放在撲克牌的資料架構規劃,先實現比大小的功能。
實現功能
- 每一局將有整副隨機順序的52牌
- 提供兩個玩家可以不照順序抽牌
- 第一個抽牌不會先掀牌,第二個抽完牌後才將兩張掀牌並比大小
- 比贏的人可以得100分
- 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
}
建立介面元件
- 中間待抽取牌的UIView
- 左右已抽取的牌的UIView
- 顯示剩餘牌數與玩家分數
實例化卡牌與玩家
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判別抽牌的優先順序:
- 第一個抽牌者,執行setPattern()的函數
- 第二個抽牌者,除了執行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
}
}
}