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


 
fn main() {
    let hands = vec![
        "🂡 🂮 🂭 🂫 🂪",
        "🃏 🃂 🂢 🂮 🃍",
        "🃏 🂵 🃇 🂨 🃉",
        "🃏 🃂 🂣 🂤 🂥",
        "🃏 🂳 🃂 🂣 🃃",
        "🃏 🂷 🃂 🂣 🃃",
        "🃏 🂷 🃇 🂧 🃗",
        "🃏 🂻 🂽 🂾 🂱",
        "🃏 🃔 🃞 🃅 🂪",
        "🃏 🃞 🃗 🃖 🃔",
        "🃏 🃂 🃟 🂤 🂥",
        "🃏 🃍 🃟 🂡 🂪",
        "🃏 🃍 🃟 🃁 🃊",
        "🃏 🃂 🂢 🃟 🃍",
        "🃏 🃂 🂢 🃍 🃍",
        "🃂 🃞 🃍 🃁 🃊",
    ];
    for hand in hands{
        println!("{} {}", hand, poker_hand(hand));
    }
}
 
fn poker_hand(cards: &str) -> &str {
    let mut suits = vec![0u8; 4];
    let mut faces = vec![0u8; 15];
    let mut hand = vec![];
 
    for card in cards.chars(){
        if card == ' ' { continue; }
        let values = get_card_value(card);
        if values.0 < 14 && hand.contains(&values) {
            return "invalid";
        }
        hand.push(values);
        faces[values.0 as usize]+=1;
        if values.1 >= 0 {
            suits[values.1 as usize]+=1;
        }
    }
    if hand.len()!=5 {
        return "invalid";
    }
    faces[13] = faces[0]; //add ace-high count
    let jokers = faces[14];
 
    //count suits
    let mut colors = suits.into_iter()
        .filter(|&x| x > 0).collect::<Vec<_>>();
    colors.sort_unstable();
    colors[0] += jokers; // add joker suits to the highest one;
    let is_flush = colors[0] == 5;
 
    //straight
    let mut is_straight = false;
    //pointer to optimise some work
    //avoids looking again at cards that were the start of a sequence
    //as they cannot be part of another sequence
    let mut ptr = 14;
    while ptr>3{
        let mut jokers_left = jokers;
        let mut straight_cards = 0;
        for i in (0..ptr).rev(){
            if faces[i]==0 {
                if jokers_left == 0 {break;}
                jokers_left -= 1;
            }
            else if i==ptr-1 { ptr-=1; }
            straight_cards+=1;
        }
        ptr-=1;
        if straight_cards == 5 {
            is_straight = true;
            break;
        }
    }
 
     //count values
     let mut values = faces.into_iter().enumerate().take(14).filter(|&x| x.1>0).collect::<Vec<_>>();
     //sort by quantity, then by value, high to low
     values.sort_unstable_by(|a, b| if b.1 == a.1 { (b.0).cmp(&a.0) } else { (b.1).cmp(&a.1)} );
     let first_group = values[0].1 + jokers;
     let second_group = if values.len()>1 {values[1].1} else {0};
 
     match (is_flush, is_straight, first_group, second_group){
        (_,_,5,_) => "five-of-a-kind",
        (true, true, _, _) => if ptr == 8 {"royal-flush"} else {"straight-flush"},
        (_,_,4,_) => "four-of-a-kind",
        (_,_,3,2) => "full-house",
        (true,_,_,_) => "flush",
        (_,true,_,_) => "straight",
        (_,_,3,_) => "three-of-a-kind",
        (_,_,2,2) => "two-pair",
        (_,_,2,_) => "one-pair",
        _ => "high-card"
     }
}
 
fn get_card_value(card: char) -> (i8,i8) {
    // transform glyph to face + suit, zero-indexed
    let base = card as u32 - 0x1F0A1;
    let mut suit = (base / 16) as i8;
    let mut face = (base % 16) as i8;
    if face > 11 && face < 14 { face-=1; } // Unicode has a Knight that we do not want
    if face == 14 { suit = -1; } //jokers do not have a suit
    (face, suit)
}
 