Poker hand analyser: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|Python}}: Make sure cards in a hand are unique.)
(→‎{{header|Perl 6}}: Show output)
Line 152: Line 152:
}
}
</lang>
</lang>

Output:

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♣ 4♣: flush


=={{header|Python}}==
=={{header|Python}}==

Revision as of 23:44, 10 December 2013

Poker hand analyser is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Create a program to parse a single 5 card poker hand and rank it according to the List of poker hands [1].

A poker hand is specified as a space separated list of 5 playing cards. Each input card has two characters indicating face and suit. For 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: ♥ ♦ ♣ ♠

For extra credit, use the playing card characters introduced with Unicode 6.0 (U+1F0A1 - U+1F0DE).

Duplicate cards are illegal.

The program should analyse 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♣ 4♣: flush

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

Perl 6

This example does not show the output mentioned in the task description on this page (or a page linked to from here). (task was changed to require this Please ensure that it meets all task requirements and remove this message.
Note that phrases in task descriptions such as "print and display" and "print and show" for example, indicate that (reasonable length) output be a part of a language's solution.

}} This solution is written as a Perl 6 grammar. <lang perl6>use v6;

grammar PokerHand {

   # Perl6 Grammar to parse and rank 5-card poker hands
   # E.g. PokerHand.parse("2♥ 3♥ 2♦ 3♣ 3♦");
   rule TOP {
       <hand>
       :my ($n, $flush, $straight);
       {
           $n        = n-of-a-kind($<hand>);
           $flush    = flush($<hand>);
           $straight = straight($<hand>);
       }
       <rank($n, $flush, $straight)>
   }
   proto token suit {*}
   token suit:sym<♥>  {<sym>}
   token suit:sym<♦>  {<sym>}
   token suit:sym<♠>  {<sym>}
   token suit:sym<♣>  {<sym>}
   token face {:i <[2..9]> | 10 | j | q | k | a }
  token card {<face><suit> <?{
           my $card = ~$/;
           ! %*PLAYED{$card.lc}++;
       }>
   }
   rule hand {
       :my %*PLAYED;
       { %*PLAYED = () }
       [ <card> ]**5
    }
   token rank($n, $flush, $straight) {
           $<straight-flush>  = <?{$straight && $flush}>
        || $<four-of-a-kind>  = <?{$n[0] == 4}>
        || $<full-house>      = <?{$n[0] == 3 && $n[1] == 2}>
        || $<flush>           = <?{$flush}>
        || $<straight>        = <?{$straight}>
        || $<three-of-a-kind> = <?{$n[0] == 3}>
        || $<two-pair>        = <?{$n[0] == 2 && $n[1] == 2}>
        || $<one-pair>        = <?{$n[0] == 2}>
        || $<high-card>       = <?>
   }
   sub n-of-a-kind($/) {
       my %n;
       for @<card> -> $/ {
           %n{ ~$<face> }++
       }
       return %n.values.sort: {$^b <=> $^a};
   }
   sub flush($/) {
       my %m;
       for @<card> -> $/ {
           %m{ ~$<suit> }++
       }
       return +%m.keys == 1;
   }
   sub straight($/) {
       # allow both ace-low and ace-high straights
       my @seq = 'a', 2 .. 10, < j q k a >;
       my %got;
       for @<card> -> $/ {
           %got{ ~$<face>.lc }++
       }
       my $run = 0;
       for @seq {
           if %got{ $_ } {
               return True
                   if ++$run >= 5;
           }
           else {
               $run = 0;
           }
       }
       return False;
   }

}

for ("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♣ 4♣",  # flush
  ) {
  PokerHand.parse($_);
  my $rank = $<rank>
     ?? $<rank>.caps
     !! 'invalid';
  say "$_: $rank";

} </lang>

Output:

   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♣ 4♣: flush

Python

Goes a little further in also giving the ordered tie-breaker information from the wikipedia page. <lang python>from collections import namedtuple

class Card(namedtuple('Card', 'face, suit')):

   def __repr__(self):
       return .join(self)


suit = '♥ ♦ ♣ ♠'.split()

  1. ordered strings of faces

faces = '2 3 4 5 6 7 8 9 10 j q k a' lowaces = 'a 2 3 4 5 6 7 8 9 10 j q k'

  1. faces as lists

face = faces.split() lowace = lowaces.split()


