首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >F#中的扑克手卡塔

F#中的扑克手卡塔
EN

Code Review用户
提问于 2014-03-09 00:42:54
回答 3查看 1.3K关注 0票数 13

我是一个F#新手,我想分享我的扑克手卡塔问题实现,以获得一些反馈。

代码语言:javascript
复制
module PokerHands

open System.Text.RegularExpressions

type Figure = 
    | Two  | Three | Four  | Five 
    | Six  | Seven | Eight | Nine 
    | Ten  | Jack  | Queen | King | Ace

type Suit = Diamonds | Spades | Hearts | Clubs

type Card = Figure * Suit

type Rank =
  | HighCard        of Figure * Rank option
  | Pair            of Figure * Rank
  | TwoPair         of Figure * Figure * Rank
  | ThreeOfKind     of Figure
  | Straight        of Figure
  | Flush           of Rank
  | FullHouse       of Figure
  | FourOfKind      of Figure
  | StraightFlush   of Rank

type Player = {
    Name: string;
    Cards: Card list;
    Hand: Rank;
}

let SortDesc sequence = sequence |> Seq.toList |> List.sortWith (fun x y -> compare y x)
let SortDescBy f sequence = sequence |> List.sortWith (fun x y -> compare (f y) (f x))

let rec BuildHighCard (figures : Figure list) =
    match SortDesc figures with
    | [h] -> HighCard(h, None)
    | h :: t -> HighCard(h, Some(BuildHighCard(t)))
    | _ -> failwith "empty"

let isFlush (cards : Card list) =
    cards |> Seq.distinctBy snd |> Seq.length = 1

let isStraight (cards : Card list) =
    let figs = cards |> List.map fst |> List.sort
    let flag = ref true
    for i in {1 .. List.length figs - 1} do
        if (compare figs.[i-1] figs.[i]) <> -1 then flag.Value <- false
    flag.Value 

let (|IsStraightFlush|_|) cards = 
    if isFlush cards && isStraight cards then Some(StraightFlush(BuildHighCard(cards |> List.map fst))) else None

let (|IsFlush|_|) cards = 
    if isFlush cards then Some(Flush(BuildHighCard(cards |> List.map fst))) else None

let (|IsStraight|_|) cards = 
    if isStraight cards then Some(Straight(cards |> List.minBy fst |> fst)) else None

let (|IsGrouped|_|) (counts : int list) cards  =
    let groups = cards |> Seq.countBy fst |> Seq.toList |> SortDescBy snd
    if groups |> List.map snd = counts then Some(groups |> List.map fst) else None

let DetermineRank (cards : Card list) = 
    match cards with
    | IsStraightFlush straightFlush -> straightFlush
    | IsGrouped [4;1] [f;_]         -> FourOfKind(f)
    | IsGrouped [3;2] [f;_]         -> FullHouse(f)
    | IsFlush flush                 -> flush
    | IsStraight straight           -> straight
    | IsGrouped [3;1;1] [f;_;_]     -> ThreeOfKind(f)
    | IsGrouped [2;2;1] [f1;f2;rest]-> TwoPair(List.max [f1;f2], List.min [f1;f2], BuildHighCard([rest]))
    | IsGrouped [2;1;1;1] (h :: t)  -> Pair(h, BuildHighCard(t))
    | _                             -> BuildHighCard(cards |> List.map fst)

let ParsePlayers input = 
    let r = Regex("(\w+):( [2-9,T,J,Q,K,A][H,C,D,S]){5}")
    let matches = input |> r.Matches

    let parseCard (capture : Capture) =
        let literal = capture.Value.Trim()
        let figure = 
            match literal.[0] with
            | '2' -> Two
            | '3' -> Three
            | '4' -> Four
            | '5' -> Five
            | '6' -> Six
            | '7' -> Seven
            | '8' -> Eight
            | '9' -> Nine
            | 'T' -> Ten
            | 'J' -> Jack
            | 'Q' -> Queen
            | 'K' -> King
            | 'A' -> Ace
            | _ -> failwith "unrecognized figure"
        let suit = 
            match literal.[1] with
            | 'D' -> Diamonds
            | 'S' -> Spades
            | 'H' -> Hearts
            | 'C' -> Clubs
            | _ -> failwith "unrecognized suit"
        (figure, suit)

    let parsePlayer (input : Match) =
        let name = input.Groups.[1].Value
        let cards =
            input.Groups.[2].Captures
            |> Seq.cast
            |> Seq.map parseCard
            |> Seq.toList
        {Name = name; Cards = cards; Hand = DetermineRank cards}

    matches
    |> Seq.cast
    |> Seq.map parsePlayer
    |> Seq.toArray

type Score =
    | Win of string * string
    | Tie

