Swiftでブラックジャックを実装してみる


https://qiita.com/hirossyi73/items/cf8648c31898216312e5

こんないい記事をみたので、Swiftでブラックジャックを実装してみよう。
実装するルールは上記サイトを少し改変する。
- 初期カードは52枚。1ゲーム中にカードの重複は起きない。
- ディーラーと任意の数のプレイヤーの対戦。ディーラーは自動、プレイヤーは実行者。
- 実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
- その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバーストしてプレイヤーの負け、その時点でゲーム終了
- プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
- プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
- プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
- JとQとKは10として扱う
- Aはとりあえず「1」としてだけ扱う。「11」にはしない
- ダブルダウンなし、スプリットなし、サレンダーなし、その他特殊そうなルールなし

### 成果物
https://github.com/Kota1021/BlackjackOnSwift.git

### 依存関係
View → Presenter → Game → Blackjack →Card
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2361573/079a7870-ce36-264b-21f3-4a6ba8ad140e.png)


### それぞれの責務<br>

#### 概念モジュール(SwiftyBlackjack)

###### Card
- 表面を向いているかどうか<br>
- ランク<br>
- スート<br>

###### Blackjack
- 山札
- ディーラー
- プレイヤー → 勝負の結果（Blackjackは複数人で遊べるが、その勝負はディーラー対各プレイヤーで、プレイヤー同士は干渉しない）
- 山札からドローし手札に加えるといったBlackjackの本質的な操作

#### 実装モジュール

###### Game
進行状況として
- 開始前
- プレイ中 → ゲームの進行状況
- 勝敗 → 勝負の結果
- 実際に進行する

###### GamePresenter
- Gameの保持しViewと仲介

### まずは本質から作成する。<br>
アプリに依存しない最も本質の論理を切り離すためにSwiftyBlackjackという別モジュールとして作成。今回の場合、抽象度を高めるためにSwiftyBlackjackにおいてSwift標準ライブラリ以外のライブラリを使用していない。つまりimport...をしていない<br>
今回の設計の大部分は下記を参考にしている。
詳しくはhttps://www.youtube.com/watch?app=desktop&v=uTaFfUHUmvY<br>

まず、ゲームルール以前にトランプカードを作成しよう。
``` Card.swift
public struct Card{
    public let face:Face
    public var isFaceUp:Bool
    ...
}

extension Card{
    public mutating func putFaceUp(){ self = faceUp }
    public var faceUp: Card{ .init(face: face, isFaceUp: true) }
    ...
}
...
extension Card{
    public struct Face{
        public let rank:Rank
        public let suit:Suit
        ...
    }

    public enum Rank:String,CaseIterable{
        case ace = "A"
        case two = "2"
        case three = "3"
        ...
    }

    public enum Suit:String, CaseIterable{
        case heart = "❤︎"
        case clover = "♣︎"
        case diamond = "◆"
        case spade = "♠︎"
    }
}
```

次はblackjackの本質的なオブジェクトやゲーム操作を定義

オブジェクト
- talon(山札)
- Dealer
- Player
- point(点数)
- GameResult

操作
- dealCard
- dealCardToDealer
- flipDealersSecondCard

など

``` Blackjack.swift

public struct Blackjack {
    public private(set) var talon:[Card] //山札
    public private(set) var dealer: Dealer
    public private(set) var players: [Player]
    ...
}

//MARK: input interface

extension Blackjack{
    mutating public func dealCard(toPlayerIndexOf index: Int){...}
    mutating public func dealCardToDealer(isFaceUp: Bool = true){...}
    mutating public func flipDealersSecondCard(){...}
    mutating private func drawFromTalon(isFaceUp: Bool = true)->Card{...}
}

//MARK: Definition of Types. Dealer/Player/GameResult

extension Blackjack{
    public struct Dealer{...}

    public struct Player{...}

    public enum GameResult: String{
        case win = "win"
        case lose = "lose"
        case draw = "draw"
    }
}

//MARK: point

extension Blackjack{
    ///点数はカードの本質ではなくゲームルールなのでCardではなくBlackjackで定義
    static func point(of card:Card)->Int{
        switch card.face.rank{
        case .ace:
            return 1
        ...
        case .nine:
            return 9
        case .ten:
            return 10
        ...
        case .king:
            return 10
        }
    }

}

```

### 次にアプリに依存する実装

```Game.swift
import SwiftyBlackjack

struct Game{
    private var blackJack:Blackjack

    //MARK: output interface
    public var dealer: Blackjack.Dealer{ blackJack.dealer }
    public var players: [Blackjack.Player]{ blackJack.players }
    public private(set) var gameState: GameState
    public private(set) var message:Message?

    init(numberOfPlayers:Int = 1) {
        self.blackJack = Blackjack(numberOfPlayers: numberOfPlayers)
        gameState = .beforeStart
    }
}
...
```

Viewには可能な限り論理分岐を持たせたくないので、PresenterがUIに関することはなんでも計算。
```GamePresenter.swift
import SwiftyBlackjack

struct GamePresenter{
    public private(set) var game: Game = Game()
}

// MARK: Output interface

extension GamePresenter{
    public var playerCount:Int{ game.players.count }//コンピューテッドプロパティとしてself.gameから求める
    public var playerTurnIndex:Int?{...}
    public var alert:Message?{...}
    public var addPlayerIsEnabled:Bool{...}
    ...

}

// MARK: Input interface

extension GamePresenter{
    public mutating func addPlayer(){...}

    public mutating func removePlayer(){...}

    public mutating func startToPlay(){ game.startToPlay() }
    public mutating func hit(){ game.hit() }
    public mutating func stand(){ game.stand() }
    public mutating func reset(){ self = .init() }
    public mutating func onAlertDismissed(){...}
}


```

# 今後改善したい点
ゲームなのでアクションに適切なアニメーションをつけたい