def straightflush(hand):

   f,fs = ( (lowace, lowaces) if any(card.face == '2' for card in hand)
            else (face, faces) )
   ordered = sorted(hand, key=lambda card: (f.index(card.face), card.suit))
   first, rest = ordered[0], ordered[1:]
   if ( all(card.suit == first.suit for card in rest) and
        ' '.join(card.face for card in ordered) in fs ):
       return 'straight-flush', ordered[-1].face
   return False

def fourofakind(hand):

   allfaces = [f for f,s in hand]
   allftypes = set(allfaces)
   if len(allftypes) != 2:
       return False
   for f in allftypes:
       if allfaces.count(f) == 4:
           allftypes.remove(f)
           return 'four-of-a-kind', [f, allftypes.pop()]
   else:
       return False

def fullhouse(hand):

   allfaces = [f for f,s in hand]
   allftypes = set(allfaces)
   if len(allftypes) != 2:
       return False
   for f in allftypes:
       if allfaces.count(f) == 3:
           allftypes.remove(f)
           return 'full-house', [f, allftypes.pop()]
   else:
       return False

def flush(hand):

   allstypes = {s for f, s in hand}
   if len(allstypes) == 1:
       allfaces = [f for f,s in hand]
       return 'flush', sorted(allfaces,
                              key=lambda f: face.index(f),
                              reverse=True)
   return False

def straight(hand):

   f,fs = ( (lowace, lowaces) if any(card.face == '2' for card in hand)
            else (face, faces) )
   ordered = sorted(hand, key=lambda card: (f.index(card.face), card.suit))
   first, rest = ordered[0], ordered[1:]
   if ' '.join(card.face for card in ordered) in fs:
       return 'straight', ordered[-1].face
   return False

def threeofakind(hand):

   allfaces = [f for f,s in hand]
   allftypes = set(allfaces)
   if len(allftypes) <= 2:
       return False
   for f in allftypes:
       if allfaces.count(f) == 3:
           allftypes.remove(f)
           return ('three-of-a-kind', [f] +
                    sorted(allftypes,
                           key=lambda f: face.index(f),
                           reverse=True))
   else:
       return False

def twopair(hand):

   allfaces = [f for f,s in hand]
   allftypes = set(allfaces)
   pairs = [f for f in allftypes if allfaces.count(f) == 2]
   if len(pairs) != 2:
       return False
   p0, p1 = pairs
   other = [(allftypes - set(pairs)).pop()]
   return 'two-pair', pairs + other if face.index(p0) > face.index(p1) else pairs[::-1] + other

def onepair(hand):

   allfaces = [f for f,s in hand]
   allftypes = set(allfaces)
   pairs = [f for f in allftypes if allfaces.count(f) == 2]
   if len(pairs) != 1:
       return False
   allftypes.remove(pairs[0])
   return 'one-pair', pairs + sorted(allftypes,
                                     key=lambda f: face.index(f),
                                     reverse=True)

def highcard(hand):

   allfaces = [f for f,s in hand]
   return 'high-card', sorted(allfaces,
                              key=lambda f: face.index(f),
                              reverse=True)

handrankorder = (straightflush, fourofakind, fullhouse,

                 flush, straight, threeofakind,
                 twopair, onepair, highcard)
             

def rank(cards):

   hand = handy(cards)
   for ranker in handrankorder:
       rank = ranker(hand)
       if rank:
           break
   assert rank, "Invalid: Failed to rank cards: %r" % cards
   return rank

def handy(cards='2♥ 2♦ 2♣ k♣ q♦'):

   hand = []
   for card in cards.split():
       f, s = card[:-1], card[-1]
       assert f in face, "Invalid: Don't understand card face %r" % f
       assert s in suit, "Invalid: Don't understand card suit %r" % s
       hand.append(Card(f, s))
   assert len(hand) == 5, "Invalid: Must be 5 cards in a hand, not %i" % len(hand)
   assert len(set(hand)) == 5, "Invalid: All cards in the hand must be unique %r" % cards
   return hand


if __name__ == '__main__':

   hands = ["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♣",
    ]
   print("%-18s %-15s %s" % ("HAND", "CATEGORY", "TIE-BREAKER"))
   for cards in hands:
       r = rank(cards)
       print("%-18r %-15s %r" % (cards, r[0], r[1]))</lang>