let rec Rationale wonHand lostHand =
    let (|RanksEq|_|) (wonHand,lostHand) =
        match wonHand, lostHand with
        | StraightFlush(w), StraightFlush(l)                            -> Some (Rationale w l)
        | Flush(w), Flush(l)                                            -> Some (Rationale w l)
        | TwoPair(w1,w2,r1), TwoPair(l1,l2,r2) when w1 = l1 && w2 = l2  -> Some (Rationale r1 r2)
        | Pair(f1,r1), Pair(f2,r2) when f1 = f2                         -> Some (Rationale r1 r2)
        | HighCard(w,r1), HighCard(l,r2) when w = l                     -> Some (Rationale (Option.get r1) (Option.get r2))
        | _ -> None

    match wonHand, lostHand with
    | RanksEq(res)          -> res
    | StraightFlush(_), _   -> "straight flush"
    | FourOfKind(f), _      -> sprintf "four of kind: %A" f
    | FullHouse(f), _       -> "full house"
    | Flush(_), _           -> "flush"
    | Straight(f), _        -> "straight"
    | ThreeOfKind(f), _     -> sprintf "three of kind: %A" f
    | TwoPair(f1,f2,_), _   -> sprintf "two pairs: %A + %A" f1 f2
    | Pair(f,_), _          -> sprintf "pair of: %A" f
    | HighCard(w,_), _      -> sprintf "high card: %A" w
    | _                     -> failwith "unhandled rationale"

let DetermineScore (players : Player array) =
    let c = compare (players.[0].Hand) (players.[1].Hand)
    if c = 0
        then Tie 
    else
        let winner = players |> Array.maxBy (fun p -> p.Hand)
        let looser = players |> Array.minBy (fun p -> p.Hand)
        Win(winner.Name, Rationale winner.Hand looser.Hand)

let FormatScore score =
    match score with
    | Win(winner, rationale) -> sprintf "%s wins - %s" winner rationale
    | Tie -> "Tie"

let CompareHands input =
    input
    |> ParsePlayers
    |> DetermineScore
    |> FormatScore

乍一看,让人感到奇怪的是Rank歧视联盟。我决定用这种方式来实现它,这样我就可以比较一下Ranks了。

需要解释的另一件事是基本原理函数:它解决了获胜级别的字符串逻辑,内部RanksEq活动模式解决了两个级别都成对的情况--在这里,逻辑应该返回“高卡”。

以下是我所关心的问题:

  1. isStraight函数-如何实现一个纯函数外观的函数来检查序列中的所有元素是否是连续的(对于受歧视的联合类型)?
  2. IsStraightIsFlushIsStraightFlush活动模式,这三种模式都有共同之处--它们能以某种方式合并成一个活动模式吗?
  3. DetermineScore函数--我不喜欢使用Array.min和max从一对值中得到一个越低越高的值--是否有一个一行呢?比如返回元组Player * Player的东西?

对代码的任何其他评论都将非常欢迎。

EN

回答 3

Code Review用户

发布于 2014-03-14 22:32:08

我喜欢这个!我认为,你使用受歧视的结合和积极的模式是相当优雅的。(尽管利用受歧视工会的compare来做这件事,似乎有点不对--尽管我很难为这种情绪辩护。)

至于你的关切:

  1. 这里有一种手动的方法(将let flag ... flag.Value替换为此):让rec check = function x ::y ::zs -> compare x y <> -1 & check (y : zs) X -> true _ ->虚假检查图查看有关此样式的更多信息,请参见这里。或者,您可以尝试使用组合子。如果你只想比较五张牌的顺序,那就像这样吧?List.toSeq卡|> Seq.distinct |> Seq.toList |>函数x X;_;y ->比较x= -4 false _ -> false
  2. 我觉得这些没问题。我不认为你应该试着考虑一下看起来多余的BuildHighCard(cards |> List.map fst)。我认为如果你这样做了,你会把你的程序弄乱,而不是让它更易读。事实上,很清楚发生了什么事,是吗?
  3. 是的:让胜利者,失败者= Array.max球员,Array.min球员

但是,也许ParsePlayers已经返回一个元组而不是数组?

小建议:您可以将SortDescSortDescBy实现为:

代码语言:javascript
复制
let SortDescBy f = List.sortWith (fun x y -> compare (f y) (f x))    

一般来说,当您编写x |> f时,它的意思只是f x。所以当你定义

代码语言:javascript
复制
let f x = x |> g ...

你可以把中间的人裁掉

代码语言:javascript
复制
let f = g ...
票数 6
EN

Code Review用户

发布于 2014-03-18 20:39:22

以函数方式实现isStraight的方法是使用Seq.zip接收所有对i-th和(i+1)-th卡,然后确保所有对都满足使用Seq.forall的条件:

代码语言:javascript
复制
let isStraight (cards : Card list) =
    let figs = cards |> Seq.map fst |> Seq.sort
    let pairs = Seq.zip figs (Seq.skip 1 figs)
    Seq.forall (fun pair -> compare (fst pair) (snd pair) = -1) pairs

(我使用Seq而不是List,因为List.zip的行为略有不同,而且没有List.skip。)

票数 4
EN

Code Review用户

发布于 2014-03-21 08:05:05

结果是,有一个Seq.pairwise函数在本例中将与Seq.zip figs (Seq.skip 1 figs)一样工作。

因此,将@svick和@soren的建议放在一起,使用Seq.pairwise,我们可以编写如下isStraight函数:

代码语言:javascript
复制
let isStraight (cards : Card list) =
    cards
    |> Seq.map fst
    |> Seq.sort
    |> Seq.pairwise
    |> Seq.forall ((<||) compare >> (=) -1)
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/43831

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档