高階関数だらけのポーカー

極力高階関数を適用すべく、ポーカーを書いてみた。
ジョーカーを除く52枚のカードを利用、カード交換はなし、初回の手札について判定。

##コード(新)
@htsignさんから頂いたコメントを元に修正。

```fsharp
open System

// Number that circulates from 1 to 13
// ex1) A - 1 => K
type Suit = Heart | Clover | Spade | Diamond

type CardNumber = Ace | Jack | Queen | King | Other of int with

    static member (-) (x : CardNumber, y : CardNumber) =
        let subValue = (int x) - (int y)
        CardNumber.Create <| if subValue < 1 then subValue + 13 else subValue

    static member op_Explicit (x : CardNumber) : int =
        match x with
        | Ace -> 1
        | Jack -> 11
        | Queen -> 12
        | King -> 13
        | Other n -> n

    static member Create = function
        | 1 -> Ace
        | 11 -> Jack
        | 12 -> Queen
        | 13 -> King
        | n -> Other n

// Playing card
type Card = 
    {
      Number : CardNumber
      Suit : Suit
    }

    static member Create number suit =
        {
            Number = CardNumber.Create number
            Suit = suit
        }

    override x.ToString() =
      x.Suit.ToString() + x.Number.ToString()

let makeSuitCards suit = 
     [for i in 1..13 -> Card.Create i suit]

// Poker Hand
let isFlash groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 5)

let isStraight handCards =
    handCards |> Array.map (fun x -> x.Number) |> Array.pairwise |> Array.forall (fun (a, b) -> int(b - a) = 1)

let isStraightFlash handCards groups =
    isStraight handCards && isFlash groups

let isRoyalStraightFlash handCards groups = 
    isStraightFlash handCards groups 
    && Array.exists(fun elm -> elm.Number = (CardNumber.Create 1)) handCards 
    && Array.exists(fun elm -> elm.Number = (CardNumber.Create 10)) handCards

let isThreeCard groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 3)

let isFourCard groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 4)

let isTwoPair groups =
    groups |> Array.filter(fun elm -> Array.length (snd elm) = 2) |> Array.length = 2

let isOnePair groups =
    groups |> Array.filter(fun elm -> Array.length (snd elm) = 2) |> Array.length = 1

let isFullHouse groups = 
    isThreeCard groups && isOnePair groups


// Grouping
let groupBySuit handCards =
    handCards 
    |> Array.groupBy (fun { Suit = suit } -> suit)

let groupByNumber handCards = 
    handCards |> Array.groupBy(fun elm -> elm.Number)


let play =
    printfn "*play*"

    // Initialize Cards
    let makeDeck = [Heart; Clover; Spade; Diamond] |> List.collect makeSuitCards
    let cards = List.toArray makeDeck |> Array.sortBy(fun elm -> Guid.NewGuid())

    let handCards = Array.truncate 5 cards |> Array.sortBy(fun elm -> elm.Number)
    handCards |> printfn "handCards:%A"

    let handCardsSuitGroup = groupBySuit handCards 
    let handCardNumberGroup = groupByNumber handCards

    isRoyalStraightFlash handCards handCardsSuitGroup
    |> printfn "isRoyalStraightFlash:%A"

    isStraightFlash handCards handCardsSuitGroup
    |> printfn "isStraightFlash:%A"

    isFourCard handCardNumberGroup
    |> printfn "isFourCard:%A"

    isFullHouse handCardNumberGroup
    |> printfn "isFullHouse:%A"

    isFlash handCardsSuitGroup
    |> printfn "isFlash:%A"

    isStraight handCards
    |> printfn "isStraight:%A"

    isThreeCard handCardNumberGroup
    |> printfn "isThreeCard:%A"

    isTwoPair handCardNumberGroup
    |> printfn "isTwoPair:%A"

    isOnePair handCardNumberGroup
    |> printfn "isOnePair:%A"


```

