極力高階関数を適用すべく、ポーカーを書いてみた。
ジョーカーを除く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)
もし、もっと綺麗に書けるよ!とかあったら教えてください
…ところでMSDNのF# ArrayもListも未掲載関数があるんですけど
それ以前に、そもそもCollections.Arrayのページがぶっ壊れたまま放置されているような気が(;´∀`)