LoginSignup
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-10-16

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

コード(新)

@htsignさんから頂いたコメントを元に修正。

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"


コード(旧)

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

テスト

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


// 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キャストされない。
なんでうまくいかないのかずっと悩んでた。


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も未掲載関数があるんですけど
それ以前に、そもそもCollections.Arrayのページがぶっ壊れたまま放置されているような気が(;´∀`)

image.png

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3