##コード(旧)
```fsharp
open System

// Number that circulates from 1 to 13
// ex1) K + 1 => A
// ex2) A - 1 => K
type CardNumber(value) =
    member x.value = value

    static member (+) (x: CardNumber, y: int) =
      let addValue = x.value + y 
      CardNumber(if addValue > 13 then addValue - 13 else addValue)

    static member (-) (x: CardNumber, y: int) =
      let subValue = x.value - y 
      CardNumber(if subValue < 1 then subValue + 13 else subValue)

    override x.GetHashCode() =
      hash(x.value)

    override x.Equals(arg) =
      match arg with
      | :? CardNumber as target -> value = target.value
      | _ -> false

// Playing card
type Card = 
  {
    Number : CardNumber
    Suit : string
    Write : string
  }

  override x.ToString() =
    x.Suit + x.Write

let makeSuitCards suit = 
  [ for i in 1..13 -> 
    {
      Number = CardNumber(i);
      Suit = suit;
      Write = 
        match i with 
        | 1  -> "A"
        | 11 -> "J"
        | 12 -> "Q"
        | 13 -> "K"
        | _ -> i.ToString()
    }
  ]

// Poker Hand
let isFlash groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 5)

let isStraight handCards =
    let firstNumber = (Array.head handCards).Number
    let straightCards = Array.init 5 (fun elm -> {Number = firstNumber + elm;Suit = "?"; Write = "?";})
    Array.forall2(fun elm1 elm2 -> elm1.Number = elm2.Number) handCards straightCards

let isStraightFlash handCards groups =
    isStraight handCards && isFlash groups

let isRoyalStraightFlash handCards groups = 
    isStraightFlash handCards groups 
    && Array.exists(fun elm -> elm.Write = "A") handCards 
    && Array.exists(fun elm -> elm.Write = "10") handCards

let isThreeCard groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 3)

let isFourCard groups = 
    groups |> Array.exists(fun elm -> Array.length (snd elm) = 4)

let isTwoPair groups =
    groups |> Array.filter(fun elm -> Array.length (snd elm) = 2) |> Array.length = 2

let isOnePair groups =
    groups |> Array.filter(fun elm -> Array.length (snd elm) = 2) |> Array.length = 1

let isFullHouse groups = 
    isThreeCard groups && isOnePair groups


// Grouping
let groupBySuit handCards =
  handCards 
  |> Array.groupBy(fun elm ->
      match elm.Suit with 
      | "H" -> "H" 
      | "C" -> "C" 
      | "S" -> "S" 
      | "D" -> "D" 
      | _ -> "?"
    )

let groupByNumber handCards = 
  handCards |> Array.groupBy(fun elm -> elm.Number)


let play =
    printfn "*play*"

    // Initialize Cards
    let makeDeck = [Heart; Clover; Spade; Diamond] |> List.collect makeSuitCards
    let cards = List.toArray makeDeck |> Array.sortBy(fun elm -> Guid.NewGuid())

    let handCards = Array.truncate 5 cards |> Array.sortBy(fun elm -> elm.Number)
    handCards |> printfn "handCards:%A"

    let handCardsSuitGroup = groupBySuit handCards 
    let handCardNumberGroup = groupByNumber handCards

    isRoyalStraightFlash handCards handCardsSuitGroup
    |> printfn "isStraightFlash:%A"

    isStraightFlash handCards handCardsSuitGroup
    |> printfn "isStraightFlash:%A"

    isFourCard handCardNumberGroup
    |> printfn "isFourCard:%A"

    isFullHouse handCardNumberGroup
    |> printfn "isFullHouse:%A"

    isFlash handCardsSuitGroup
    |> printfn "isFlash:%A"

    isStraight handCards
    |> printfn "isStraight:%A"

    isThreeCard handCardNumberGroup
    |> printfn "isThreeCard:%A"

    isTwoPair handCardNumberGroup
    |> printfn "isTwoPair:%A"

    isOnePair handCardNumberGroup
    |> printfn "isOnePair:%A"
```

##実行結果
```
*play*
handCards:HA,DA,CJ,HK,SK
isStraightFlash:false
isStraightFlash:false
isFourCard:false
isFullHouse:false
isFlash:false
isStraight:false
isThreeCard:false
isTwoPair:true
isOnePair:false


*play*
handCards:H9,C10,SJ,HQ,HK
isStraightFlash:false
isStraightFlash:false
isFourCard:false
isFullHouse:false
isFlash:false
isStraight:true
isThreeCard:false
isTwoPair:false
isOnePair:false
```

###テスト
ちゃんとしたテストフレームワーク入れないとダメやなぁ