Output:
HAND               CATEGORY        TIE-BREAKER
'2♥ 2♦ 2♣ k♣ q♦'   three-of-a-kind ['2', 'k', 'q']
'2♥ 5♥ 7♦ 8♣ 9♠'   high-card       ['9', '8', '7', '5', '2']
'a♥ 2♦ 3♣ 4♣ 5♦'   straight        '5'
'2♥ 3♥ 2♦ 3♣ 3♦'   full-house      ['3', '2']
'2♥ 7♥ 2♦ 3♣ 3♦'   two-pair        ['3', '2', '7']
'2♥ 7♥ 7♦ 7♣ 7♠'   four-of-a-kind  ['7', '2']
'10♥ j♥ q♥ k♥ a♥'  straight-flush  'a'
'4♥ 4♠ k♠ 5♦ 10♠'  one-pair        ['4', 'k', '10', '5']
'q♣ 10♣ 7♣ 6♣ 4♣'  flush           ['q', '10', '7', '6', '4']

REXX

<lang rexx>/* REXX ---------------------------------------------------------------

  • 10.12.2013 Walter Pachl
  • --------------------------------------------------------------------*/

d.1='2h 2d 2s ks qd'; x.1='three-of-a-kind' d.2='2h 5h 7d 8s 9d'; x.2='high-card' d.3='ah 2d 3s 4s 5s'; x.3='straight' d.4='2h 3h 2d 3s 3d'; x.4='full-house' d.5='2h 7h 2d 3s 3d'; x.5='two-pair' d.6='2h 7h 7d 7s 7c'; x.6='four-of-a-kind' d.7='th jh qh kh ah'; x.7='straight-flush' d.8='4h 4c kc 5d tc'; x.8='one-pair' d.9='qc tc 7c 6c 4c'; x.9='flush' d.10='ah 2h 3h 4h' d.11='ah 2h 3h 4h 5h 6h' d.12='2h 2h 3h 4h 5h' d.13='xh 2h 3h 4h 5h' d.14='2x 2h 3h 4h 5h' Do ci=1 To 14

 Call poker d.ci,x.ci
 end

Exit

poker: Parse Arg deck,expected have.=0 f.=0; fmax=0 s.=0; smax=0 cnt.=0 If words(deck)<5 Then Return err('less than 5 cards') If words(deck)>5 Then Return err('more than 5 cards') Do i=1 To 5

 c=word(deck,i)
 Parse Var c f +1 s
 If have.f.s=1 Then Return err('duplicate card:' c)
 have.f.s=1
 m=pos(f,'a23456789tjqk')
 If m=0 Then Return err('invalid face' f 'in' c)
 cnt.m=cnt.m+1
 n=pos(s,'hdcs')
 If n=0 Then Return err('invalid suit' s 'in' c)
 f.m=f.m+1; fmax=max(fmax,f.m)
 s.n=s.n+1; smax=max(smax,s.n)
 End

cntl= cnt.14=cnt.1 Do i=1 To 14

 cntl=cntl||cnt.i
 End

Select

 When fmax=4 Then res='four-of-a-kind'
 When fmax=3 Then Do
   If x_pair() Then
     res='full-house'
   Else
     res='three-of-a-kind'
   End
 When fmax=2 Then Do
   If x_2pair() Then
     res='two-pair'
   Else
     res='one-pair'
   End
 When smax=5 Then Do
   If x_street() Then
     res='straight-flush'
   Else
     res='flush'
   End
 When x_street() Then
   res='straight'
 Otherwise
   res='high-card'
 End

Say deck res If res<>expected Then

 Say copies(' ',14) expected

Return

x_pair:

 Do p=1 To 13
   If f.p=2 Then return 1
   End
 Return 0

x_2pair:

 pp=0
 Do p=1 To 13
   If f.p=2 Then pp=pp+1
   End
 Return pp=2

x_street:

 Return pos('11111',cntl)>0

err:

 Say deck 'Error:' arg(1)
 Return 0</lang>
Output:
2h 2d 2s ks qd three-of-a-kind
2h 5h 7d 8s 9d high-card
ah 2d 3s 4s 5s straight
2h 3h 2d 3s 3d full-house
2h 7h 2d 3s 3d two-pair
2h 7h 7d 7s 7c four-of-a-kind
th jh qh kh ah straight-flush
4h 4c kc 5d tc one-pair
qc tc 7c 6c 4c flush
ah 2h 3h 4h Error: less than 5 cards
ah 2h 3h 4h 5h 6h Error: more than 5 cards
2h 2h 3h 4h 5h Error: duplicate card: 2h
xh 2h 3h 4h 5h Error: invalid face x in xh
2x 2h 3h 4h 5h Error: invalid suit x in 2x