Task

Create a program to parse a single five card poker hand and rank it according to this list of poker hands.



A poker hand is specified as a space separated list of five playing cards.

Each input card has two characters indicating face and suit.



Example

2d       (two of diamonds).


Faces are:    a, 2, 3, 4, 5, 6, 7, 8, 9, 10, j, q, k

Suits are:    h (hearts),   d (diamonds),   c (clubs),   and   s (spades),   or

alternatively,   the unicode card-suit characters:     ♥ ♦ ♣ ♠ 



Duplicate cards are illegal.

The program should analyze a single hand and produce one of the following outputs:

 straight-flush
 four-of-a-kind
 full-house
 flush
 straight
 three-of-a-kind
 two-pair
 one-pair
 high-card
 invalid



Examples

   2♥ 2♦ 2♣ k♣ q♦:   three-of-a-kind
   2♥ 5♥ 7♦ 8♣ 9♠:   high-card
   a♥ 2♦ 3♣ 4♣ 5♦:   straight
   2♥ 3♥ 2♦ 3♣ 3♦:   full-house
   2♥ 7♥ 2♦ 3♣ 3♦:   two-pair
   2♥ 7♥ 7♦ 7♣ 7♠:   four-of-a-kind 
   10♥ j♥ q♥ k♥ a♥:  straight-flush
   4♥ 4♠ k♠ 5♦ 10♠:  one-pair
   q♣ 10♣ 7♣ 6♣ q♣:  invalid

The programs output for the above examples should be displayed here on this page.



Extra credit

 use the playing card characters introduced with Unicode 6.0 (U+1F0A1 - U+1F0DE).
 allow two jokers
 use the symbol   joker
 duplicates would be allowed (for jokers only)
 five-of-a-kind would then be the highest hand


More extra credit examples

   joker  2♦  2♠  k♠  q♦:     three-of-a-kind
   joker  5♥  7♦  8♠  9♦:     straight
   joker  2♦  3♠  4♠  5♠:     straight
   joker  3♥  2♦  3♠  3♦:     four-of-a-kind
   joker  7♥  2♦  3♠  3♦:     three-of-a-kind
   joker  7♥  7♦  7♠  7♣:     five-of-a-kind
   joker  j♥  q♥  k♥  A♥:     straight-flush
   joker  4♣  k♣  5♦ 10♠:     one-pair
   joker  k♣  7♣  6♣  4♣:     flush
   joker  2♦  joker  4♠  5♠:  straight
   joker  Q♦  joker  A♠ 10♠:  straight
   joker  Q♦  joker  A♦ 10♦:  straight-flush
   joker  2♦  2♠  joker  q♦:  four-of-a-kind



Related tasks

 Playing cards
 Card shuffles
 Deal cards_for_FreeCell
 War Card_Game
 Go Fish


import algorithm, sequtils, strutils, tables, unicode
 
type
 
  Suit* = enum ♠, ♥, ♦, ♣
  Face* {.pure.} = enum
    Ace = (1, "a")
    Two = (2, "2")
    Three = (3, "3")
    Four = (4, "4")
    Five = (5, "5")
    Six = (6, "6")
    Seven = (7, "7")
    Eight = (8, "8")
    Nine = (9, "9")
    Ten = (10, "10")
    Jack = (11, "j")
    Queen = (12, "q")
    King = (13, "k")
 
  Card* = tuple[face: Face; suit: Suit]
  Hand* = array[5, Card]
 
  HandValue {.pure.} = enum
    Invalid = "invalid"
    StraightFlush = "straight-flush"
    FourOfAKind = "four-of-a-kind"
    FullHouse = "full-house"
    Flush = "flush"
    Straight = "straight"
    ThreeOfAKind = "three-of-a-kind"
    TwoPair = "two-pair"
    OnePair = "one-pair"
    HighCard = "high-card"
 
  CardError = object of ValueError
 
 
proc toCard(cardStr: string): Card =
  ## Convert a card string to a Card.
  var runes = cardStr.toRunes
  let suitStr = $(runes.pop())  # Extract the suit.
  let faceStr = $runes          # Take what’s left as the face.
  try:
    result.face = parseEnum[Face](faceStr)
  except ValueError:
    raise newException(CardError, "wrong face: " & faceStr)
  try:
    result.suit = parseEnum[Suit](suitStr)
  except ValueError:
    raise newException(CardError, "wrong suit: " & suitStr)
 
 
proc value(hand: openArray[Card]): HandValue =
  ## Return the value of a hand.
 
  doAssert hand.len == 5, "Hand must have five cards."
 
  var
    cards: seq[Card]          # The cards.
    faces: CountTable[Face]   # Count faces.
    suits: CountTable[Suit]   # Count suits.
 
  for card in hand:
    if card in cards: return Invalid    # Duplicate card.
    cards.add card
    faces.inc card.face
    suits.inc card.suit
 
  faces.sort()  # Greatest counts first.
  suits.sort()  # Greatest counts first.
  cards.sort()  # Smallest faces first.
 
  # Check faces.
  for face, count in faces:
    case count
    of 4:
      return FourOfAKind
    of 3:
      result = ThreeOfAKind
    of 2:
      if result == ThreeOfAKind: return FullHouse
      if result == OnePair: return TwoPair
      result = OnePair
    else:
      if result != Invalid: return
 
  # Search straight.
  result = Straight
  let start = if cards[0].face == Ace and cards[4].face == King: 2 else: 1
  for n in start..4:
    if cards[n].face != succ(cards[n - 1].face):
      result = HighCard   # No straight.
      break
 
  # Check suits.
  if suits.len == 1:   # A single suit.
    result = if result == Straight: StraightFlush else: Flush
 
 
proc `$`(card: Card): string =
  ## Return the representation of a card.
  var val = 0x1F0A0 + ord(card.suit) * 0x10 + ord(card.face)
  if card.face >= Queen: inc val    # Skip Knight.
  result = $Rune(val)
 
 
 
when isMainModule:
 
  const HandStrings = ["2♥ 2♦ 2♣ k♣ q♦",
                       "2♥ 5♥ 7♦ 8♣ 9♠",
                       "a♥ 2♦ 3♣ 4♣ 5♦",
                       "2♥ 3♥ 2♦ 3♣ 3♦",
                       "2♥ 7♥ 2♦ 3♣ 3♦",
                       "2♥ 7♥ 7♦ 7♣ 7♠",
                       "10♥ j♥ q♥ k♥ a♥",
                       "4♥ 4♠ k♠ 5♦ 10♠",
                       "q♣ 10♣ 7♣ 6♣ 4♣",
                       "4♥ 4♣ 4♥ 4♠ 4♦"]
 
  for handString in HandStrings:
    let hand = handString.split(' ').map(toCard)
    echo hand.map(`$`).join("  "), "  → ", hand.value