```fsharp

// Test cards
let s9 = {Number=(CardNumber.Create 9);Suit=Spade;}
let c9 = {Number=(CardNumber.Create 9);Suit=Clover;}
let s10 = {Number=(CardNumber.Create 10);Suit=Spade;}
let c10 = {Number=(CardNumber.Create 10);Suit=Clover;}
let cJ = {Number=(CardNumber.Create 11);Suit=Clover;}
let cQ = {Number=(CardNumber.Create 12);Suit=Clover;}
let cK = {Number=(CardNumber.Create 13);Suit=Clover;}
let cA = {Number=(CardNumber.Create 1);Suit=Clover;}
let c2 = {Number=(CardNumber.Create 2);Suit=Clover;}
let c3 = {Number=(CardNumber.Create 3);Suit=Clover;}
let c4 = {Number=(CardNumber.Create 4);Suit=Clover;}
let c5 = {Number=(CardNumber.Create 5);Suit=Clover;}

let test =
    let royalStraightFlash1 = [|c10;cJ;cQ;cK;cA|]
    let royalStraightFlash2 = [|s10;cJ;cQ;cK;cA|]
    let straightFlash = [|c9;c10;cJ;cQ;cK;|]
    let fourCard = [|c9;c9;c9;c9;cK;|]
    let fullHouse = [|c9;c9;c9;cK;cK;|]
    let flash = [|c3;c5;c9;cQ;cK;|]
    let straight1 = [|cA;c2;c3;c4;c5|]
    let straight2 = [|c2;c3;c4;c5;cK;|]
    let straight3 = [|s9;c10;cJ;cQ;cK;|]
    let threeCard = [|c9;c9;c9;c10;cK;|]
    let twoPair = [|cA;cA;c2;c2;cK;|]
    let onePair = [|cA;cA;c9;c10;cK;|]
    
    printfn "*test*"
    isRoyalStraightFlash royalStraightFlash1 (groupBySuit royalStraightFlash1) = true
    |> printfn "1:%b"
    isRoyalStraightFlash royalStraightFlash2 (groupBySuit royalStraightFlash2) = false
    |> printfn "X:%b"
    isRoyalStraightFlash straightFlash (groupBySuit straightFlash) = false
    |> printfn "2:%b"
    isStraightFlash straightFlash (groupBySuit straightFlash) = true
    |> printfn "3:%b"
    isStraightFlash straight3 (groupBySuit straight3) = false
    |> printfn "4:%b"
    isFourCard (groupByNumber fourCard) = true
    |> printfn "5:%b"
    isFourCard (groupByNumber threeCard) = false
    |> printfn "6:%b"
    isFullHouse (groupByNumber fullHouse) = true
    |> printfn "7:%b"
    isFullHouse (groupByNumber threeCard) = false
    |> printfn "8:%b"
    isFullHouse (groupByNumber twoPair) = false
    |> printfn "9:%b"
    isFullHouse (groupByNumber onePair) = false
    |> printfn "10:%b"
    isFlash (groupBySuit flash) = true
    |> printfn "11:%b"
    isFlash (groupBySuit straight3) = false
    |> printfn "12:%b"
    isStraight straight1 = true
    |> printfn "13:%b"
    isStraight straight2 = false
    |> printfn "14:%b"
    isStraight straight3 = true
    |> printfn "15:%b"
    isThreeCard (groupByNumber threeCard) = true
    |> printfn "16:%b"
    isThreeCard (groupByNumber fourCard) = false
    |> printfn "17:%b"
    isTwoPair (groupByNumber fourCard) = false
    |> printfn "18:%b"
    isTwoPair (groupByNumber twoPair) = true
    |> printfn "19:%b"
    isTwoPair (groupByNumber onePair) = false
    |> printfn "20:%b"
    isOnePair (groupByNumber threeCard) = false
    |> printfn "21:%b"
    isOnePair (groupByNumber twoPair) = false
    |> printfn "22:%b"
    isOnePair (groupByNumber onePair) = true
    |> printfn "23:%b"

```

##所感
トランプって日本でだけ使われてる呼称なのね。

高階関数で書くのはかなり楽しい。
適切な関数を探すのはパズルを解いている感覚。

ストレートの実装が割と悩ましかった。
AとKの扱いが難しかったので、友人の案に従って1～13で循環するtypeを自作。

~~ストレートとロイヤルストレートフラッシュの実装があまり気に入っていない。
もっとスマートに書く方法があるような気もするが、思いつけず。
あとgroupBySuitもなんか気持ち悪い。~~

@htsignさんから頂いたコメントを元に修正するとめっちゃスマートになって感激。

少しだけ変更を加えていて、
・未使用になった+のオーバーロードの破棄
・ストレートの判定ロジックを変更

pairwiseからforallすると、aもbもCardNumber型(型というか、判別共用体？)になるので、
-オペレータでもらってくる引数をCardNumber型に変更、計算結果をintに戻すように修正。

ところで、Try F#でずっとコード書いてたんだけど
fableだとコンパイラがop_Explicitをうまく扱えないようで、ちゃんとintキャストされない。
なんでうまくいかないのかずっと悩んでた。

```fsharp

let isStraight handCards =
    handCards |> Array.map (fun x -> x.Number) |> Array.pairwise |> Array.forall (fun (a, b) -> int(b - a) = 1)
```

もし、もっと綺麗に書けるよ！とかあったら教えてください:pray:


…ところでMSDNのF# ArrayもListも[未掲載関数](https://github.com/MicrosoftDocs/visualfsharpdocs/issues/184)があるんですけど
それ以前に、そもそも[Collections.Array](https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/collections.array-module-%5bfsharp%5d
)のページがぶっ壊れたまま放置されているような気が(;´∀｀)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/270579/114edcfe-668e-441e-1f62-032f5ebd1c2a.png)