Swiftでポーカーの役判定


Swiftをさわってみようということで、[DevNomi](http://xpfriend.com/DevNomi/#/start)でお馴染みのポーカー役判定をTDDっぽくやってみました。

# 環境構築

App　StoreからXcodeをインストール。

# Playgroundで練習

Swiftってどんなもんかいじるために、Playgroundでいじってみる。

初期画面 => Get started with a playground

※ 「WEB+DB PRESS vol.84」を読みながら書いたらなんとなく。。。


# プロジェクト作成

1. 初期画面 => Create a new Xcode project
2. OS X => Other => Empty
3. Product Name: Poker

# Pokerクラスの作成

1. 画面左ペインの左から４つ目のアイコンをクリックして[Test Navigator]を開く
2. 画面左下の[+]アイコンをクリック => New Test Target
3. Product Name: Poker, Organization Identifier: 任意

※ なぜかテストができてしまうので、本当は手順が違う？

![swift1.png](https://qiita-image-store.s3.amazonaws.com/0/50720/c0860bfd-6c94-91e4-6c17-40faa0bd1cea.png "swift1.png")

# テストクラスの作成

1. 画面左ペインの左から４つ目のアイコンをクリックして[Test Navigator]を開く
2. 画面左下の[+]アイコンをクリック => New Test Class
3. OS X => Source => Test Case Class
4. Class: PokerTest, Subclass of: XCTestCase, Language: Swift

# テストの実行

## テスト実行環境設定

やっておくとテスト実行時にコンソールが表示される。

1.Preference => Behaviors => Testing => Starts
2.[Show] debugger with [Valiables & Console] をチェック

![swift2.png](https://qiita-image-store.s3.amazonaws.com/0/50720/d1a5c686-c35e-1064-dad3-e5627d9224fe.png "swift2.png")

## テストの試し打ち

1. 画面左上のフォルダーアイコンで[Project Navigator]を開く
2. PokerTest.swiftを次のとおり編集

```swift
import XCTest

class PokerTest: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    
    func test() {
        XCTAssertEqual(true, true)
    }
    
}

```

3.Cmd+U

# ブタの実装

## 方針

* 1枚のカードはRankとSuitをもつ構造体
* 手札は5枚のカードの配列
* 役はenum

## Red

とりあえず、テストコードを次の通り作成。

PokerTest.swift

```
import XCTest

class PokerTest: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    
    func testHightCard() {
        let cards = [
            Card(rank:1,  suit:"♣"),
            Card(rank:9,  suit:"♣"),
            Card(rank:8,  suit:"♥"),
            Card(rank:7,  suit:"♦"),
            Card(rank:5,  suit:"♠")
        ];
        XCTAssertEqual(Poker.getScore(cards), Hands.HIGH_CARD.score)
    }
    
}
```

当然、コンパイルエラーなので、次をプロダクションコードの中で作成。

* カードの構造体: Card
* 役のenum: Hands
* getScoreメソッド: とりあえず、0をreturnする。

Poker.swift

```swift
enum Hands {
    case HIGH_CARD
    case ONE_PAIR
    case TWO_PAIR
    case THREE_OF_A_KIND
    case STRAIGHT
    case FLUSH
    case FULL_HOUSE
    case FOUR_OF_A_KIND
    case STRAIGHT_FLUSH
    case ROYAL_FLUSH
    
    var score: Int {
        switch self {
        case .HIGH_CARD: return 100
        case .ONE_PAIR: return 200
        case .TWO_PAIR: return 300
        case .THREE_OF_A_KIND: return 400
        case .STRAIGHT: return 500
        case .FLUSH: return 600
        case .FULL_HOUSE: return 700
        case .FOUR_OF_A_KIND: return 800
        case .STRAIGHT_FLUSH: return 900
        case .ROYAL_FLUSH: return 1000
        }
    }
}

struct Card {
    let rank: Int
    let suit: String
}

class Poker{
    class func getScore(cards: [Card]) -> Int {
        return 0
    }
}
```

PokerTest.swiftの画面でCmd+Uでテスト実行。
当然、Testは失敗する。
（PokerTest.swiftのコード上に赤いXマークがつく）

## Green

HIGH_CARDをreturnするように修正。

```swift
class Poker{
    class func getScore(cards: [Card]) -> Int {
        return Hands.HIGH_CARD.score
    }
}
```

PokerTest.swiftの画面でCmd+Uでテスト実行。
Testが成功する。
（PokerTest.swiftのコード上に緑のチェックマークがつく）

## Refactor

この時点ではなし。

# ワンペアの実装

## Red

PokerTest.swiftに次のメソッドを追加してテスト実行

```swift
func testOnePair() {
	let cards = [
        Card(rank:9, suit:"♣"),
        Card(rank:9, suit:"♦"),
        Card(rank:1, suit:"♣"),
        Card(rank:8, suit:"♥"),
        Card(rank:5, suit:"♠")
    ]
    XCTAssertEqual(Poker.getScore(cards), Hands.ONE_PAIR.score)
}
```

## Green

Poker.swift#getScoreを次のとおり修正してテスト実行

```swift
class func getScore(cards: [Card]) -> Int {
    // 各ランクがそれぞれ何枚あるかをnumOfRanksに保存
    var numOfRanks = [0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    for card in cards {
        numOfRanks[card.rank]++
    }
    
    // ONE_PAIR
    if find(numOfRanks, 2) != nil {
        return Hands.ONE_PAIR.score
    }
    
    return Hands.HIGH_CARD.score
}
```

## Refactor

numOfRanksの引数がrankの値になっているのがわかりにくいのでリファクタリングしたい。
でも、ちょっといい案が浮かばないので。。。

# そんな感じでやった結果

* とりあえず、動いたけど、色々わかってない。
* Collectionクラスのメソッドはもっと気の利いたのが色々あるかと思ったらなくて残念。
* きっとSwiftらしくない書き方なんだと思う。。。

PokerTest.swift

```swift
import XCTest

class PokerTest: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testHightCard() {
        let cards = [
            Card(rank:1,  suit:"♣"),
            Card(rank:9,  suit:"♣"),
            Card(rank:8,  suit:"♥"),
            Card(rank:7,  suit:"♦"),
            Card(rank:5,  suit:"♠")
        ];
        XCTAssertEqual(Poker.getScore(cards), Hands.HIGH_CARD.score)
    }
    
    func testOnePair() {
        let cards = [
            Card(rank:9, suit:"♣"),
            Card(rank:9, suit:"♦"),
            Card(rank:1, suit:"♣"),
            Card(rank:8, suit:"♥"),
            Card(rank:5, suit:"♠")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.ONE_PAIR.score)
    }
    
    func testTwoPair(){
        let cards = [
            Card(rank:9,  suit:"♣"),
            Card(rank:9,  suit:"♦"),
            Card(rank:5,  suit:"♣"),
            Card(rank:5,  suit:"♥"),
            Card(rank:1,  suit:"♠")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.TWO_PAIR.score)
    }
    
    func testThreeOfAKind(){
        let cards = [
            Card(rank:9,  suit:"♣"),
            Card(rank:9,  suit:"♦"),
            Card(rank:9,  suit:"♠"),
            Card(rank:1,  suit:"♥"),
            Card(rank:7,  suit:"♠")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.THREE_OF_A_KIND.score)
    }
    
    func testStraight(){
        let cards = [
            Card(rank:8,  suit:"♥"),
            Card(rank:7,  suit:"♦"),
            Card(rank:6,  suit:"♦"),
            Card(rank:5,  suit:"♠"),
            Card(rank:4,  suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.STRAIGHT.score)
    }
    
    func testStraight2(){
        let cards = [
            Card(rank:13,  suit:"♥"),
            Card(rank:1,  suit:"♦"),
            Card(rank:2,  suit:"♦"),
            Card(rank:3,  suit:"♠"),
            Card(rank:4,  suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.STRAIGHT.score)
    }
    
    func testFlush(){
        let cards = [
            Card(rank:13, suit:"♥"),
            Card(rank:10, suit:"♥"),
            Card(rank:9,  suit:"♥"),
            Card(rank:6,  suit:"♥"),
            Card(rank:2,  suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.FLUSH.score)
    }
    
    func testFullHouse(){
        let cards = [
            Card(rank:5, suit:"♣"),
            Card(rank:5, suit:"♦"),
            Card(rank:5, suit:"♥"),
            Card(rank:8, suit:"♥"),
            Card(rank:8, suit:"♠")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.FULL_HOUSE.score)
    }
    
    func testFourOfAKind(){
        let cards = [
            Card(rank:5, suit:"♣"),
            Card(rank:5, suit:"♦"),
            Card(rank:5, suit:"♥"),
            Card(rank:5, suit:"♠"),
            Card(rank:8, suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.FOUR_OF_A_KIND.score)
    }
    
    func testStraightFlush(){
        let cards = [
            Card(rank:8, suit:"♥"),
            Card(rank:7, suit:"♥"),
            Card(rank:6, suit:"♥"),
            Card(rank:5, suit:"♥"),
            Card(rank:4, suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.STRAIGHT_FLUSH.score)
    }
    
    func testRoyalStraightFlush(){
        let cards = [
            Card(rank:10, suit:"♥"),
            Card(rank:11, suit:"♥"),
            Card(rank:12, suit:"♥"),
            Card(rank:13, suit:"♥"),
            Card(rank:1, suit:"♥")
        ]
        XCTAssertEqual(Poker.getScore(cards), Hands.ROYAL_FLUSH.score)
    }
    
}

```

Poker.swift

```swift
enum Hands {
    case HIGH_CARD
    case ONE_PAIR
    case TWO_PAIR
    case THREE_OF_A_KIND
    case STRAIGHT
    case FLUSH
    case FULL_HOUSE
    case FOUR_OF_A_KIND
    case STRAIGHT_FLUSH
    case ROYAL_FLUSH
    
    var score: Int {
        switch self {
        case .HIGH_CARD: return 100
        case .ONE_PAIR: return 200
        case .TWO_PAIR: return 300
        case .THREE_OF_A_KIND: return 400
        case .STRAIGHT: return 500
        case .FLUSH: return 600
        case .FULL_HOUSE: return 700
        case .FOUR_OF_A_KIND: return 800
        case .STRAIGHT_FLUSH: return 900
        case .ROYAL_FLUSH: return 1000
        }
    }
}

struct Card {
    let rank: Int
    let suit: String
}

struct NUM {
    static let MAX_RANK = 13 // カードの最高ランク（キング）
    static let CARDS = 5 // 手札の数
}

class Poker{
    class func getScore(cards: [Card]) -> Int {
        // 各ランクがそれぞれ何枚あるかをnumOfRanksに保存
        var numOfRanks = [0,0,0,0,0,0,0,0,0,0,0,0,0,0]
        for card in cards {
            numOfRanks[card.rank]++
        }
        
        // ROYAL_STRAIGHT_FLUSH
        if isStraight(cards, numOfRanks: numOfRanks) && isFlush(cards) && numOfRanks[10] == 1 && numOfRanks[1] == 1 {
            return Hands.ROYAL_FLUSH.score
        }
        // STRAIGHT_FLUSH
        if isStraight(cards, numOfRanks: numOfRanks) && isFlush(cards) {
            return Hands.STRAIGHT_FLUSH.score
        }
        // FOUR_OF_A_KIND
        if find(numOfRanks, 4) != nil {
            return Hands.FOUR_OF_A_KIND.score
        }
        // FULL_HOUSE
        if find(numOfRanks, 3) != nil && find(numOfRanks, 2) != nil {
            return Hands.FULL_HOUSE.score
        }
        // FLUSH
        if isFlush(cards) {
            return Hands.FLUSH.score
        }
        // STRAIGHT
        if isStraight(cards, numOfRanks: numOfRanks) {
            return Hands.STRAIGHT.score
        }
        // THREE_OF_A_KIND
        if find(numOfRanks, 3) != nil {
            return Hands.THREE_OF_A_KIND.score
        }
        // TWO_PAIR
        if numOfRanks.filter({$0 == 2}).count == 2 {
            return Hands.TWO_PAIR.score
        }
        // ONE_PAIR
        if find(numOfRanks, 2) != nil {
            return Hands.ONE_PAIR.score
        }
        // HIGH_CARD
        return Hands.HIGH_CARD.score
    }
    
    /**
     * STRAIGHTの判定
     * numOfRanksの中から５枚とって、全部が1のものがあればストレート
     * 10, 11, 12, 13, 1のように巡回するパターンもあるので、はじめにnumOfRanks + numOfRanks[1 ... NUM_OF_CARDS-1]として巡回させている
     */
    private class func isStraight(cards: [Card], numOfRanks: [Int]) -> Bool {
        var isStraight = false
        for var i = 1; i <= NUM.MAX_RANK; i++ {
            if (numOfRanks + numOfRanks[1 ... NUM.CARDS-1])[i ..< i+NUM.CARDS].reduce(true, { $0 && $1 == 1 }) {
                isStraight = true
                break
            }
        }
        return isStraight
    }
    
    // FLUSHの判定
    private class func isFlush(cards: [Card]) -> Bool {
        var isFlush = true
        for var i = 1; i < NUM.CARDS; i++ {
            isFlush = isFlush && cards[i-1].suit == cards[i].suit
        }
        return isFlush
    }
}
```