Poker hand analyser

From Rosetta Code
Task
Poker hand analyser
You are encouraged to solve this task according to the task description, using any language you may know.
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
  1. use the playing card characters introduced with Unicode 6.0 (U+1F0A1 - U+1F0DE).
  2. 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



11l

Translation of: D
F analyzeHandHelper(faceCount, suitCount)
   V
      p1 = 0B
      p2 = 0B
      t  = 0B
      f  = 0B
      fl = 0B
      st = 0B

   L(fc) faceCount
      S fc
         2 {I p1 {p2 = 1B} E p1 = 1B}
         3 {t = 1B}
         4 {f = 1B}

   L(sc) suitCount
      I sc == 5
         fl = 1B

   I !p1 & !p2 & !t & !f
      V s = 0
      L(fc) faceCount
         I fc != 0
            s++
         E
            s = 0
         I s == 5
            L.break

      st = (s == 5) | (s == 4 & faceCount[0] != 0 & faceCount[1] == 0)

   I st & fl   {R ‘straight-flush’}
   E I f       {R ‘four-of-a-kind’}
   E I p1 & t  {R ‘full-house’}
   E I fl      {R ‘flush’}
   E I st      {R ‘straight’}
   E I t       {R ‘three-of-a-kind’}
   E I p1 & p2 {R ‘two-pair’}
   E I p1      {R ‘one-pair’}
   E           {R ‘high-card’}

F analyzeHand(inHand)
   V handLen = 5
   V face = ‘A23456789TJQK’
   V suit = ‘SHCD’
   V errorMessage = ‘invalid hand.’

   V hand = sorted(inHand.split(‘ ’))
   I hand.len != handLen
      R errorMessage
   I Set(hand).len != handLen
      R errorMessage‘ Duplicated cards.’

   V faceCount = [0] * face.len
   V suitCount = [0] * suit.len
   L(card) hand
      I card.len != 2
         R errorMessage
      V? n = face.find(card[0])
      V? l = suit.find(card[1])
      I n == N | l == N
         R errorMessage
      faceCount[n]++
      suitCount[l]++

   R analyzeHandHelper(faceCount, suitCount)

L(hand) [‘2H 2D 2S KS QD’,
         ‘2H 5H 7D 8S 9D’,
         ‘AH 2D 3S 4S 5S’,
         ‘2H 3H 2D 3S 3D’,
         ‘2H 7H 2D 3S 3D’,
         ‘2H 7H 7D 7S 7C’,
         ‘TH JH QH KH AH’,
         ‘4H 4C KC 5D TC’,
         ‘QC TC 7C 6C 4C’]
   print(hand‘: ’analyzeHand(hand))
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

AutoHotkey

PokerHand(hand){
	StringUpper, hand, hand
	Sort, hand, FCardSort D%A_Space%
	cardSeq	:= RegExReplace(hand, "[^2-9TJQKA]")
	Straight:= InStr("23456789TJQKA", cardSeq) || (cardSeq = "2345A") ? true : false	
	hand 	:= cardSeq = "2345A" ? RegExReplace(hand, "(.*)\h(A.)", "$2 $1") : hand
	Royal 	:= InStr(hand, "A") ? "Royal": "Straight"
	return  (hand ~= "[2-9TJQKA](.)\h.\1\h.\1\h.\1\h.\1") && (Straight) 			? hand "`t" Royal " Flush"
			: (hand ~= "([2-9TJQKA]).*?\1.*?\1.*?\1") 				? hand "`tFour of a Kind"
			: (hand ~= "^([2-9TJQKA]).\h\1.\h(?!\1)([2-9TJQKA]).\h\2.\h\2.$") 	? hand "`tFull House"	; xxyyy
			: (hand ~= "^([2-9TJQKA]).\h\1.\h\1.\h(?!\1)([2-9TJQKA]).\h\2.$") 	? hand "`tFull House"	; xxxyy
			: (hand ~= "[2-9TJQKA](.)\h.\1\h.\1\h.\1\h.\1") 			? hand "`tFlush" 
			: (Straight)								? hand "`tStraight"
			: (hand ~= "([2-9TJQKA]).*?\1.*?\1")					? hand "`tThree of a Kind"
			: (hand ~= "([2-9TJQKA]).\h\1.*?([2-9TJQKA]).\h\2")			? hand "`tTwo Pair"
			: (hand ~= "([2-9TJQKA]).\h\1")						? hand "`tOne Pair"
			: 									  hand "`tHigh Card"
}
CardSort(a, b){
	a := SubStr(a, 1, 1), b := SubStr(b, 1, 1)
	a := (a = "T") ? 10 : (a = "J") ? 11 : (a = "Q") ? 12 : (a = "K") ? 13 : a
	b := (b = "T") ? 10 : (b = "J") ? 11 : (b = "Q") ? 12 : (b = "K") ? 13 : b
	return a > b ? 1 : a < b ? -1 : 0
}
Examples:
hands =
(join`r`n
2♥ 2♦ 2♣ k♣ q♦
2♥ 5♥ 7♦ 8♣ 9♠
a♥ 2♦ 3♣ 4♣ 5♦
2♥ 3♥ 2♦ 3♣ 3♦
2♥ 3♥ 2♦ 2♣ 3♦
2♥ 7♥ 2♦ 3♣ 3♦
2♥ 7♥ 7♦ 7♣ 7♠
T♥ j♥ q♥ a♥ K♥
T♥ j♥ q♥ 9♥ K♥
4♥ 4♠ k♠ 5♦ T♠
q♣ T♣ 7♣ 6♣ 4♣
)
loop, parse, hands, `n, `r
	res .= PokerHand(A_LoopField) "`n"
MsgBox, 262144, , % res
return
Outputs:
2♦ 2♣ 2♥ Q♦ K♣	Three of a Kind
2♥ 5♥ 7♦ 8♣ 9♠	High Card
A♥ 2♦ 3♣ 4♣ 5♦	Straight
2♦ 2♥ 3♣ 3♦ 3♥	Full House
2♣ 2♦ 2♥ 3♦ 3♥	Full House
2♦ 2♥ 3♣ 3♦ 7♥	Two Pair
2♥ 7♦ 7♣ 7♠ 7♥	Four of a Kind
T♥ J♥ Q♥ K♥ A♥	Royal Flush
9♥ T♥ J♥ Q♥ K♥	Straight Flush
4♠ 4♥ 5♦ T♠ K♠	One Pair
4♣ 6♣ 7♣ T♣ Q♣	Flush

C

Translation of: Kotlin
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

#define TRUE 1
#define FALSE 0

#define FACES "23456789tjqka"
#define SUITS "shdc"

typedef int bool;

typedef struct {
    int face;  /* FACES map to 0..12 respectively */
    char suit;
} card;

card cards[5];

int compare_card(const void *a, const void *b) {
    card c1 = *(card *)a;
    card c2 = *(card *)b;
    return c1.face - c2.face;
}

bool equals_card(card c1, card c2) {
    if (c1.face == c2.face && c1.suit == c2.suit) return TRUE;
    return FALSE;
}

bool are_distinct() {
    int i, j;
    for (i = 0; i < 4; ++i)
        for (j = i + 1; j < 5; ++j)
            if (equals_card(cards[i], cards[j])) return FALSE;
    return TRUE;
}

bool is_straight() {
    int i;
    qsort(cards, 5, sizeof(card), compare_card);
    if (cards[0].face + 4 == cards[4].face) return TRUE;
    if (cards[4].face == 12 && cards[0].face == 0 &&
        cards[3].face == 3) return TRUE;
    return FALSE;
}

bool is_flush() {
    int i;
    char suit = cards[0].suit;
    for (i = 1; i < 5; ++i) if (cards[i].suit != suit) return FALSE;
    return TRUE;
}

const char *analyze_hand(const char *hand) {
    int i, j, gs = 0;
    char suit, *cp;
    bool found, flush, straight;
    int groups[13];
    if (strlen(hand) != 14) return "invalid";
    for (i = 0; i < 14; i += 3) {
        cp = strchr(FACES, tolower(hand[i]));
        if (cp == NULL) return "invalid";
        j = i / 3;
        cards[j].face = cp - FACES;
        suit = tolower(hand[i + 1]);
        cp = strchr(SUITS, suit);
        if (cp == NULL) return "invalid";
        cards[j].suit = suit;
    }
    if (!are_distinct()) return "invalid";
    for (i = 0; i < 13; ++i) groups[i] = 0;
    for (i = 0; i < 5; ++i) groups[cards[i].face]++;
    for (i = 0; i < 13; ++i) if (groups[i] > 0) gs++;
    switch(gs) {
        case 2:
            found = FALSE;
            for (i = 0; i < 13; ++i) if (groups[i] == 4) {
                found = TRUE;
                break;
            }
            if (found) return "four-of-a-kind";
            return "full-house";
        case 3:
            found = FALSE;
            for (i = 0; i < 13; ++i) if (groups[i] == 3) {
                found = TRUE;
                break;
            }
            if (found) return "three-of-a-kind";
            return "two-pairs";
        case 4:
            return "one-pair";
        default:
            flush = is_flush();
            straight = is_straight();
            if (flush && straight)
                return "straight-flush";
            else if (flush)
                return "flush";
            else if (straight)
                return "straight";
            else
                return "high-card";
    }
}

int main(){
    int i;
    const char *type;
    const char *hands[10] = {
        "2h 2d 2c kc qd",
        "2h 5h 7d 8c 9s",
        "ah 2d 3c 4c 5d",
        "2h 3h 2d 3c 3d",
        "2h 7h 2d 3c 3d",
        "2h 7h 7d 7c 7s",
        "th jh qh kh ah",
        "4h 4s ks 5d ts",
        "qc tc 7c 6c 4c",
        "ah ah 7c 6c 4c"
    };
    for (i = 0; i < 10; ++i) {
        type = analyze_hand(hands[i]);
        printf("%s: %s\n", hands[i], type);
    }
    return 0;
}
Output:
2h 2d 2c kc qd: three-of-a-kind
2h 5h 7d 8c 9s: high-card
ah 2d 3c 4c 5d: straight
2h 3h 2d 3c 3d: full-house
2h 7h 2d 3c 3d: two-pairs
2h 7h 7d 7c 7s: four-of-a-kind
th jh qh kh ah: straight-flush
4h 4s ks 5d ts: one-pair
qc tc 7c 6c 4c: flush
ah ah 7c 6c 4c: invalid

C#

Works with: C sharp version 8
using System;
using System.Collections.Generic;
using static System.Linq.Enumerable;

public static class PokerHandAnalyzer
{
    private enum Hand {
        Invalid, High_Card, One_Pair, Two_Pair, Three_Of_A_Kind, Straight,
        Flush, Full_House, Four_Of_A_Kind, Straight_Flush, Five_Of_A_Kind
    }

    private const bool Y = true;
    private const char C = '♣', D = '♦', H = '♥', S = '♠';
    private const int rankMask = 0b11_1111_1111_1111;
    private const int suitMask = 0b1111 << 14;
    private static readonly string[] ranks = { "a", "2", "3", "4", "5", "6", "7", "8", "9", "10", "j", "q", "k" };
    private static readonly string[] suits = { C + "", D + "", H + "", S + "" };
    private static readonly Card[] deck = (from suit in Range(1, 4) from rank in Range(1, 13) select new Card(rank, suit)).ToArray();

    public static void Main() {
        string[] 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♣",
            "4♥ 4♣ 4♥ 4♠ 4♦", //duplicate card
            "joker 2♦ 2♠ k♠ q♦",
            "joker 5♥ 7♦ 8♠ 9♦",
            "joker 2♦ 3♠ 4♠ 5♠",
            "joker 3♥ 2♦ 3♠ 3♦",
            "joker 7♥ 2♦ 3♠ 3♦",
            "joker 7♥ 7♦ 7♠ 7♣",
            "joker j♥ q♥ k♥ A♥",
            "joker 4♣ k♣ 5♦ 10♠",
            "joker k♣ 7♣ 6♣ 4♣",
            "joker 2♦ joker 4♠ 5♠",
            "joker Q♦ joker A♠ 10♠",
            "joker Q♦ joker A♦ 10♦",
            "joker 2♦ 2♠ joker q♦"
        };
        foreach (var h in hands) {
            Console.WriteLine($"{h}: {Analyze(h).Name()}");
        }
    }

    static string Name(this Hand hand) => string.Join('-', hand.ToString().Split('_')).ToLower();

    static List<T> With<T>(this List<T> list, int index, T item) {
        list[index] = item;
        return list;
    }

    struct Card : IEquatable<Card>, IComparable<Card>
    {
        public static readonly Card Invalid = new Card(-1, -1);
        public static readonly Card Joker = new Card(0, 0);

        public Card(int rank, int suit) {
            (Rank, Suit, Code) = (rank, suit) switch {
                (_, -1) => (-1, -1, -1),
                (-1, _) => (-1, -1, -1),
                (0, _) => (0, 0, 0),
                (1, _) => (rank, suit, (1 << (13 + suit)) | ((1 << 13) | 1)),
                (_, _) => (rank, suit, (1 << (13 + suit)) | (1 << (rank - 1)))
            };
        }

        public static implicit operator Card((int rank, int suit) tuple) => new Card(tuple.rank, tuple.suit);
        public int Rank { get; }
        public int Suit { get; }
        public int Code { get; }

        public override string ToString() => Rank switch {
            -1 => "invalid",
            0 => "joker",
            _ => $"{ranks[Rank-1]}{suits[Suit-1]}"
        };
        
        public override int GetHashCode() => Rank << 16 | Suit;
        public bool Equals(Card other) => Rank == other.Rank && Suit == other.Suit;

        public int CompareTo(Card other) {
            int c = Rank.CompareTo(other.Rank);
            if (c != 0) return c;
            return Suit.CompareTo(other.Suit);
        }
    }

    static Hand Analyze(string hand) {
        var cards = ParseHand(hand);
        if (cards.Count != 5) return Hand.Invalid; //hand must consist of 5 cards
        cards.Sort();
        if (cards[0].Equals(Card.Invalid)) return Hand.Invalid;
        int jokers = cards.LastIndexOf(Card.Joker) + 1;
        if (jokers > 2) return Hand.Invalid; //more than 2 jokers
        if (cards.Skip(jokers).Distinct().Count() + jokers != 5) return Hand.Invalid; //duplicate cards

        if (jokers == 2) return (from c0 in deck from c1 in deck select Evaluate(cards.With(0, c0).With(1, c1))).Max();
        if (jokers == 1) return (from c0 in deck select Evaluate(cards.With(0, c0))).Max();
        return Evaluate(cards);
    }

    static List<Card> ParseHand(string hand) =>
        hand.Split(default(char[]), StringSplitOptions.RemoveEmptyEntries)
        .Select(card => ParseCard(card.ToLower())).ToList();

    static Card ParseCard(string card) => (card.Length, card) switch {
        (5, "joker") => Card.Joker,
        (3, _) when card[..2] == "10" => (10, ParseSuit(card[2])),
        (2, _) => (ParseRank(card[0]), ParseSuit(card[1])),
        (_, _) => Card.Invalid
    };

    static int ParseRank(char rank) => rank switch {
        'a' => 1,
        'j' => 11,
        'q' => 12,
        'k' => 13,
        _ when rank >= '2' && rank <= '9' => rank - '0',
        _ => -1
    };

    static int ParseSuit(char suit) => suit switch {
        C => 1, 'c' => 1,
        D => 2, 'd' => 2,
        H => 3, 'h' => 3,
        S => 4, 's' => 4,
        _ => -1
    };

    static Hand Evaluate(List<Card> hand) {
        var frequencies = hand.GroupBy(c => c.Rank).Select(g => g.Count()).OrderByDescending(c => c).ToArray();
        (int f0, int f1) = (frequencies[0], frequencies.Length > 1 ? frequencies[1] : 0);

        return (IsFlush(), IsStraight(), f0, f1) switch {
            (_, _, 5, _) => Hand.Five_Of_A_Kind,
            (Y, Y, _, _) => Hand.Straight_Flush,
            (_, _, 4, _) => Hand.Four_Of_A_Kind,
            (_, _, 3, 2) => Hand.Full_House,
            (Y, _, _, _) => Hand.Flush,
            (_, Y, _, _) => Hand.Straight,
            (_, _, 3, _) => Hand.Three_Of_A_Kind,
            (_, _, 2, 2) => Hand.Two_Pair,
            (_, _, 2, _) => Hand.One_Pair,
                        _ => Hand.High_Card
        };

        bool IsFlush() => hand.Aggregate(suitMask, (r, c) => r & c.Code) > 0;

        bool IsStraight() {
            int r = hand.Aggregate(0, (r, c) => r | c.Code) & rankMask;
            for (int i = 0; i < 4; i++) r &= r << 1;
            return r > 0;
        }
    }
    
}
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
4♥ 4♣ 4♥ 4♠ 4♦: invalid
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

C++

#include <iostream>
#include <sstream>
#include <algorithm>
#include <vector>

using namespace std;

class poker
{
public:
    poker() { face = "A23456789TJQK"; suit = "SHCD"; }
    string analyze( string h )
    {
	memset( faceCnt, 0, 13 ); memset( suitCnt, 0, 4 ); vector<string> hand;
	transform( h.begin(), h.end(), h.begin(), toupper ); istringstream i( h );
	copy( istream_iterator<string>( i ), istream_iterator<string>(), back_inserter<vector<string> >( hand ) );
	if( hand.size() != 5 ) return "invalid hand."; vector<string>::iterator it = hand.begin();
	sort( it, hand.end() ); if( hand.end() != adjacent_find( it, hand.end() ) ) return "invalid hand.";
	while( it != hand.end() )
	{
	    if( ( *it ).length() != 2 ) return "invalid hand.";
	    int n = face.find( ( *it ).at( 0 ) ), l = suit.find( ( *it ).at( 1 ) );
	    if( n < 0 || l < 0 ) return "invalid hand.";
	    faceCnt[n]++; suitCnt[l]++; it++;
	}
	cout << h << ": "; return analyzeHand();
    }
private:
    string analyzeHand()
    {
	bool p1 = false, p2 = false, t = false, f = false, fl = false, st = false;
	for( int x = 0; x < 13; x++ )
	    switch( faceCnt[x] )
	    {
		case 2: if( p1 ) p2 = true; else p1 = true; break;
		case 3: t = true; break;
		case 4: f = true;
	    }
	for( int x = 0; x < 4; x++ )if( suitCnt[x] == 5 ){ fl = true; break; }

	if( !p1 && !p2 && !t && !f )
        {
	    int s = 0;
	    for( int x = 0; x < 13; x++ )
	    { 
		if( faceCnt[x] ) s++; else s = 0;
		if( s == 5 ) break;
	    }
	    st = ( s == 5 ) || ( s == 4 && faceCnt[0] && !faceCnt[1] );
	}

	if( st && fl ) return "straight-flush";
	else if( f ) return "four-of-a-kind"; 
	else if( p1 && t ) return "full-house";
	else if( fl ) return "flush";
	else if( st ) return "straight";
	else if( t ) return "three-of-a-kind";
	else if( p1 && p2 ) return "two-pair";
	else if( p1 ) return "one-pair";
        return "high-card";
    }
    string face, suit;
    unsigned char faceCnt[13], suitCnt[4];
};

int main( int argc, char* argv[] )
{
    poker p; 
    cout << p.analyze( "2h 2d 2s ks qd" ) << endl; cout << p.analyze( "2h 5h 7d 8s 9d" ) << endl;
    cout << p.analyze( "ah 2d 3s 4s 5s" ) << endl; cout << p.analyze( "2h 3h 2d 3s 3d" ) << endl;
    cout << p.analyze( "2h 7h 2d 3s 3d" ) << endl; cout << p.analyze( "2h 7h 7d 7s 7c" ) << endl;
    cout << p.analyze( "th jh qh kh ah" ) << endl; cout << p.analyze( "4h 4c kc 5d tc" ) << endl;
    cout << p.analyze( "qc tc 7c 6c 4c" ) << endl << endl; return system( "pause" );
}
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

Clojure

(defn rank [card]
  (let [[fst _] card]
    (if (Character/isDigit fst)
      (Integer/valueOf (str fst))
      ({\T 10, \J 11, \Q 12, \K 13, \A 14} fst))))

(defn suit [card]
  (let [[_ snd] card]
    (str snd)))

(defn n-of-a-kind [hand n]
  (not (empty? (filter #(= true %) (map #(>= % n) (vals (frequencies (map rank hand))))))))

(defn ranks-with-ace [hand]
  (let [ranks (sort (map rank hand))]
    (if (some #(= 14 %) ranks) (cons 1 ranks) ranks)))

(defn pair? [hand]
  (n-of-a-kind hand 2))

(defn three-of-a-kind? [hand]
  (n-of-a-kind hand 3))

(defn four-of-a-kind? [hand]
  (n-of-a-kind hand 4))

(defn flush? [hand]
  (not (empty? (filter #(= true %) (map #(>= % 5) (vals (frequencies (map suit hand))))))))

(defn full-house? [hand]
  (true? (and
    (some #(= 2 %) (vals (frequencies (map rank hand))))
    (some #(= 3 %) (vals (frequencies (map rank hand)))))))

(defn two-pairs? [hand]
  (or
    (full-house? hand)
    (four-of-a-kind? hand)
    (= 2 (count (filter #(= true %) (map #(>= % 2) (vals (frequencies (map rank hand)))))))))

(defn straight? [hand]
  (let [hand-a (ranks-with-ace hand)
        fst (first hand-a)
        snd (second hand-a)]
    (or
      (= (take 5 hand-a) (range fst (+ fst 5)))
      (= (drop 1 hand-a) (range snd (+ snd 5))))))

(defn straight-flush? [hand]
  (and
    (straight? hand)
    (flush? hand)))

(defn invalid? [hand]
  (not= 5 (count (set hand))))

(defn check-hand [hand]
  (cond
    (invalid? hand) "invalid"
    (straight-flush? hand) "straight-flush"
    (four-of-a-kind? hand) "four-of-a-kind"
    (full-house? hand) "full-house"
    (flush? hand) "flush"
    (straight? hand) "straight"
    (three-of-a-kind? hand) "three-of-a-kind"
    (two-pairs? hand) "two-pair"
    (pair? hand) "one-pair"
    :else "high-card"))

; Test examples
(def hands [["2H" "2D" "2S" "KS" "QD"]
            ["2H" "5H" "7D" "8S" "9D"]
            ["AH" "2D" "3S" "4S" "5S"]
            ["2H" "3H" "2D" "3S" "3D"]
            ["2H" "7H" "2D" "3S" "3D"]
            ["2H" "7H" "7D" "7S" "7C"]
            ["TH" "JH" "QH" "KH" "AH"]
            ["4H" "4C" "KC" "5D" "TC"]
            ["QC" "TC" "7C" "6C" "4C"]])
(run! println (map #(str % " : " (check-hand %)) hands))
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

D

Basic Version

No bonus for this simple version.

Translation of: C++
import std.stdio, std.string, std.algorithm, std.range;

string analyzeHand(in string inHand) pure /*nothrow @safe*/ {
    enum handLen = 5;
    static immutable face = "A23456789TJQK", suit = "SHCD";
    static immutable errorMessage = "invalid hand.";

    /*immutable*/ const hand = inHand.toUpper.split.sort().release;
    if (hand.length != handLen)
        return errorMessage;
    if (hand.uniq.walkLength != handLen)
        return errorMessage ~ " Duplicated cards.";

    ubyte[face.length] faceCount;
    ubyte[suit.length] suitCount;
    foreach (immutable card; hand) {
        if (card.length != 2)
            return errorMessage;
        immutable n = face.countUntil(card[0]);
        immutable l = suit.countUntil(card[1]);
        if (n < 0 || l < 0)
            return errorMessage;
        faceCount[n]++;
        suitCount[l]++;
    }

    return analyzeHandHelper(faceCount, suitCount);
}

private string analyzeHandHelper(const ref ubyte[13] faceCount,
                                 const ref ubyte[4] suitCount)
pure nothrow @safe @nogc {
    bool p1, p2, t, f, fl, st;

    foreach (immutable fc; faceCount)
        switch (fc) {
            case 2: (p1 ? p2 : p1) = true; break;
            case 3: t = true; break;
            case 4: f = true; break;
            default: // Ignore.
        }

    foreach (immutable sc; suitCount)
        if (sc == 5) {
            fl = true;
            break;
        }

    if (!p1 && !p2 && !t && !f) {
        uint s = 0;
        foreach (immutable fc; faceCount) {
            if (fc)
                s++;
            else
                s = 0;
            if (s == 5)
                break;
        }

        st = (s == 5) || (s == 4 && faceCount[0] && !faceCount[1]);
    }

    if (st && fl)      return "straight-flush";
    else if (f)        return "four-of-a-kind";
    else if (p1 && t)  return "full-house";
    else if (fl)       return "flush";
    else if (st)       return "straight";
    else if (t)        return "three-of-a-kind";
    else if (p1 && p2) return "two-pair";
    else if (p1)       return "one-pair";
    else               return "high-card";
}

void main() {
    // S = Spades, H = Hearts, C = Clubs, D = Diamonds.
    foreach (immutable hand; ["2H 2D 2S KS QD",
                              "2H 5H 7D 8S 9D",
                              "AH 2D 3S 4S 5S",
                              "2H 3H 2D 3S 3D",
                              "2H 7H 2D 3S 3D",
                              "2H 7H 7D 7S 7C",
                              "TH JH QH KH AH",
                              "4H 4C KC 5D TC",
                              "QC TC 7C 6C 4C"])
        writeln(hand, ": ", hand.analyzeHand);
}
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

Elixir

Translation of: Ruby
Works with: Elixir version 1.2
defmodule Card do
  @faces   ~w(2 3 4 5 6 7 8 9 10 j q k a)
  @suits   ~w(♥ ♦ ♣ ♠)                          # ~w(h d c s)
  @ordinal @faces |> Enum.with_index |> Map.new
  
  defstruct ~w[face suit ordinal]a
  
  def new(str) do
    {face, suit} = String.split_at(str, -1)
    if face in @faces and suit in @suits do
      ordinal = @ordinal[face]
      %__MODULE__{face: face, suit: suit, ordinal: ordinal}
    else
      raise ArgumentError, "invalid card: #{str}"
    end
  end
  
  def deck do
    for face <- @faces, suit <- @suits, do: "#{face}#{suit}"
  end
end

defmodule Hand do
  @ranks ~w(high-card one-pair two-pair three-of-a-kind straight flush
            full-house four-of-a-kind straight-flush five-of-a-kind)a |>
         Enum.with_index |> Map.new
  @wheel_faces ~w(2 3 4 5 a)
  
  def new(str_of_cards) do
    cards = String.downcase(str_of_cards) |>
            String.split([" ", ","], trim: true) |>
            Enum.map(&Card.new &1)
    grouped = Enum.group_by(cards, &(&1.ordinal)) |> Map.values
    face_pattern = Enum.map(grouped, &(length &1)) |> Enum.sort
    {consecutive, wheel_faces} = consecutive?(cards)
    rank = categorize(cards, face_pattern, consecutive)
    rank_num = @ranks[rank]
    tiebreaker = if wheel_faces do
                   for ord <- 3..-1, do: {1,ord}
                 else
                   Enum.map(grouped, &{length(&1), hd(&1).ordinal}) |>
                   Enum.sort |> Enum.reverse
                 end
    {rank_num, tiebreaker, str_of_cards, rank}
  end
  
  defp one_suit?(cards) do
    Enum.map(cards, &(&1.suit)) |> Enum.uniq |> length == 1
  end
 
  defp consecutive?(cards) do
    sorted = Enum.sort_by(cards, &(&1.ordinal))
    if Enum.map(sorted, &(&1.face)) == @wheel_faces do
      {true, true}
    else
      flag = Enum.map(sorted, &(&1.ordinal)) |>
             Enum.chunk(2,1) |>
             Enum.all?(fn [a,b] -> a+1 == b end)
      {flag, false}
    end
  end
  
  defp categorize(cards, face_pattern, consecutive) do
    case {consecutive, one_suit?(cards)} do
      {true, true}  -> :"straight-flush"
      {true, false} -> :straight
      {false, true} -> :flush
      _ ->  case face_pattern do
              [1,1,1,1,1] -> :"high-card"
              [1,1,1,2]   -> :"one-pair"
              [1,2,2]     -> :"two-pair"
              [1,1,3]     -> :"three-of-a-kind"
              [2,3]       -> :"full-house"
              [1,4]       -> :"four-of-a-kind"
              [5]         -> :"five-of-a-kind"
            end
    end
  end
end

test_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♥ 6♥ 2♦ 3♣ 3♦
10♥ j♥ q♥ k♥ a♥
4♥ 4♠ k♠ 2♦ 10♠
4♥ 4♠ k♠ 3♦ 10♠
q♣ 10♣ 7♣ 6♣ 4♣
q♣ 10♣ 7♣ 6♣ 3♣
9♥ 10♥ q♥ k♥ j♣
2♥ 3♥ 4♥ 5♥ a♥
2♥ 2♥ 2♦ 3♣ 3♦
"""
hands = String.split(test_hands, "\n", trim: true) |> Enum.map(&Hand.new(&1))
IO.puts "High to low"
Enum.sort(hands) |> Enum.reverse |>
Enum.each(fn hand -> IO.puts "#{elem(hand,2)}: \t#{elem(hand,3)}" end)

# Extra Credit 2. Examples:
IO.puts "\nExtra Credit 2"
extra_hands = """
joker  2♦  2♠  k♠  q♦
joker  5♥  7♦  8♠  9♦
joker  2♦  3♠  4♠  5♠
joker  3♥  2♦  3♠  3♦
joker  7♥  2♦  3♠  3♦
joker  7♥  7♦  7♠  7♣
joker  j♥  q♥  k♥  A♥
joker  4♣  k♣  5♦ 10♠
joker  k♣  7♣  6♣  4♣
joker  2♦  joker  4♠  5♠
joker  Q♦  joker  A♠ 10♠
joker  Q♦  joker  A♦ 10♦
joker  2♦  2♠  joker  q♦
"""
deck = Card.deck
String.split(extra_hands, "\n", trim: true) |>
Enum.each(fn hand ->
  [a,b,c,d,e] = String.split(hand) |>
                Enum.map(fn c -> if c=="joker", do: deck, else: [c] end)
  cards_list = for v<-a, w<-b, x<-c, y<-d, z<-e, do: "#{v} #{w} #{x} #{y} #{z}"
  best = Enum.map(cards_list, &Hand.new &1) |> Enum.max
  IO.puts "#{hand}:\t#{elem(best,3)}"
end)
Output:
High to low
10♥ j♥ q♥ k♥ a♥: 	straight-flush
2♥ 3♥ 4♥ 5♥ a♥: 	straight-flush
2♥ 3♥ 2♦ 3♣ 3♦: 	full-house
2♥ 2♥ 2♦ 3♣ 3♦: 	full-house
q♣ 10♣ 7♣ 6♣ 4♣: 	flush
q♣ 10♣ 7♣ 6♣ 3♣: 	flush
9♥ 10♥ q♥ k♥ j♣: 	straight
a♥ 2♦ 3♣ 4♣ 5♦: 	straight
2♥ 2♦ 2♣ k♣ q♦: 	three-of-a-kind
2♥ 7♥ 2♦ 3♣ 3♦: 	two-pair
2♥ 6♥ 2♦ 3♣ 3♦: 	two-pair
4♥ 4♠ k♠ 3♦ 10♠: 	one-pair
4♥ 4♠ k♠ 2♦ 10♠: 	one-pair
2♥ 5♥ 7♦ 8♣ 9♠: 	high-card

Extra Credit 2
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

F#

type Card = int * int

type Cards = Card list

let joker = (69,69)

let rankInvalid = "invalid", 99

let allCards = {0..12} |> Seq.collect (fun x->({0..3} |> Seq.map (fun y->x,y)))

let allSame = function | y::ys -> List.forall ((=) y) ys | _-> false

let straightList (xs:int list) = xs |> List.sort |> List.mapi (fun i n->n - i) |> allSame

let cardList (s:string): Cards =
  s.Split() |> Seq.map (fun s->s.ToLower())
  |> Seq.map (fun s ->
    if s="joker" then joker
    else
      match (s |> List.ofSeq) with
      | '1'::'0'::xs -> (9, xs) | '!'::xs -> (-1, xs) | x::xs-> ("a23456789!jqk".IndexOf(x), xs) | _  as xs-> (-1, xs)
      |> function | -1, _  -> (-1, '!') | x, y::[] -> (x, y) | _  -> (-1, '!')
      |> function 
      | x, 'h' | x, '♥' -> (x, 0) | x, 'd' | x, '♦' -> (x, 1) | x, 'c' | x, '♣' -> (x, 2)
      | x, 's' | x, '♠' -> (x, 3) | _ -> (-1, -1)
    )
  |> Seq.filter (fst >> ((<>) -1)) |> List.ofSeq


let rank (cards: Cards) =
  if cards.Length<>5 then rankInvalid
  else 
    let cts = cards |> Seq.groupBy fst |> Seq.map (snd >> Seq.length) |> List.ofSeq |> List.sort |> List.rev
    if cts.[0]=5 then ("five-of-a-kind", 1)
    else
      let flush = cards |> List.map snd |> allSame
      let straight = 
        let (ACE, ALT_ACE) = 0, 13
        let faces = cards |> List.map fst |> List.sort
        (straightList faces) || (if faces.Head<>ACE then false else (straightList (ALT_ACE::(faces.Tail))))
      if straight && flush then ("straight-flush", 2)
      else
        let cts = cards |> Seq.groupBy fst |> Seq.map (snd >> Seq.length) |> List.ofSeq |> List.sort |> List.rev
        if cts.[0]=4 then ("four-of-a-kind", 3)
        elif cts.[0]=3 && cts.[1]=2 then ("full-house", 4)
        elif flush then ("flush", 5)
        elif straight then ("straight", 6)
        elif cts.[0]=3 then ("three-of-a-kind", 7)
        elif cts.[0]=2 && cts.[1]=2 then ("two-pair", 8)
        elif cts.[0]=2 then ("one-pair", 9)
        else ("high-card", 10)

let pickBest (xs: seq<Cards>) =
  let cmp a b = (<) (snd a) (snd b)
  let pick currentBest x = if (cmp (snd x) (snd currentBest)) then x else currentBest
  xs |> Seq.map (fun x->x, (rank x)) |> Seq.fold pick ([], rankInvalid)

let calcHandRank handStr =
  let cards = handStr |> cardList
  if cards.Length<>5 
    then (cards, rankInvalid) 
    else
      cards |> List.partition ((=) joker) |> fun (x,y) -> x.Length, y
      |> function
      | (0,xs) when (xs |> Seq.distinct |> Seq.length)=5 -> xs, (rank xs)
      | (1,xs) -> allCards |> Seq.map (fun x->x::xs) |> pickBest
      | (2,xs) -> allCards |> Seq.collect (fun x->allCards |> Seq.map (fun y->y::x::xs)) |> pickBest
      | _ -> cards, rankInvalid


let showHandRank handStr =
  // handStr |> calcHandRank |> fun (cards, (rankName,_)) -> printfn "%s: %A %s" handStr cards rankName
  handStr |> calcHandRank |> (snd >> fst) |> printfn "%s: %s" handStr

[
"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♣"
"joker  2♦  2♠  k♠  q♦"
"joker  5♥  7♦  8♠  9♦"
"joker  2♦  3♠  4♠  5♠"
"joker  3♥  2♦  3♠  3♦"
"joker  7♥  2♦  3♠  3♦"
"joker  7♥  7♦  7♠  7♣"
"joker  j♥  q♥  k♥  A♥"
"joker  4♣  k♣  5♦ 10♠"
"joker  k♣  7♣  6♣  4♣"
"joker  2♦  joker  4♠  5♠"
"joker  Q♦  joker  A♠ 10♠"
"joker  Q♦  joker  A♦ 10♦"
"joker  2♦  2♠  joker  q♦"
] 
|> List.iter showHandRank
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
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

Factor

Factor comes with a poker hand evaluator.

USING: formatting kernel poker sequences ;
{
    "2H 2D 2C KC QD"
    "2H 5H 7D 8C 9S"
    "AH 2D 3C 4C 5D"
    "2H 3H 2D 3C 3D"
    "2H 7H 2D 3C 3D"
    "2H 7H 7D 7C 7S"
    "TH JH QH KH AH"
    "4H 4S KS 5D TS"
    "QC TC 7C 6C 4C"
} [ dup string>hand-name "%s: %s\n" printf ] each
Output:
2H 2D 2C KC QD: Three of a Kind
2H 5H 7D 8C 9S: High Card
AH 2D 3C 4C 5D: Straight
2H 3H 2D 3C 3D: Full House
2H 7H 2D 3C 3D: Two Pair
2H 7H 7D 7C 7S: Four of a Kind
TH JH QH KH AH: Straight Flush
4H 4S KS 5D TS: One Pair
QC TC 7C 6C 4C: Flush

FreeBASIC

Translation of: C
Const As String FACES = "23456789tjqka"
Const As String SUITS = "shdc"

Type card
    face As Integer  ' FACES map to 0..12 respectively
    suit As String
End Type

Dim Shared As card cards(4)

Sub insertionSort(arr() As card, Byval n As Integer)
    Dim As Integer i, key, j
    For i = 1 To n-1
        key = arr(i).face
        j = i-1
        While j >= 0 And arr(j).face > key
            arr(j+1) = arr(j)
            j = j-1
        Wend
        arr(j+1).face = key
    Next i
End Sub

Function compareCard(Byval a As card, Byval b As card) As Integer
    Return a.face - b.face
End Function

Function equalsCard(Byval c1 As card, Byval c2 As card) As Integer
    If c1.face = c2.face And c1.suit = c2.suit Then Return True
    Return False
End Function

Function areDistinct() As Integer
    Dim As Integer i, j
    For i = 0 To 3
        For j = i + 1 To 4
            If equalsCard(cards(i), cards(j)) = True Then Return False
        Next j
    Next i
    Return True
End Function

Function isStraight() As Boolean
    insertionSort(cards(), 5)
    If cards(0).face + 4 = cards(4).face Then Return True
    If cards(4).face = 12 And cards(0).face = 0 And cards(3).face = 3 Then Return True
    Return False
End Function

Function isFlush() As Boolean
    Dim As String suit = cards(0).suit
    For i As Integer = 1 To 4
        If cards(i).suit <> suit Then Return False
    Next i
    Return True
End Function

Function analyzeHand(Byval hand As String) As String
    Dim As Integer i, j, cp, gs = 0
    Dim As String suit
    Dim As Integer found, flush, straight
    Dim As Integer groups(12)
    
    If Len(hand) <> 14 Then Return "invalid"
    For i = 0 To 13 Step 3
        cp = Instr(FACES, Lcase(Mid(hand, i + 1, 1)))
        If cp = 0 Then Return "invalid"
        j = i \ 3
        cards(j).face = cp - 1
        suit = Lcase(Mid(hand, i + 2, 1))
        cp = Instr(SUITS, suit)
        If cp = 0 Then Return "invalid"
        cards(j).suit = suit
    Next i
    If areDistinct() = False Then Return "invalid"
    For i = 0 To 12
        groups(i) = 0
    Next i
    For i = 0 To 4
        groups(cards(i).face) += 1
    Next i
    For i = 0 To 12
        If groups(i) > 0 Then gs += 1
    Next i
    Select Case gs
    Case 2
        found = False
        For i = 0 To 12
            If groups(i) = 4 Then
                found = True
                Exit For
            End If
        Next i
        If found = True Then Return "four-of-a-kind"
        Return "full-house"
    Case 3
        found = False
        For i = 0 To 12
            If groups(i) = 3 Then
                found = True
                Exit For
            End If
        Next i
        If found = True Then Return "three-of-a-kind"
        Return "two-pairs"
    Case 4
        Return "one-pair"
    Case Else
        flush = isFlush()
        straight = isStraight()
        If flush = True And straight = True Then
            Return "straight-flush"
        Elseif flush = True Then
            Return "flush"
        Elseif straight = True Then
            Return "straight"
        Else
            Return "high-card"
        End If
    End Select
End Function

Dim As String tipo
Dim As String hands(9) = { _
"2h 2d 2c kc qd", _
"2h 5h 7d 8c 9s", _
"ah 2d 3c 4c 5d", _
"2h 3h 2d 3c 3d", _
"2h 7h 2d 3c 3d", _
"2h 7h 7d 7c 7s", _
"th jh qh kh ah", _
"4h 4s ks 5d ts", _
"qc tc 7c 6c 4c", _
"ah ah 7c 6c 4c" }

For i As Integer = 0 To 9
    tipo = analyzeHand(hands(i))
    Print hands(i); ": "; tipo
Next i

Sleep
Output:
Same as C entry.

Go

Translation of: Kotlin

Basic Version

package main

import (
    "fmt"
    "sort"
    "strings"
)

type card struct {
    face byte
    suit byte
}

const faces = "23456789tjqka"
const suits = "shdc"

func isStraight(cards []card) bool {
    sorted := make([]card, 5)
    copy(sorted, cards)
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i].face < sorted[j].face
    })
    if sorted[0].face+4 == sorted[4].face {
        return true
    }
    if sorted[4].face == 14 && sorted[0].face == 2 && sorted[3].face == 5 {
        return true
    }
    return false
}

func isFlush(cards []card) bool {
    suit := cards[0].suit
    for i := 1; i < 5; i++ {
        if cards[i].suit != suit {
            return false
        }
    }
    return true
}

func analyzeHand(hand string) string {
    temp := strings.Fields(strings.ToLower(hand))
    splitSet := make(map[string]bool)
    var split []string
    for _, s := range temp {
        if !splitSet[s] {
            splitSet[s] = true
            split = append(split, s)
        }
    }
    if len(split) != 5 {
        return "invalid"
    }
    var cards []card

    for _, s := range split {
        if len(s) != 2 {
            return "invalid"
        }
        fIndex := strings.IndexByte(faces, s[0])
        if fIndex == -1 {
            return "invalid"
        }
        sIndex := strings.IndexByte(suits, s[1])
        if sIndex == -1 {
            return "invalid"
        }
        cards = append(cards, card{byte(fIndex + 2), s[1]})
    }

    groups := make(map[byte][]card)
    for _, c := range cards {
        groups[c.face] = append(groups[c.face], c)
    }

    switch len(groups) {
    case 2:
        for _, group := range groups {
            if len(group) == 4 {
                return "four-of-a-kind"
            }
        }
        return "full-house"
    case 3:
        for _, group := range groups {
            if len(group) == 3 {
                return "three-of-a-kind"
            }
        }
        return "two-pair"
    case 4:
        return "one-pair"
    default:
        flush := isFlush(cards)
        straight := isStraight(cards)
        switch {
        case flush && straight:
            return "straight-flush"
        case flush:
            return "flush"
        case straight:
            return "straight"
        default:
            return "high-card"
        }
    }
}

func main() {
    hands := [...]string{
        "2h 2d 2c kc qd",
        "2h 5h 7d 8c 9s",
        "ah 2d 3c 4c 5d",
        "2h 3h 2d 3c 3d",
        "2h 7h 2d 3c 3d",
        "2h 7h 7d 7c 7s",
        "th jh qh kh ah",
        "4h 4s ks 5d ts",
        "qc tc 7c 6c 4c",
        "ah ah 7c 6c 4c",
    }
    for _, hand := range hands {
        fmt.Printf("%s: %s\n", hand, analyzeHand(hand))
    }
}
Output:
2h 2d 2c kc qd: three-of-a-kind
2h 5h 7d 8c 9s: high-card
ah 2d 3c 4c 5d: straight
2h 3h 2d 3c 3d: full-house
2h 7h 2d 3c 3d: two-pair
2h 7h 7d 7c 7s: four-of-a-kind
th jh qh kh ah: straight-flush
4h 4s ks 5d ts: one-pair
qc tc 7c 6c 4c: flush
ah ah 7c 6c 4c: invalid

Extra Credit Version

package main

import (
    "fmt"
    "sort"
    "strings"
)

type card struct {
    face byte
    suit byte
}

func isStraight(cards []card, jokers int) bool {
    sorted := make([]card, 5)
    copy(sorted, cards)
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i].face < sorted[j].face
    })
    switch jokers {
    case 0:
        switch {
        case sorted[0].face+4 == sorted[4].face,
            sorted[4].face == 14 && sorted[3].face == 5:
            return true
        default:
            return false
        }
    case 1:
        switch {
        case sorted[0].face+3 == sorted[3].face,
            sorted[0].face+4 == sorted[3].face,
            sorted[3].face == 14 && sorted[2].face == 4,
            sorted[3].face == 14 && sorted[2].face == 5:
            return true
        default:
            return false
        }
    default:
        switch {
        case sorted[0].face+2 == sorted[2].face,
            sorted[0].face+3 == sorted[2].face,
            sorted[0].face+4 == sorted[2].face,
            sorted[2].face == 14 && sorted[1].face == 3,
            sorted[2].face == 14 && sorted[1].face == 4,
            sorted[2].face == 14 && sorted[1].face == 5:
            return true
        default:
            return false
        }
    }
}

func isFlush(cards []card) bool {
    sorted := make([]card, 5)
    copy(sorted, cards)
    sort.Slice(sorted, func(i, j int) bool {
        return sorted[i].face < sorted[j].face
    })
    suit := sorted[0].suit
    for i := 1; i < 5; i++ {
        if sorted[i].suit != suit && sorted[i].suit != 'j' {
            return false
        }
    }
    return true
}

func analyzeHand(hand string) string {
    temp := strings.Fields(strings.ToLower(hand))
    splitSet := make(map[string]bool)
    var split []string
    for _, s := range temp {
        if !splitSet[s] {
            splitSet[s] = true
            split = append(split, s)
        }
    }
    if len(split) != 5 {
        return "invalid"
    }
    var cards []card
    var jokers = 0

    for _, s := range split {
        if len(s) != 4 {
            return "invalid"
        }
        cp := []rune(s)[0]
        var cd card
        switch {
        case cp == 0x1f0a1:
            cd = card{14, 's'}
        case cp == 0x1f0b1:
            cd = card{14, 'h'}
        case cp == 0x1f0c1:
            cd = card{14, 'd'}
        case cp == 0x1f0d1:
            cd = card{14, 'c'}
        case cp == 0x1f0cf:
            jokers++
            cd = card{15, 'j'} // black joker
        case cp == 0x1f0df:
            jokers++
            cd = card{16, 'j'} // white joker
        case cp >= 0x1f0a2 && cp <= 0x1f0ab:
            cd = card{byte(cp - 0x1f0a0), 's'}
        case cp >= 0x1f0ad && cp <= 0x1f0ae:
            cd = card{byte(cp - 0x1f0a1), 's'}
        case cp >= 0x1f0b2 && cp <= 0x1f0bb:
            cd = card{byte(cp - 0x1f0b0), 'h'}
        case cp >= 0x1f0bd && cp <= 0x1f0be:
            cd = card{byte(cp - 0x1f0b1), 'h'}
        case cp >= 0x1f0c2 && cp <= 0x1f0cb:
            cd = card{byte(cp - 0x1f0c0), 'd'}
        case cp >= 0x1f0cd && cp <= 0x1f0ce:
            cd = card{byte(cp - 0x1f0c1), 'd'}
        case cp >= 0x1f0d2 && cp <= 0x1f0db:
            cd = card{byte(cp - 0x1f0d0), 'c'}
        case cp >= 0x1f0dd && cp <= 0x1f0de:
            cd = card{byte(cp - 0x1f0d1), 'c'}
        default:
            cd = card{0, 'j'} // invalid
        }
        if cd.face == 0 {
            return "invalid"
        }
        cards = append(cards, cd)
    }

    groups := make(map[byte][]card)
    for _, c := range cards {
        groups[c.face] = append(groups[c.face], c)
    }

    switch len(groups) {
    case 2:
        for _, group := range groups {
            if len(group) == 4 {
                switch jokers {
                case 0:
                    return "four-of-a-kind"
                default:
                    return "five-of-a-kind"
                }
            }
        }
        return "full-house"
    case 3:
        for _, group := range groups {
            if len(group) == 3 {
                switch jokers {
                case 0:
                    return "three-of-a-kind"
                case 1:
                    return "four-of-a-kind"
                default:
                    return "five-of-a-kind"
                }
            }
        }
        if jokers == 0 {
            return "two-pair"
        }
        return "full-house"
    case 4:
        switch jokers {
        case 0:
            return "one-pair"
        case 1:
            return "three-of-a-kind"
        default:
            return "four-of-a-kind"
        }
    default:
        flush := isFlush(cards)
        straight := isStraight(cards, jokers)
        switch {
        case flush && straight:
            return "straight-flush"
        case flush:
            return "flush"
        case straight:
            return "straight"
        default:
            if jokers == 0 {
                return "high-card"
            } else {
                return "one-pair"
            }
        }
    }
}

func main() {
    hands := [...]string{
        "🃏 🃂 🂢 🂮 🃍",
        "🃏 🂵 🃇 🂨 🃉",
        "🃏 🃂 🂣 🂤 🂥",
        "🃏 🂳 🃂 🂣 🃃",
        "🃏 🂷 🃂 🂣 🃃",
        "🃏 🂷 🃇 🂧 🃗",
        "🃏 🂻 🂽 🂾 🂱",
        "🃏 🃔 🃞 🃅 🂪",
        "🃏 🃞 🃗 🃖 🃔",
        "🃏 🃂 🃟 🂤 🂥",
        "🃏 🃍 🃟 🂡 🂪",
        "🃏 🃍 🃟 🃁 🃊",
        "🃏 🃂 🂢 🃟 🃍",
        "🃏 🃂 🂢 🃍 🃍",
        "🃂 🃞 🃍 🃁 🃊",
    }
    for _, hand := range hands {
        fmt.Printf("%s: %s\n", hand, analyzeHand(hand))
    }
}
Output:
🃏 🃂 🂢 🂮 🃍 : three-of-a-kind
🃏 🂵 🃇 🂨 🃉 : straight
🃏 🃂 🂣 🂤 🂥 : straight
🃏 🂳 🃂 🂣 🃃 : four-of-a-kind
🃏 🂷 🃂 🂣 🃃 : three-of-a-kind
🃏 🂷 🃇 🂧 🃗 : five-of-a-kind
🃏 🂻 🂽 🂾 🂱 : straight-flush
🃏 🃔 🃞 🃅 🂪 : one-pair
🃏 🃞 🃗 🃖 🃔 : flush
🃏 🃂 🃟 🂤 🂥 : straight
🃏 🃍 🃟 🂡 🂪 : straight
🃏 🃍 🃟 🃁 🃊 : straight-flush
🃏 🃂 🂢 🃟 🃍 : four-of-a-kind
🃏 🃂 🂢 🃍 🃍 : invalid
🃂 🃞 🃍 🃁 🃊 : high-card

Haskell

Basic Version

{-# LANGUAGE TupleSections #-}

import Data.Function (on)
import Data.List     (group, nub, any, sort, sortBy)
import Data.Maybe    (mapMaybe)
import Text.Read     (readMaybe)

data Suit = Club | Diamond | Spade | Heart deriving (Show, Eq)

data Rank = Ace | Two | Three | Four | Five | Six | Seven
          | Eight | Nine | Ten | Jack | Queen | King
          deriving (Show, Eq, Enum, Ord, Bounded)

data Card = Card { suit :: Suit, rank :: Rank } deriving (Show, Eq)

type Hand = [Card]

consumed = pure . (, "")

instance Read Suit where
  readsPrec d s = case s of "♥" -> consumed Heart
                            "♦" -> consumed Diamond
                            "♣" -> consumed Spade
                            "♠" -> consumed Club
                            "h" -> consumed Heart
                            _   -> []

instance Read Rank where
  readsPrec d s = case s of "a"  -> consumed Ace
                            "2"  -> consumed Two
                            "3"  -> consumed Three
                            "4"  -> consumed Four
                            "5"  -> consumed Five
                            "6"  -> consumed Six
                            "7"  -> consumed Seven
                            "8"  -> consumed Eight
                            "9"  -> consumed Nine
                            "10" -> consumed Ten
                            "j"  -> consumed Jack
                            "q"  -> consumed Queen
                            "k"  -> consumed King
                            _    -> []

instance Read Card where
  readsPrec d = fmap (, "") . mapMaybe card . lex
    where 
      card (r, s) = Card <$> (readMaybe s :: Maybe Suit)
                         <*> (readMaybe r :: Maybe Rank)

-- Special hand
acesHigh :: [Rank]
acesHigh = [Ace, Ten, Jack, Queen, King]

isSucc :: (Enum a, Eq a, Bounded a) => [a] -> Bool
isSucc []  = True
isSucc [x] = True
isSucc (x:y:zs) = (x /= maxBound && y == succ x) && isSucc (y:zs)

nameHand :: Hand -> String
nameHand [] = "Invalid Input"
nameHand cards | invalidHand          = "Invalid hand"
               | straight && flush    = "Straight flush"
               | ofKind 4             = "Four of a kind"
               | ofKind 3 && ofKind 2 = "Full house"
               | flush                = "Flush"
               | straight             = "Straight"
               | ofKind 3             = "Three of a kind"
               | uniqRanks == 3       = "Two pair"
               | uniqRanks == 4       = "One pair"
               | otherwise            = "High card"
 where
  sortedRank  = sort $ rank <$> cards
  rankCounts  = sortBy (compare `on` snd) $ (,) <$> head <*> length <$> group sortedRank
  uniqRanks   = length rankCounts
  ofKind n    = any ((==n) . snd) rankCounts
  straight    = isSucc sortedRank || sortedRank == acesHigh
  flush       = length (nub $ suit <$> cards) == 1
  invalidHand = length (nub cards) /= 5

testHands :: [(String, Hand)]
testHands = (,) <$> id <*> mapMaybe readMaybe . words <$>
  [ "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♣"
  , "q♣ 10♣ 7♣ 6♣ 7♣" -- duplicate cards
  , "Bad input" ]

main :: IO ()
main = mapM_ (putStrLn . (fst <> const ": " <> nameHand . snd)) testHands
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
q♣ 10♣ 7♣ 6♣ 7♣: Invalid hand
Bad input: Invalid Input

J

parseHand=: ' ' cut 7&u:      NB. hand must be well formed
Suits=: <"> 7 u: '♥♦♣♦'       NB. or Suits=: 'hdcs'
Faces=: <;._1 ' 2 3 4 5 6 7 8 9 10 j q k a'

suits=: {:&.>
faces=: }:&.>
flush=: 1 =&#&~. suits
straight=: 1 = (i.#Faces) +/@E.~ Faces /:~@i. faces
kinds=: #/.~ @:faces
five=: 5 e. kinds NB. jokers or other cheat
four=: 4 e. kinds
three=: 3 e. kinds
two=: 2 e. kinds
twoPair=: 2 = 2 +/ .= kinds
highcard=: 5 = 1 +/ .= kinds

IF=: 2 :'(,&(<m) ^: v)"1'
Or=: 2 :'u ^:(5 e. $) @: v'

Deck=: ,Faces,&.>/Suits
Joker=: <'joker'
joke=: [: ,/^:(#@$ - 2:) (({. ,"1 Deck ,"0 1 }.@}.)^:(5>[)~ i.&Joker)"1^:2@,:
punchLine=: {:@-.&a:@,@|:
rateHand=: [:;:inv [: (, [: punchLine -1 :(0 :0-.LF)@joke) parseHand 
 ('invalid' IF 1:) Or
 ('high-card' IF highcard) Or
 ('one-pair' IF two) Or
 ('two-pair' IF twoPair) Or
 ('three-of-a-kind' IF three) Or
 ('straight' IF straight) Or
 ('flush' IF flush) Or
 ('full-house' IF (two * three)) Or
 ('four-of-a-kind' IF four) Or
 ('straight-flush' IF (straight * flush)) Or
 ('five-of-a-kind' IF five)
)

Hands=: <@deb;._2 {{)n
 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
}}

Note that * acts as "logical and" on logical values (if you need to deal with boolean values in the original sense - which were not constrained to logical values - you should use *. instead of * to achieve boolean multiplication, but that's not needed here).

Output for rateHand@> Hands:

2♥ 2♦ 2♣ k♣ q♦ three-of-a-kind
2♥ 5♥ 7♦ 8♣ 9♠ high-card      
a♥ 2♦ 3♣ 4♣ 5♦ high-card      
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         

Output for 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

Java

Works with: Java version 7

This code does not qualify for extra credit. Although it supports wildcards, it does not allow for duplicates.

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;

public class PokerHandAnalyzer {

    final static String faces = "AKQJT98765432";
    final static String suits = "HDSC";
    final static String[] deck = buildDeck();

    public static void main(String[] args) {
        System.out.println("Regular hands:\n");
        for (String input : new String[]{"2H 2D 2S KS QD",
            "2H 5H 7D 8S 9D",
            "AH 2D 3S 4S 5S",
            "2H 3H 2D 3S 3D",
            "2H 7H 2D 3S 3D",
            "2H 7H 7D 7S 7C",
            "TH JH QH KH AH",
            "4H 4C KC 5D TC",
            "QC TC 7C 6C 4C",
            "QC TC 7C 7C TD"}) {
            System.out.println(analyzeHand(input.split(" ")));
        }

        System.out.println("\nHands with wildcards:\n");
        for (String input : new String[]{"2H 2D 2S KS WW",
            "2H 5H 7D 8S WW",
            "AH 2D 3S 4S WW",
            "2H 3H 2D 3S WW",
            "2H 7H 2D 3S WW",
            "2H 7H 7D WW WW",
            "TH JH QH WW WW",
            "4H 4C KC WW WW",
            "QC TC 7C WW WW",
            "QC TC 7H WW WW"}) {
            System.out.println(analyzeHandWithWildcards(input.split(" ")));
        }
    }

    private static Score analyzeHand(final String[] hand) {
        if (hand.length != 5)
            return new Score("invalid hand: wrong number of cards", -1, hand);

        if (new HashSet<>(Arrays.asList(hand)).size() != hand.length)
            return new Score("invalid hand: duplicates", -1, hand);

        int[] faceCount = new int[faces.length()];
        long straight = 0, flush = 0;
        for (String card : hand) {

            int face = faces.indexOf(card.charAt(0));
            if (face == -1)
                return new Score("invalid hand: non existing face", -1, hand);
            straight |= (1 << face);

            faceCount[face]++;

            if (suits.indexOf(card.charAt(1)) == -1)
                return new Score("invalid hand: non-existing suit", -1, hand);
            flush |= (1 << card.charAt(1));
        }

        // shift the bit pattern to the right as far as possible
        while (straight % 2 == 0)
            straight >>= 1;

        // straight is 00011111; A-2-3-4-5 is 1111000000001
        boolean hasStraight = straight == 0b11111 || straight == 0b1111000000001;

        // unsets right-most 1-bit, which may be the only one set
        boolean hasFlush = (flush & (flush - 1)) == 0;

        if (hasStraight && hasFlush)
            return new Score("straight-flush", 9, hand);

        int total = 0;
        for (int count : faceCount) {
            if (count == 4)
                return new Score("four-of-a-kind", 8, hand);
            if (count == 3)
                total += 3;
            else if (count == 2)
                total += 2;
        }

        if (total == 5)
            return new Score("full-house", 7, hand);

        if (hasFlush)
            return new Score("flush", 6, hand);

        if (hasStraight)
            return new Score("straight", 5, hand);

        if (total == 3)
            return new Score("three-of-a-kind", 4, hand);

        if (total == 4)
            return new Score("two-pair", 3, hand);

        if (total == 2)
            return new Score("one-pair", 2, hand);

        return new Score("high-card", 1, hand);
    }

    private static WildScore analyzeHandWithWildcards(String[] hand) {
        if (Collections.frequency(Arrays.asList(hand), "WW") > 2)
            throw new IllegalArgumentException("too many wildcards");

        return new WildScore(analyzeHandWithWildcardsR(hand, null), hand.clone());
    }

    private static Score analyzeHandWithWildcardsR(String[] hand,
            Score best) {

        for (int i = 0; i < hand.length; i++) {
            if (hand[i].equals("WW")) {
                for (String card : deck) {
                    if (!Arrays.asList(hand).contains(card)) {
                        hand[i] = card;
                        best = analyzeHandWithWildcardsR(hand, best);
                    }
                }
                hand[i] = "WW";
                break;
            }
        }
        Score result = analyzeHand(hand);
        if (best == null || result.weight > best.weight)
            best = result;
        return best;
    }

    private static String[] buildDeck() {
        String[] dck = new String[suits.length() * faces.length()];
        int i = 0;
        for (char s : suits.toCharArray()) {
            for (char f : faces.toCharArray()) {
                dck[i] = "" + f + s;
                i++;
            }
        }
        return dck;
    }

    private static class Score {
        final int weight;
        final String name;
        final String[] hand;

        Score(String n, int w, String[] h) {
            weight = w;
            name = n;
            hand = h != null ? h.clone() : h;
        }

        @Override
        public String toString() {
            return Arrays.toString(hand) + " " + name;
        }
    }

    private static class WildScore {
        final String[] wild;
        final Score score;

        WildScore(Score s, String[] w) {
            score = s;
            wild = w;
        }

        @Override
        public String toString() {
            return String.format("%s%n%s%n", Arrays.toString(wild),
                    score.toString());
        }
    }
}
Output:
Regular hands:

[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
[QC, TC, 7C, 7C, TD] invalid hand: duplicates

Hands with wildcards:

[2H, 2D, 2S, KS, WW]
[2H, 2D, 2S, KS, 2C] four-of-a-kind

[2H, 5H, 7D, 8S, WW]
[2H, 5H, 7D, 8S, 8H] one-pair

[AH, 2D, 3S, 4S, WW]
[AH, 2D, 3S, 4S, 5H] straight

[2H, 3H, 2D, 3S, WW]
[2H, 3H, 2D, 3S, 3D] full-house

[2H, 7H, 2D, 3S, WW]
[2H, 7H, 2D, 3S, 2S] three-of-a-kind

[2H, 7H, 7D, WW, WW]
[2H, 7H, 7D, 7S, 7C] four-of-a-kind

[TH, JH, QH, WW, WW]
[TH, JH, QH, AH, KH] straight-flush

[4H, 4C, KC, WW, WW]
[4H, 4C, KC, 4D, 4S] four-of-a-kind

[QC, TC, 7C, WW, WW]
[QC, TC, 7C, AC, KC] flush

[QC, TC, 7H, WW, WW]
[QC, TC, 7H, QH, QD] three-of-a-kind

JavaScript

Works with: JavaScript version ECMAScript 6
const FACES = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'j', 'q', 'k', 'a'];
const SUITS = ['♥', '♦', '♣', '♠'];

function analyzeHand(hand){
	let cards  = hand.split(' ').filter(x => x !== 'joker');
	let jokers = hand.split(' ').length - cards.length;
	
	let faces = cards.map( card => FACES.indexOf(card.slice(0,-1)) );
	let suits = cards.map( card => SUITS.indexOf(card.slice(-1)) );
	
	if( cards.some( (card, i, self) => i !== self.indexOf(card) ) || faces.some(face => face === -1) || suits.some(suit => suit === -1) ) 
		return 'invalid';
	
	let flush    = suits.every(suit => suit === suits[0]);
	let groups   = FACES.map( (face,i) => faces.filter(j => i === j).length).sort( (x, y) => y - x );
	let shifted  = faces.map(x => (x + 1) % 13);
	let distance = Math.min( Math.max(...faces) - Math.min(...faces), Math.max(...shifted) - Math.min(...shifted));
	let straight = groups[0] === 1 && distance < 5;
	groups[0] += jokers;
	
	if      (groups[0] === 5)                    return 'five-of-a-kind'
	else if (straight && flush)                  return 'straight-flush'
	else if (groups[0] === 4)                    return 'four-of-a-kind'
	else if (groups[0] === 3 && groups[1] === 2) return 'full-house'
	else if (flush)                              return 'flush'
	else if (straight)                           return 'straight'
	else if (groups[0] === 3)                    return 'three-of-a-kind'
	else if (groups[0] === 2 && groups[1] === 2) return 'two-pair'
	else if (groups[0] === 2)                    return 'one-pair'
	else                                         return 'high-card';
}

Demonstrating:

let testHands = [
	"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♣",
	"joker 4♣ k♣ 5♦ 10♠",
	"joker 2♦ 2♠ k♠ q♦",
	"joker 3♥ 2♦ 3♠ 3♦",
	"joker 7♥ 7♦ 7♠ 7♣",
	"joker 2♦ joker 4♠ 5♠",
	"joker 2♠ joker a♠ 10♠",
	"joker q♦ joker a♦ 10♦"
];
	
for(hand of testHands) console.log(hand + ": " + analyzeHand(hand));
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
joker 4♣ k♣ 5♦ 10♠: one-pair
joker 2♦ 2♠ k♠ q♦: three-of-a-kind
joker 3♥ 2♦ 3♠ 3♦: four-of-a-kind
joker 7♥ 7♦ 7♠ 7♣: five-of-a-kind
joker 2♦ joker 4♠ 5♠: straight
joker 2♠ joker a♠ 10♠: flush
joker q♦ joker a♦ 10♦: straight-flush

Julia

sorteddeck = [string(r) * s for s in "♣♦♥♠", r in "23456789TJQKA"]

cardlessthan(card1, card2) = indexin(x, sorteddeck)[1] < indexin(y, sorteddeck)[1]

decksort(d) = sort(d, lt=cardlessthan)

highestrank(d) = string(highestcard(d)[1])

function hasduplicate(d)
    s = sort(d)
    for i in 1:length(s)-1
        if s[i] == s[i+1]
            return true
        end
    end
    false
end

invalid(d) = !all(x -> x in sorteddeck, d) || hasduplicate(d)

function countranks(d)
    ranks = Dict()
    for c in d
        r = string(c[1])
        if !haskey(ranks, r)
            ranks[r] = 1
        else
            ranks[r] += 1
        end
    end
    ranks
end

function countsuits(d)
    suits = Dict()
    for c in d
        s = string(c[2])
        if !haskey(suits, s)
            suits[s] = 1
        else
            suits[s] += 1
        end
    end
    suits
end

const rankmodifiers = Dict("A" => 130, "K" => 120, "Q" => 110, "J" => 100, "T" => 90,
                    "9" => 80, "8" => 70, "7" => 60, "6" => 50, "5" => 40,
                    "4" => 30, "3" => 20, "2" => 10)

rank(card) = rankmodifiers[string(card[1])]

const suitmodifiers = Dict("♠" => 4, "♥" => 3, "♦" => 2, "♣" => 1)

suitrank(card) = suitmodifiers[string(card[2])]

function isstraight(ranksdict)
    v = collect(values(ranksdict))
    if maximum(v) != 1
        return false
    else
        s = sort(map(x->rankmodifiers[x], collect(keys(ranksdict))))
        if s == [10, 20, 30, 40, 130]  # aces low straight
            return true
        else
            for i in 1:length(s)-1
                if abs(s[i] - s[i+1]) > 10
                    return false
                end
            end
        end
    end
    true
end

highestsuit(suitsdict) = sort(collect(keys(suitsdict)), lt=(x,y)->suitsdict[x] < suitsdict[y])[end]

isflush(suitsdict) = length(collect(values(suitsdict))) == 1

isstraightflush(ranks, suits) = isstraight(ranks) && isflush(suits)

isfourofakind(ranksdict) = maximum(values(ranksdict)) == 4 ? true : false

isfullhouse(ranksdict) = sort(collect(values(ranksdict))) == [2, 3]

isthreeofakind(ranksdict) = maximum(values(ranksdict)) == 3 && !isfullhouse(ranksdict) ? true : false

istwopair(ranksdict) = sort(collect(values(ranksdict)))[end-1: end] == [2,2]

isonepair(ranksdict) = sort(collect(values(ranksdict)))[end-1: end] == [1,2]

ishighcard(ranks, suits) = maximum(values(ranks)) == 1 && !isflush(suits) && !isstraight(ranks)

function scorehand(d)
    suits = countsuits(d)
    ranks = countranks(d)
    if invalid(d)
        return "invalid"
    end
    if isstraightflush(ranks, suits) return "straight-flush"
    elseif isfourofakind(ranks)      return "four-of-a-kind"
    elseif isfullhouse(ranks)        return "full-house"
    elseif isflush(suits)            return "flush"
    elseif isstraight(ranks)         return "straight"
    elseif isthreeofakind(ranks)     return "three-of-a-kind"
    elseif istwopair(ranks)          return "two-pair"
    elseif isonepair(ranks)          return "one-pair"
    elseif ishighcard(ranks, suits)  return "high-card"
    end
end

const 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♠"],
   ["T♥", "J♥", "Q♥", "K♥", "A♥"], ["4♥", "4♠", "K♠", "5♦", "T♠"],
   ["Q♣", "T♣", "7♣", "6♣", "4♣"]]

for hand in hands
    println("Hand $hand is a ", scorehand(hand), " hand.")
end
Output:

Hand ["2♥", "2♦", "2♣", "K♣", "Q♦"] is a three-of-a-kind hand.
Hand ["2♥", "5♥", "7♦", "8♣", "9♠"] is a high-card hand.
Hand ["A♥", "2♦", "3♣", "4♣", "5♦"] is a straight hand.
Hand ["2♥", "3♥", "2♦", "3♣", "3♦"] is a full-house hand.
Hand ["2♥", "7♥", "2♦", "3♣", "3♦"] is a two-pair hand.
Hand ["2♥", "7♥", "7♦", "7♣", "7♠"] is a four-of-a-kind hand.
Hand ["T♥", "J♥", "Q♥", "K♥", "A♥"] is a straight-flush hand.
Hand ["4♥", "4♠", "K♠", "5♦", "T♠"] is a one-pair hand.
Hand ["Q♣", "T♣", "7♣", "6♣", "4♣"] is a flush hand.



Kotlin

Basic Version

// version 1.1.2

class Card(val face: Int, val suit: Char)

const val FACES = "23456789tjqka"
const val SUITS = "shdc"

fun isStraight(cards: List<Card>): Boolean {
    val sorted = cards.sortedBy { it.face }
    if (sorted[0].face + 4 == sorted[4].face) return true
    if (sorted[4].face == 14 && sorted[0].face == 2 && sorted[3].face == 5) return true 
    return false
}

fun isFlush(cards: List<Card>): Boolean {
    val suit = cards[0].suit
    if (cards.drop(1).all { it.suit == suit }) return true 
    return false
}

fun analyzeHand(hand: String): String {
    val h = hand.toLowerCase()
    val split = h.split(' ').filterNot { it == "" }.distinct()
    if (split.size != 5) return "invalid"
    val cards = mutableListOf<Card>()

    for (s in split) {
        if (s.length != 2) return "invalid"            
        val fIndex = FACES.indexOf(s[0])
        if (fIndex == -1) return "invalid"
        val sIndex = SUITS.indexOf(s[1])
        if (sIndex == -1) return "invalid"
        cards.add(Card(fIndex + 2, s[1]))
    } 
     
    val groups = cards.groupBy { it.face }
    when (groups.size) {
        2 -> {
            if (groups.any { it.value.size == 4 }) return "four-of-a-kind"
            return "full-house"
        }
        3 -> {
            if (groups.any { it.value.size == 3 }) return "three-of-a-kind"
            return "two-pair"
        }
        4 -> return "one-pair" 
        else -> {
            val flush = isFlush(cards)
            val straight = isStraight(cards)
            when {
                flush && straight -> return "straight-flush"
                flush             -> return "flush"
                straight          -> return "straight"
                else              -> return "high-card"
            }
        }
    } 
}    

fun main(args: Array<String>) {
    val hands = arrayOf(
        "2h 2d 2c kc qd",
        "2h 5h 7d 8c 9s",
        "ah 2d 3c 4c 5d",
        "2h 3h 2d 3c 3d",
        "2h 7h 2d 3c 3d",
        "2h 7h 7d 7c 7s",
        "th jh qh kh ah",
        "4h 4s ks 5d ts",
        "qc tc 7c 6c 4c",
        "ah ah 7c 6c 4c"
    )
    for (hand in hands) {
        println("$hand: ${analyzeHand(hand)}")
    }    
}
Output:
2h 2d 2c kc qd: three-of-a-kind
2h 5h 7d 8c 9s: high-card
ah 2d 3c 4c 5d: straight
2h 3h 2d 3c 3d: full-house
2h 7h 2d 3c 3d: two-pair
2h 7h 7d 7c 7s: four-of-a-kind
th jh qh kh ah: straight-flush
4h 4s ks 5d ts: one-pair
qc tc 7c 6c 4c: flush
ah ah 7c 6c 4c: invalid

Extra Credit Version

// version 1.1.2

class Card(val face: Int, val suit: Char)

fun isStraight(cards: List<Card>, jokers: Int): Boolean {
    val sorted = cards.sortedBy { it.face }
    when (jokers) {
        0    -> {
            if (sorted[0].face + 4 == sorted[4].face) return true
            if (sorted[4].face == 14 && sorted[3].face == 5) return true 
            return false
        }
        1    -> {
            if (sorted[0].face + 3 == sorted[3].face) return true
            if (sorted[0].face + 4 == sorted[3].face) return true
            if (sorted[3].face == 14 && sorted[2].face == 4) return true 
            if (sorted[3].face == 14 && sorted[2].face == 5) return true
            return false 
        }
        else -> {
            if (sorted[0].face + 2 == sorted[2].face) return true
            if (sorted[0].face + 3 == sorted[2].face) return true
            if (sorted[0].face + 4 == sorted[2].face) return true
            if (sorted[2].face == 14 && sorted[1].face == 3) return true 
            if (sorted[2].face == 14 && sorted[1].face == 4) return true
            if (sorted[2].face == 14 && sorted[1].face == 5) return true
            return false 
        }
    }  
}

fun isFlush(cards: List<Card>): Boolean {
    val sorted = cards.sortedBy { it.face }
    val suit = sorted[0].suit
    if (sorted.drop(1).all { it.suit == suit || it.suit == 'j' }) return true 
    return false
}

fun analyzeHand(hand: String): String {
    val split = hand.split(' ').filterNot { it == "" }.distinct()
    if (split.size != 5) return "invalid"
    val cards = mutableListOf<Card>()
    var jokers = 0
    for (s in split) {
        if (s.length != 2) return "invalid"
        val cp = s.codePointAt(0)
        val card = when (cp) {
             0x1f0a1 -> Card(14, 's')
             0x1f0b1 -> Card(14, 'h')
             0x1f0c1 -> Card(14, 'd')
             0x1f0d1 -> Card(14, 'c')
             0x1f0cf -> { jokers++; Card(15, 'j') }  // black joker
             0x1f0df -> { jokers++; Card(16, 'j') }  // white joker  
             in 0x1f0a2..0x1f0ab -> Card(cp - 0x1f0a0, 's')
             in 0x1f0ad..0x1f0ae -> Card(cp - 0x1f0a1, 's')
             in 0x1f0b2..0x1f0bb -> Card(cp - 0x1f0b0, 'h')
             in 0x1f0bd..0x1f0be -> Card(cp - 0x1f0b1, 'h')
             in 0x1f0c2..0x1f0cb -> Card(cp - 0x1f0c0, 'd')
             in 0x1f0cd..0x1f0ce -> Card(cp - 0x1f0c1, 'd')
             in 0x1f0d2..0x1f0db -> Card(cp - 0x1f0d0, 'c')
             in 0x1f0dd..0x1f0de -> Card(cp - 0x1f0d1, 'c')
             else                -> Card(0, 'j') // invalid 
        }
        if (card.face == 0) return "invalid"
        cards.add(card)
    } 
     
    val groups = cards.groupBy { it.face }
    when (groups.size) {
        2 -> {
            if (groups.any { it.value.size == 4 }) {
                return when (jokers) {
                    0    -> "four-of-a-kind"
                    else -> "five-of-a-kind"
                }
            }
            return "full-house"
        }
        3 -> {
            if (groups.any { it.value.size == 3 }) {
                return when (jokers) {
                    0    -> "three-of-a-kind"
                    1    -> "four-of-a-kind"
                    else -> "five-of-a-kind"
                }
            } 
            return if (jokers == 0) "two-pair" else "full-house"
        }
        4 -> return when (jokers) {
                    0    -> "one-pair"
                    1    -> "three-of-a-kind"
                    else -> "four-of-a-kind"
             }
        else -> {
            val flush = isFlush(cards) 
            val straight = isStraight(cards,jokers)
            when {
                flush && straight -> return "straight-flush"
                flush             -> return "flush"
                straight          -> return "straight"
                else              -> return if (jokers == 0) "high-card" else "one-pair"
            }
        }
    } 
}    

fun main(args: Array<String>) {
    val hands = arrayOf(
        "🃏 🃂 🂢 🂮 🃍",
        "🃏 🂵 🃇 🂨 🃉",
        "🃏 🃂 🂣 🂤 🂥",
        "🃏 🂳 🃂 🂣 🃃",
        "🃏 🂷 🃂 🂣 🃃",
        "🃏 🂷 🃇 🂧 🃗",
        "🃏 🂻 🂽 🂾 🂱",
        "🃏 🃔 🃞 🃅 🂪",
        "🃏 🃞 🃗 🃖 🃔",
        "🃏 🃂 🃟 🂤 🂥",
        "🃏 🃍 🃟 🂡 🂪",
        "🃏 🃍 🃟 🃁 🃊",
        "🃏 🃂 🂢 🃟 🃍",
        "🃏 🃂 🂢 🃍 🃍",
        "🃂 🃞 🃍 🃁 🃊"
    )
    for (hand in hands) {
        println("$hand : ${analyzeHand(hand)}")
    }    
}
Output:
🃏 🃂 🂢 🂮 🃍 : three-of-a-kind
🃏 🂵 🃇 🂨 🃉 : straight
🃏 🃂 🂣 🂤 🂥 : straight
🃏 🂳 🃂 🂣 🃃 : four-of-a-kind
🃏 🂷 🃂 🂣 🃃 : three-of-a-kind
🃏 🂷 🃇 🂧 🃗 : five-of-a-kind
🃏 🂻 🂽 🂾 🂱 : straight-flush
🃏 🃔 🃞 🃅 🂪 : one-pair
🃏 🃞 🃗 🃖 🃔 : flush
🃏 🃂 🃟 🂤 🂥 : straight
🃏 🃍 🃟 🂡 🂪 : straight
🃏 🃍 🃟 🃁 🃊 : straight-flush
🃏 🃂 🂢 🃟 🃍 : four-of-a-kind
🃏 🃂 🂢 🃍 🃍 : invalid
🃂 🃞 🃍 🃁 🃊 : high-card

Lua

-- Check whether t is a valid poker hand
function valid (t)
    if #t ~= 5 then return false end
    for k, v in pairs(t) do
        for key, card in pairs(t) do
            if  v.value == card.value and
                v.suit == card.suit and
                k ~= key
            then
                return false
            end
        end
    end
    return true
end

-- Return numerical value of a single card
function cardValue (card)
    local val = card:sub(1, -2)
    local n = tonumber(val)
    if n then return n end
    if val == "j" then return 11 end
    if val == "q" then return 12 end
    if val == "k" then return 13 end
    if val == "a" then return 1 end
    error("Invalid card value: " .. val)
end

-- Detect whether hand t is a straight
function straight (t)
    table.sort(t, function (a, b) return a.value < b.value end)
    local ace, thisValue, lastValue = false
    for i = 2, #t do
        thisValue, lastValue = t[i].value, t[i-1].value
        if lastValue == 1 then ace = i - 1 end
        if thisValue ~= lastValue + 1 then
            if ace then
                t[ace].value = 14
                return straight(t)
            else
                return false
            end
        end
    end
    return true
end

-- Detect whether hand t is a flush
function isFlush (t)
    local suit = t[1].suit
    for card = 2, #t do
        if t[card].suit ~= suit then return false end
    end
    return true
end

-- Return a table of the count of each card value in hand t
function countValues (t)
    local countTab, maxCount = {}, 0
    for k, v in pairs(t) do
        if countTab[v.value] then
            countTab[v.value] = countTab[v.value] + 1
        else
            countTab[v.value] = 1
        end
    end
    return countTab
end

-- Find the highest value in t
function highestCount (t)
    local maxCount = 0
    for k, v in pairs(t) do
        if v > maxCount then maxCount = v end
    end
    return maxCount
end

-- Detect full-house and two-pair using the value counts in t
function twoTypes (t)
    local threes, twos = 0, 0
    for k, v in pairs(t) do
        if v == 3 then threes = threes + 1 end
        if v == 2 then twos = twos + 1 end
    end
    return threes, twos
end

-- Return the rank of a poker hand represented as a string
function rank (cards)
    local hand = {}
    for card in cards:gmatch("%S+") do
        table.insert(hand, {value = cardValue(card), suit = card:sub(-1, -1)})
    end
    if not valid(hand) then return "invalid" end
    local st, fl = straight(hand), isFlush(hand)
    if st and fl then return "straight-flush" end
    local valCount = countValues(hand)
    local highCount = highestCount(valCount)
    if highCount == 4 then return "four-of-a-kind" end
    local n3, n2 = twoTypes(valCount)
    if n3 == 1 and n2 == 1 then return "full-house" end
    if fl then return "flush" end
    if st then return "straight" end
    if highCount == 3 then return "three-of-a-kind" end
    if n3 == 0 and n2 == 2 then return "two-pair" end
    if highCount == 2 then return "one-pair" end
    return "high-card"
end

-- Main procedure
local testCases = {
    "2h 2d 2c kc qd", -- three-of-a-kind
    "2h 5h 7d 8c 9s", -- high-card
    "ah 2d 3c 4c 5d", -- straight
    "2h 3h 2d 3c 3d", -- full-house
    "2h 7h 2d 3c 3d", -- two-pair
    "2h 7h 7d 7c 7s", -- four-of-a-kind 
    "10h jh qh kh ah",-- straight-flush
    "4h 4s ks 5d 10s",-- one-pair
    "qc 10c 7c 6c 4c" -- flush
}
for _, case in pairs(testCases) do print(case, ": " .. rank(case)) end
Output:
2h 2d 2c kc qd  : three-of-a-kind
2h 5h 7d 8c 9s  : high-card
ah 2d 3c 4c 5d  : straight
2h 3h 2d 3c 3d  : full-house
2h 7h 2d 3c 3d  : two-pair
2h 7h 7d 7c 7s  : four-of-a-kind
10h jh qh kh ah : straight-flush
4h 4s ks 5d 10s : one-pair
qc 10c 7c 6c 4c : flush

Nim

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
Output:
🂲  🃂  🃒  🃞  🃍  → three-of-a-kind
🂲  🂵  🃇  🃘  🂩  → high-card
🂱  🃂  🃓  🃔  🃅  → straight
🂲  🂳  🃂  🃓  🃃  → full-house
🂲  🂷  🃂  🃓  🃃  → two-pair
🂲  🂷  🃇  🃗  🂧  → four-of-a-kind
🂺  🂻  🂽  🂾  🂱  → straight-flush
🂴  🂤  🂮  🃅  🂪  → one-pair
🃝  🃚  🃗  🃖  🃔  → flush
🂴  🃔  🂴  🂤  🃄  → invalid

Perl

I dont like jokers. Instead I decided to give hands proper names. For example, "Kings full of Tens" rather than just "full-house".

use strict;
use warnings;
use utf8;
use feature 'say';
use open qw<:encoding(utf-8) :std>;
 
package Hand {
    sub describe {
        my $str = pop;
        my $hand = init($str);
        return "$str: INVALID" if !$hand;
        return analyze($hand);
    }
 
    sub init {
        (my $str = lc shift) =~ tr/234567891jqka♥♦♣♠//cd;
        return if $str !~ m/\A (?: [234567891jqka] [♥♦♣♠] ){5} \z/x;
        for (my ($i, $cnt) = (0, 0); $i < 10; $i += 2, $cnt = 0) {
            my $try = substr $str, $i, 2;
            ++$cnt while $str =~ m/$try/g;
            return if $cnt > 1;
        }
        my $suits = $str =~ tr/234567891jqka//dr;
        my $ranks = $str =~ tr/♥♦♣♠//dr;
        return {
            hand  => $str,
            suits => $suits,
            ranks => $ranks,
        };
    }
 
    sub analyze {
        my $hand = shift;
        my @ranks = split //, $hand->{ranks};
        my %cards;
        for (@ranks) {
            $_ = 10, next if $_ eq '1';
            $_ = 11, next if $_ eq 'j';
            $_ = 12, next if $_ eq 'q';
            $_ = 13, next if $_ eq 'k';
            $_ = 14, next if $_ eq 'a';
        } continue {
            ++$cards{ $_ };
        }
        my $kicker = 0;
        my (@pairs, $set, $quads, $straight, $flush);
 
        while (my ($card, $count) = each %cards) {
            if ($count == 1) {
                $kicker = $card if $kicker < $card;
            }
            elsif ($count == 2) {
                push @pairs, $card;
            }
            elsif ($count == 3) {
                $set = $card;
            }
            elsif ($count == 4) {
                $quads = $card;
            }
            else {
                die "Five of a kind? Cheater!\n";
            }
        }
        $flush    = 1 if $hand->{suits} =~ m/\A (.) \1 {4}/x;
        $straight = check_straight(@ranks);
        return get_high($kicker, \@pairs, $set, $quads, $straight, $flush,);
    }
 
    sub check_straight {
        my $sequence = join ' ', sort { $a <=> $b } @_;
        return 1       if index('2 3 4 5 6 7 8 9 10 11 12 13 14', $sequence) != -1;
        return 'wheel' if index('2 3 4 5 14 6 7 8 9 10 11 12 13', $sequence) ==  0;
        return undef;
    }
 
    sub get_high {
        my ($kicker, $pairs, $set, $quads, $straight, $flush) = @_;
        $kicker = to_s($kicker, 's');
        return 'straight-flush: Royal Flush!'
            if $straight && $flush && $kicker eq 'Ace' && $straight ne 'wheel';
        return "straight-flush: Steel Wheel!"
            if $straight && $flush && $straight eq 'wheel';
        return "straight-flush: $kicker high"
            if $straight && $flush;
        return 'four-of-a-kind: '. to_s($quads, 'p')
            if $quads;
        return 'full-house: '. to_s($set, 'p') .' full of '. to_s($pairs->[0], 'p')
            if $set && @$pairs;
        return "flush: $kicker high"
            if $flush;
        return 'straight: Wheel!'
            if $straight && $straight eq 'wheel';
        return "straight: $kicker high"
            if $straight;
        return 'three-of-a-kind: '. to_s($set, 'p')
            if $set;
        return 'two-pairs: '. to_s($pairs->[0], 'p') .' and '. to_s($pairs->[1], 'p')
            if @$pairs == 2;
        return 'one-pair: '. to_s($pairs->[0], 'p')
            if @$pairs == 1;
        return "high-card: $kicker";
    }
 
    my %to_str = (
         2 => 'Two',    3 => 'Three', 4 => 'Four',  5 => 'Five', 6 => 'Six',
         7 => 'Seven',  8 => 'Eight', 9 => 'Nine', 10 => 'Ten', 11 => 'Jack',
        12 => 'Queen', 13 => 'King', 14 => 'Ace',
    );
    my %to_str_diffs = (2 => 'Deuces', 6 => 'Sixes',);
 
    sub to_s {
        my ($num, $verb) = @_;
        # verb is 'singular' or 'plural' (or 's' or 'p')
        if ($verb =~ m/\A p/xi) {
            return $to_str_diffs{ $num } if $to_str_diffs{ $num };
            return $to_str{ $num } .'s';
        }
        return $to_str{ $num };
    }
}
 
my @cards = (
    '10♥ j♥  q♥ k♥ a♥',
    '2♥  3♥  4♥ 5♥ a♥',
    '2♥  2♣  2♦ 3♣ 2♠',
    '10♥ K♥  K♦ K♣ 10♦',
    'q♣  10♣ 7♣ 6♣ 3♣',
    '5♣  10♣ 7♣ 6♣ 4♣',
    '9♥  10♥ q♥ k♥ j♣',
    'a♥  a♣  3♣ 4♣ 5♦',
    '2♥  2♦  2♣ k♣ q♦',
    '6♥  7♥  6♦ j♣ j♦',
    '2♥  6♥  2♦ 3♣ 3♦',
    '7♥  7♠  k♠ 3♦ 10♠',
    '4♥  4♠  k♠ 2♦ 10♠',
    '2♥  5♥  j♦ 8♣ 9♠',
    '2♥  5♥  7♦ 8♣ 9♠',
    'a♥  a♥  3♣ 4♣ 5♦', # INVALID: duplicate aces
);
 
say Hand::describe($_) for @cards;
Output:
straight-flush: Royal Flush!
straight-flush: Steel Wheel!
four-of-a-kind: Deuces
full-house: Kings full of Tens
flush: Queen high
flush: Ten high
straight: King high
one-pair: Aces
three-of-a-kind: Deuces
two-pairs: Sixes and Jacks
two-pairs: Threes and Deuces
one-pair: Sevens
one-pair: Fours
high-card: Jack
high-card: Nine
a♥  a♥  3♣ 4♣ 5♦: INVALID

Phix

Woke up this morning with a neat idea for detecting straights, though jokers messed it up a bit.
Uses an ad-hoc ranking system/tie breaker, not recommended for use in tournaments! Displays hands best-first.
Note: I have left a copy of this in demo\HelloUTF8.exw to prove it works, but non-ascii on a Windows console is not Phix's forte.
For an(other) example of using the unicode card characters see Playing_cards#Phix

Library: Phix/online

You can run this online here.

with javascript_semantics
function poker(string hand)
    hand = substitute(hand,"10","t")
    sequence cards = split(hand)
    if length(cards)!=5 then return "invalid hand" end if
    sequence ranks = repeat(0,13),
             suits = repeat(0,4)
    integer jokers = 0
    for i=1 to length(cards) do
        sequence ci = utf8_to_utf32(cards[i])
        if ci="joker" then
            jokers += 1
            if jokers>2 then return "invalid hand" end if
        else
            if length(ci)!=2 then return "invalid hand" end if
            integer rank = find(lower(ci[1]),"23456789tjqka")
            integer suit = find(ci[2],utf8_to_utf32("♥♣♦♠"))
            if rank=0 or suit=0 then return "invalid hand" end if
            ranks[rank] += 1
            suits[suit] += 1
        end if
    end for
    integer straight = match({1,1,1,1,1},ranks) 
    if not straight then 
        straight = sort(deep_copy(ranks))[$]=1 and match({0,0,0,0,0,0,0,0},ranks)
    end if
    integer _flush = (max(suits)+jokers = 5)
    integer _pairs = max(ranks)+jokers
    integer pair = find(2,ranks)
    integer full_house = _pairs=3 and pair and (jokers=0 or find(2,ranks,pair+1))
    integer two_pair = find(2,ranks,pair+1)
    integer high_card = rfind(1,sq_ne(ranks,0))+1
    if jokers and _pairs=jokers+1 then
        straight = 1
        integer k = find(1,ranks), j = jokers
        for l=k to min(k+5-j,13) do
            if ranks[l]=0 then
                if j=0 then
                    straight = 0
                    exit
                end if
                j -= 1
            end if
        end for
        if straight and j then
            high_card = min(high_card+j,14)
        end if
    elsif straight and ranks[1]!=0 then 
        high_card = find(0,ranks)
    end if
    if _pairs=5             then return {10,"five of a kind", find(5-jokers,ranks)+1} end if
    if straight and _flush  then return {9,"straight flush", high_card} end if
    if _pairs=4             then return {8,"four of a kind", find(4-jokers,ranks)+1} end if
    if full_house           then return {7,"full house", find(3-jokers,ranks)+1} end if
    if _flush               then return {6,"flush", high_card} end if
    if straight             then return {5,"straight", high_card} end if
    if _pairs=3             then return {4,"three of a kind", find(3-jokers,ranks)+1} end if
    if pair and two_pair    then return {3,"two pair", two_pair+1} end if
    if pair                 then return {2,"one pair", pair+1} end if
    if jokers               then return {2,"one pair", high_card} end if
                                 return {1,"high card",high_card}
end function
 
sequence hands = {{0,"2♥ 2♦ 2♣ k♣ q♦"},
                  {0,"2♥ 5♥ 7♦ 8♣ 9♠"},
                  {0,"a♥ 2♦ 3♣ 4♣ 5♦"},
                  {0,"2♥ 3♥ 2♦ 3♣ 3♦"},
                  {0,"2♥ 7♥ 2♦ 3♣ 3♦"},
                  {0,"2♥ 7♥ 7♦ 7♣ 7♠"},
                  {0,"10♥ j♥ q♥ k♥ a♥"},
                  {0,"4♥ 4♠ k♠ 5♦ 10♠"},
                  {0,"q♣ 10♣ 7♣ 6♣ 4♣"},
                  {0,"joker  2♦  2♠  k♠  q♦"},
                  {0,"joker  5♥  7♦  8♠  9♦"},
                  {0,"joker  2♦  3♠  4♠  5♠"},
                  {0,"joker  3♥  2♦  3♠  3♦"},
                  {0,"joker  7♥  2♦  3♠  3♦"},
                  {0,"joker  7♥  7♦  7♠  7♣"},
                  {0,"joker  j♥  q♥  k♥  A♥"},
                  {0,"joker  4♣  k♣  5♦ 10♠"},
                  {0,"joker  k♣  7♣  6♣  4♣"},
                  {0,"joker  2♦  joker  4♠  5♠"},
                  {0,"joker  Q♦  joker  A♠ 10♠"},
                  {0,"joker  Q♦  joker  A♦ 10♦"},
                  {0,"joker  2♦  2♠  joker  q♦"}}
 
for i=1 to length(hands) do
    hands[i][1] = poker(hands[i][2])
end for
hands = reverse(sort(deep_copy(hands)))
integer rank
string hand,desc
for i=1 to length(hands) do
    {{rank,desc},hand} = hands[i]
    printf(1,"%d. %s %s\n",{11-rank,hand,desc})
end for
Output:
1. joker  7♥  7♦  7♠  7♣ five of a kind
2. joker  j♥  q♥  k♥  A♥ straight flush
2. joker  Q♦  joker  A♦ 10♦ straight flush
2. 10♥ j♥ q♥ k♥ a♥ straight flush
3. 2♥ 7♥ 7♦ 7♣ 7♠ four of a kind
3. joker  3♥  2♦  3♠  3♦ four of a kind
3. joker  2♦  2♠  joker  q♦ four of a kind
4. 2♥ 3♥ 2♦ 3♣ 3♦ full house
5. joker  k♣  7♣  6♣  4♣ flush
5. q♣ 10♣ 7♣ 6♣ 4♣ flush
6. joker  Q♦  joker  A♠ 10♠ straight
6. joker  5♥  7♦  8♠  9♦ straight
6. joker  2♦  joker  4♠  5♠ straight
6. joker  2♦  3♠  4♠  5♠ straight
6. a♥ 2♦ 3♣ 4♣ 5♦ straight
7. joker  7♥  2♦  3♠  3♦ three of a kind
7. joker  2♦  2♠  k♠  q♦ three of a kind
7. 2♥ 2♦ 2♣ k♣ q♦ three of a kind
8. 2♥ 7♥ 2♦ 3♣ 3♦ two pair
9. joker  4♣  k♣  5♦ 10♠ one pair
9. 4♥ 4♠ k♠ 5♦ 10♠ one pair
10. 2♥ 5♥ 7♦ 8♣ 9♠ high card

Picat

go =>
  Hands = [
            [[2,h], [7,h], [2,d], [3,c], [3,d]],  % two-pair
            [[2,h], [5,h], [7,d], [8,c], [9,s]],  % high-card
            [[a,h], [2,d], [3,c], [4,c], [5,d]],  % straight
            [[2,h], [3,h], [2,d], [3,c], [3,d]],  % full-house
            [[2,h], [7,h], [2,d], [3,c], [3,d]],  % two-pair
            [[2,h], [7,h], [7,d], [7,c], [7,s]],  % four-of-a-kind
            [[10,h],[j,h], [q,h], [k,h], [a,h]],  % straight-flush
            [[4,h], [4,s], [k,s], [5,d], [10,s]], % one-pair
            [[q,c], [10,c],[7,c], [6,c], [4,c]],  % flush
            [[q,c], [q,d], [q,s], [6,c], [4,c]],  % three-of-a-kind

            [[q,c], [10,c], [7,c], [7,c], [4,c]], % invalid (duplicates)
            [[q,c], [10,c], [7,c], [7,d]]         % invalid (too short)

          ],
  foreach(Hand in Hands) 
    print_hand(Hand),
    analyse(Hand, H),
    println(hand=H),
    nl
  end,
  nl.


% Print the hand
print_hand(Hand) =>
  println([ F.to_string() ++ S.to_string() : [F,S] in Hand]).

% Faces and suites
faces(Faces) => Faces = [a, 2, 3, 4, 5, 6, 7, 8, 9, 10, j, q, k].
faces_order1(Order1) => 
   Order1 = new_map([a=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10, j=11, q=12, k=13]).
faces_order2(Order2) => 
   Order2 = new_map([2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=10, j=11, q=12, k=13, a=14]).

suites(Suites) => Suites = [h,d,c,s].

% Order of the hand
hand_order(HandOrder) => 
  HandOrder = [straight_flush,
               four_of_a_kind,
               full_house,
               flush,
               straight,
               three_of_a_kind,
               two_pair,
               one_pair,
               high_card,
               invalid].


% for the straight
in_order(List) =>
  foreach(I in 2..List.length)
    List[I] = List[I-1] + 1
  end.


% Some validity tests first
analyse(Hand,Value) ?=> 
  (
    Hand.remove_dups.length == 5,
    faces(Faces),
    foreach([F,_] in Hand) 
      member(F,Faces)
    end,
    suites(Suites),
    foreach([_,S] in Hand) 
      member(S,Suites)
    end,
    analyse1(Hand,Value)
   ;
    Value = invalid
  ).

% Identify the specific hands

% straight flush
analyse1(Hand,Value) ?=>
  permutation(Hand,Hand1),
  Hand1 = [ [_F1,S], [_F2,S], [_F3,S], [_F4,S], [_F5,S] ],
  (
    faces_order1(Order1),
    in_order([Order1.get(F) : [F,S1] in  Hand1])
    ; 
    faces_order2(Order2),
    in_order([Order2.get(F) : [F,S1] in  Hand1]),
    println("Royal Straight Flush!")
  ),
  Value=straight_flush.

% four of a kind
analyse1(Hand,Value) ?=> 
  faces(Faces),
  member(A,Faces),
  [1 : [F,_S] in Hand, F = A].length == 4,
  Value=four_of_a_kind.

% full house
analyse1(Hand,Value) ?=> 
  permutation(Hand,Hand1),
  Hand1 = [ [F1,_S1], [F1,_S2], [F2,_S3], [F2,_S4], [F2,_S5] ],
  Value = full_house.

% flush
analyse1(Hand,Value) ?=> 
  permutation(Hand,Hand1),
  Hand1 = [ [_,S], [_,S], [_,S], [_,S], [_,S] ],
  Value = flush.

% straight
analyse1(Hand,Value) ?=> 
  permutation(Hand,Hand1),
  (
    faces_order1(Order1),
    in_order([Order1.get(F) : [F,_S] in  Hand1])
    ; 
    faces_order2(Order2),
    in_order([Order2.get(F) : [F,_S] in  Hand1])

  ),
  Value = straight.

% three of a kind
analyse1(Hand,Value) ?=>
  faces(Faces),
  member(A,Faces),
  [1 : [F,_S] in Hand, F = A].length == 3,
  Value = three_of_a_kind.

% two pair
analyse1(Hand,Value) ?=> 
  permutation(Hand,Hand1),
  Hand1 = [ [F1,_S1], [F1,_S2], [F2,_S3], [F2,_S4], [_F3,_S5] ],
  Value = two_pair.

% one pair
analyse1(Hand,Value) ?=> 
  faces(Faces),
  member(A,Faces),
  [1 : [F,_S] in Hand, F = A].length == 2,
  Value = one_pair.

% high card
analyse1(_Hand,Value) => 
  Value = high_card.


Output:
[2h,7h,2d,3c,3d]
hand = two_pair

[2h,5h,7d,8c,9s]
hand = high_card

[ah,2d,3c,4c,5d]
hand = straight

[2h,3h,2d,3c,3d]
hand = full_house

[2h,7h,2d,3c,3d]
hand = two_pair

[2h,7h,7d,7c,7s]
hand = four_of_a_kind

[10h,jh,qh,kh,ah]
Royal Straight Flush!
hand = straight_flush

[4h,4s,ks,5d,10s]
hand = one_pair

[qc,10c,7c,6c,4c]
hand = flush

[qc,qd,qs,6c,4c]
hand = three_of_a_kind

[qc,10c,7c,7c,4c]
hand = invalid

[qc,10c,7c,7d]
hand = invalid

For generating and checking random hands:

go2 =>
  _ = random2(),
  Hand = random_hand(5),
  print_hand(Hand),
  analyse(Hand, H),
  println(hand=H),
  nl.

% Get one element of list L
oneof(L) = L[random(1,L.len)].

% Get a random hand
random_hand(N) = Hand =>
   faces(Faces),
   suites(Suites),
   M = new_map(),
   while (M.keys().length < N) 
      M.put([oneof(Faces),oneof(Suites)],1)
   end,
   Hand = [C : C=_ in M].sort().
Output:
[3d,5h,10c,10h,qs]
hand = one_pair


PicoLisp

(rassoc) function in picolisp after 3.1.9.10.

(setq *Rank
   '(("2" . 0) ("3" . 1) ("4" . 2)
      ("5" . 3) ("6" . 4) ("7" . 5)
      ("8" . 6) ("9" . 7) ("t" . 8)
      ("j" . 9) ("q" . 10) ("k" . 11)
      ("a" . 12) ) )
(de poker (Str)
   (let (S NIL  R NIL  Seq NIL)
      (for (L (chop Str) (cdr L) (cdddr L))
         (accu 'R (cdr (assoc (car L) *Rank)) 1)
         (accu 'S (cadr L) 1) )
      (setq Seq
         (make
            (for (L (by car sort R) (cdr L) (cdr L))
               (link (- (caar L) (caadr L))) ) ) )
      (cond
         ((and
            (= 5 (cdar S))
            (or
               (= (-1 -1 -1 -1) Seq)
               (= (-1 -1 -1 -9) Seq) ) )
            'straight-flush )
         ((rassoc 4 R) 'four-of-a-kind)
         ((and (rassoc 2 R) (rassoc 3 R)) 'full-house)
         ((= 5 (cdar S)) 'flush)
         ((or
            (= (-1 -1 -1 -1) Seq)
            (= (-1 -1 -1 -9) Seq) )
            'straight )
         ((rassoc 3 R) 'three-of-a-kind)
         ((=
            2
            (cnt '((L) (= 2 (cdr L))) R) )
            'two-pair )
         ((rassoc 2 R) 'pair)
         (T 'high-card) ) ) )

Prolog

Works with: GNU Prolog version 1.4.4

Not very efficient version.

:- initialization(main).


faces([a,k,q,j,10,9,8,7,6,5,4,3,2]).

face(F) :- faces(Fs), member(F,Fs).
suit(S) :- member(S, ['♥','♦','♣','♠']).


best_hand(Cards,H) :-
    straight_flush(Cards,C) -> H = straight-flush(C)
  ; many_kind(Cards,F,4)    -> H = four-of-a-kind(F)
  ; full_house(Cards,F1,F2) -> H = full-house(F1,F2)
  ; flush(Cards,S)          -> H = flush(S)
  ; straight(Cards,F)       -> H = straight(F)
  ; many_kind(Cards,F,3)    -> H = three-of-a-kind(F)
  ; two_pair(Cards,F1,F2)   -> H = two-pair(F1,F2)
  ; many_kind(Cards,F,2)    -> H = one-pair(F)
  ; many_kind(Cards,F,1)    -> H = high-card(F)
  ;                            H = invalid
  .

straight_flush(Cards, c(F,S)) :- straight(Cards,F), flush(Cards,S).

full_house(Cards,F1,F2) :-
    many_kind(Cards,F1,3), many_kind(Cards,F2,2), F1 \= F2.

flush(Cards,S) :- maplist(has_suit(S), Cards).
has_suit(S, c(_,S)).

straight(Cards,F) :-
    select(c(F,_), Cards, Cs), pred_face(F,F1), straight(Cs,F1).
straight([],_).
pred_face(F,F1) :- F = 2 -> F1 = a ; faces(Fs), append(_, [F,F1|_], Fs).

two_pair(Cards,F1,F2) :-
    many_kind(Cards,F1,2), many_kind(Cards,F2,2), F1 \= F2.

many_kind(Cards,F,N) :-
    face(F), findall(_, member(c(F,_), Cards), Xs), length(Xs,N).


% utils/parser
parse_line(Cards)  --> " ", parse_line(Cards).
parse_line([C|Cs]) --> parse_card(C), parse_line(Cs).
parse_line([])     --> [].

parse_card(c(F,S)) --> parse_face(F), parse_suit(S).

parse_suit(S,In,Out) :- suit(S), atom_codes(S,Xs), append(Xs,Out,In).
parse_face(F,In,Out) :- face(F), face_codes(F,Xs), append(Xs,Out,In).

face_codes(F,Xs) :- number(F) -> number_codes(F,Xs) ; atom_codes(F,Xs).


% tests
test(" 2♥  2♦ 2♣ k♣  q♦").
test(" 2♥  5♥ 7♦ 8♣  9♠").
test(" a♥  2♦ 3♣ 4♣  5♦").
test(" 2♥  3♥ 2♦ 3♣  3♦").
test(" 2♥  7♥ 2♦ 3♣  3♦").
test(" 2♥  7♥ 7♦ 7♣  7♠").
test("10♥  j♥ q♥ k♥  a♥").
test(" 4♥  4♠ k♠ 5♦ 10♠").
test(" q♣ 10♣ 7♣ 6♣  4♣").

run_tests :-
    test(Line), phrase(parse_line(Cards), Line), best_hand(Cards,H)
  , write(Cards), write('\t'), write(H), nl
  .
main :- findall(_, run_tests, _), halt.
Output:
[c(2,♥),c(2,♦),c(2,♣),c(k,♣),c(q,♦)]	three-of-a-kind(2)
[c(2,♥),c(5,♥),c(7,♦),c(8,♣),c(9,♠)]	high-card(9)
[c(a,♥),c(2,♦),c(3,♣),c(4,♣),c(5,♦)]	straight(5)
[c(2,♥),c(3,♥),c(2,♦),c(3,♣),c(3,♦)]	full-house(3,2)
[c(2,♥),c(7,♥),c(2,♦),c(3,♣),c(3,♦)]	two-pair(3,2)
[c(2,♥),c(7,♥),c(7,♦),c(7,♣),c(7,♠)]	four-of-a-kind(7)
[c(10,♥),c(j,♥),c(q,♥),c(k,♥),c(a,♥)]	straight-flush(c(a,♥))
[c(4,♥),c(4,♠),c(k,♠),c(5,♦),c(10,♠)]	one-pair(4)
[c(q,♣),c(10,♣),c(7,♣),c(6,♣),c(4,♣)]	flush(♣)

Python

Goes a little further in also giving the ordered tie-breaker information from the wikipedia page.

from collections import namedtuple

class Card(namedtuple('Card', 'face, suit')):
    def __repr__(self):
        return ''.join(self)


suit = '♥ ♦ ♣ ♠'.split()
# 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'
# 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]))
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']

Racket

#lang racket
(require (only-in srfi/1 car+cdr))

;;; --------------------------------------------------------------------------------------------------
;;; The analyser is first... the rest of it is prettiness surrounding strings and parsing!
;;; --------------------------------------------------------------------------------------------------
;; (cons f _) and (cons _ s) appear too frequently in patterns to not factor out
(define-match-expander F._ (λ (stx) (syntax-case stx () [(_ f) #'(cons f _)])))
(define-match-expander _.S (λ (stx) (syntax-case stx () [(_ s) #'(cons _ s)])))

;; Matches are easier when the cards are lined up by face: and I always put the cards in my hand with
;; the highest card on the left (should I be telling this?)... anyway face<? is written to leave high
;; cards on the left. There is no need to sort by suit, flushes are all-or-nothing
(define (face-sort hand)
  (sort hand (match-lambda** [(_ 'joker) #f] [('joker _) #t] [((F._ f1) (F._ f2)) (> f1 f2)])))

;; even playing poker for money, I never managed to consistently determine what effect jokers were
;; having on my hand... so I'll do an exhaustive search of what's best!
;;
;; scoring hands allows us to choose a best value for joker(s)
;; hand-names provides an order (and therefore a score) for each of the available hands
(define hand-names (list 'five-of-a-kind 'straight-flush 'four-of-a-kind 'full-house 'flush 'straight
                         'three-of-a-kind 'two-pair 'one-pair 'high-card))

(define hand-order# (for/hash ((h hand-names) (i (in-range (add1 (length hand-names)) 0 -1)))
                      (values h i)))
;; The score of a hand is (its order*15^5)+(first tiebreaker*15^4)+(2nd tiebreaker*15^3)...
;; powers of 15 because we have a maxmium face value of 14 (ace) -- even though there are 13 cards
;; in a suit.
(define (calculate-score analysis)
  (define-values (hand-name tiebreakers) (car+cdr analysis))
  (for/sum ((n (in-naturals)) (tb (cons (hash-ref hand-order# hand-name -1) tiebreakers)))
    (* tb (expt 15 (- 5 n)))))

;; score hand produces an analysis of a hand (which can then be returned to analyse-sorted-hand,
;; and a score that can be maximised by choosing the right jokers.
(define (score-hand hand . jokers) ; gives an orderable list of hands with tiebreakers
  (define analysis (analyse-sorted-hand (face-sort (append jokers hand))))
  (cons analysis (calculate-score analysis)))

;; if we have two (or more) jokers, they will be consumed by the recursive call to
;; analyse-sorted-hand score-hand
(define all-cards/promise (delay (for*/list ((f (in-range 2 15)) (s '(h d s c))) (cons f s))))

(define (best-jokered-hand cards) ; we've lost the first joker from cards  
  (define-values (best-hand _bhs)
    (for*/fold ((best-hand #f) (best-score 0))
      ((joker (in-list (force all-cards/promise)))
       (score (in-value (score-hand cards joker)))
       #:when (> (cdr score) best-score))
      (car+cdr score)))

  best-hand)

;; we can abbreviate 2/3/4/5-of-a-kind 2-pair full-house with 2 and 3
(define-match-expander F*2 (λ (stx) (syntax-case stx () [(_ f) #'(list (F._ f) (F._ f))])))
(define-match-expander F*3 (λ (stx) (syntax-case stx () [(_ f) #'(list (F._ f) (F._ f) (F._ f))])))

;; note that flush? is cheaper to calculate than straight?, so do it first when we test for
;; straight-flush
(define flush?
  (match-lambda [(and `(,(_.S s) ,(_.S s) ,(_.S s) ,(_.S s) ,(_.S s)) `(,(F._ fs) ...)) `(flush ,@fs)]
                [_ #f]))

(define straight?
  (match-lambda
    ;; '(straight 5) puts this at the bottom of the pile w.r.t the ordering of straights
    [`(,(F._ 14) ,(F._ 5) ,(F._ 4) ,(F._ 3) ,(F._ 2))                                   '(straight 5)]
    [`(,(F._ f5) ,(F._ f4) ,(F._ f3) ,(F._ f2) ,(F._ f1))
     (and (= f1 (- f5 4)) (< f1 f2 f3 f4 f5)                                       `(straight ,f5))]))

(define analyse-sorted-hand
  (match-lambda
    [(list 'joker cards ...)                                                (best-jokered-hand cards)]
    [`(,@(F*3 f) ,@(F*2 f))                                                      `(five-of-a-kind ,f)]
    ;; get "top" from the straight. a the top card of the flush when there is a (straight 5) will
    ;; be the ace ... putting it in the wrong place for the ordering.
    [(and (? flush?) (app straight? (list 'straight top _ ...)))               `(straight-flush ,top)]
    [(or `(,@(F*2 f) ,@(F*2 f) ,_) `(,_ ,@(F*2 f) ,@(F*2 f)))                    `(four-of-a-kind ,f)]
    [(or `(,@(F*3 fh) ,@(F*2 fl)) `(,@(F*2 fh) ,@(F*3 fl)))                     `(full-house ,fh, fl)]
    [(app flush? (and rv (list 'flush _ ...)))                                                     rv]
    [(app straight? (and rv (list 'straight _ ...)))                                               rv]
    ;; pairs and threes may be padded to the left, middle and right with tie-breakers; the lists of
    ;; which we will call l, m and r, respectively (four and 5-of-a-kind don't need tiebreaking,
    ;; they're well hard!)
    [`(,(F._ l) ... ,@(F*3 f) ,(F._ r) ...)                             `(three-of-a-kind ,f ,@l ,@r)]
    [`(,(F._ l) ... ,@(F*2 f1) ,(F._ m) ... ,@(F*2 f2) ,(F._ r) ...)  `(two-pair ,f1 ,f2 ,@l ,@m ,@r)]
    [`(,(F._ l) ... ,@(F*2 f) ,(F._ r) ...)                                    `(one-pair ,f ,@l ,@r)]
    [`(,(F._ f) ...)                                                                 `(high-card ,@f)]
    [hand                                                                (error 'invalid-hand hand)]))

(define (analyse-hand/string hand-string)
  (analyse-sorted-hand (face-sort (string->hand hand-string))))

;;; --------------------------------------------------------------------------------------------------
;;; Strings to cards, cards to strings -- that kind of thing
;;; --------------------------------------------------------------------------------------------------
(define suit->unicode (match-lambda ('h "♥") ('d "♦") ('c "♣") ('s "♠") (x x)))

(define unicode->suit (match-lambda ("♥" 'h) ("♦" 'd) ("♣" 'c) ("♠" 's) (x x)))

(define (face->number f)
  (match (string-upcase f)
    ["T" 10] ["J" 11] ["Q" 12] ["K" 13] ["A" 14] [(app string->number (? number? n)) n] [else 0]))

(define number->face (match-lambda (10 "T") (11 "J") (12 "Q") (13 "K") (14 "A") ((app ~s x) x)))

(define string->card
  (match-lambda
    ("joker" 'joker)
    ((regexp #px"^(.*)(.)$" (list _ (app face->number num) (app unicode->suit suit)))
     (cons num suit))))

(define (string->hand str)
  (map string->card (regexp-split #rx" +" (string-trim str))))

(define card->string
  (match-lambda ['joker "[]"]
                [(cons (app number->face f) (app suit->unicode s)) (format "~a~a" f s)]))

(define (hand->string h)
  (string-join (map card->string h) " "))

;; used for both testing and output
(define e.g.-hands
  (list " 2♥  2♦ 2♣ k♣  q♦" " 2♥  5♥ 7♦ 8♣  9♠" " a♥  2♦ 3♣ 4♣  5♦" "10♥  j♦ q♣ k♣  a♦"
        " 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♣"
        
        " joker  2♦  2♠  k♠  q♦"     "  joker  5♥  7♦  8♠  9♦"    "  joker  2♦  3♠  4♠  5♠"
        "  joker  3♥  2♦  3♠  3♦"    "  joker  7♥  2♦  3♠  3♦"    "  joker  7♥  7♦  7♠  7♣"
        "  joker  j♥  q♥  k♥  A♥"    "  joker  4♣  k♣  5♦ 10♠"    "  joker  k♣  7♣  6♣  4♣"
        "  joker  2♦  joker  4♠  5♠" "  joker  Q♦  joker  A♠ 10♠" "  joker  Q♦  joker  A♦ 10♦"
        "  joker  2♦  2♠  joker  q♦"))

;;; --------------------------------------------------------------------------------------------------
;;; Main and test modules
;;; --------------------------------------------------------------------------------------------------
(module+ main
  (define scored-hands
    (for/list ((h (map string->hand e.g.-hands)))
      (define-values (analysis score) (car+cdr (score-hand h)))
      (list h analysis score)))
  
  (for ((a.s (sort scored-hands > #:key third)))
    (match-define (list (app hand->string h) a _) a.s)
    (printf "~a: ~a ~a" h (~a (first a) #:min-width 15) (number->face (second a)))
    (when (pair? (cddr a)) (printf " [tiebreak: ~a]" (string-join (map number->face (cddr a)) ", ")))
    (newline)))

(module+ test
  (require rackunit)  
  (let ((e.g.-strght-flsh '((14 . h) (13 . h) (12 . h) (11 . h) (10 . h))))
    (check-match (straight? e.g.-strght-flsh) '(straight 14))
    (check-match (flush? e.g.-strght-flsh) '(flush 14 13 12 11 10))
    (check-match e.g.-strght-flsh (and (? flush?) (app straight? (list 'straight top _ ...)))))
  
  (define expected-results
    '((three-of-a-kind 2 13 12)
      (high-card 9 8 7 5 2) (straight 5) (straight 14) (full-house 3 2) (two-pair 3 2 7)
      (four-of-a-kind 7) (straight-flush 14) (one-pair 4 13 10 5) (flush 12 10 7 6 4)      
      (three-of-a-kind 2 13 12) (straight 9) (straight 6) (four-of-a-kind 3) (three-of-a-kind 3 7 2)
      (five-of-a-kind 7) (straight-flush 14) (one-pair 13 10 5 4) (flush 14 13 7 6 4) (straight 6)
      (straight 14) (straight-flush 14) (four-of-a-kind 2)))
  (for ((h e.g.-hands) (r expected-results)) (check-equal? (analyse-hand/string h) r)))
Output:
[] 7♥ 7♦ 7♠ 7♣: five-of-a-kind  7
T♥ J♥ Q♥ K♥ A♥: straight-flush  A
[] J♥ Q♥ K♥ A♥: straight-flush  A
[] Q♦ [] A♦ T♦: straight-flush  A
2♥ 7♥ 7♦ 7♣ 7♠: four-of-a-kind  7
[] 3♥ 2♦ 3♠ 3♦: four-of-a-kind  3
[] 2♦ 2♠ [] Q♦: four-of-a-kind  2
2♥ 3♥ 2♦ 3♣ 3♦: full-house      3 [tiebreak: 2]
[] K♣ 7♣ 6♣ 4♣: flush           A [tiebreak: K, 7, 6, 4]
Q♣ T♣ 7♣ 6♣ 4♣: flush           Q [tiebreak: T, 7, 6, 4]
T♥ J♦ Q♣ K♣ A♦: straight        A
[] Q♦ [] A♠ T♠: straight        A
[] 5♥ 7♦ 8♠ 9♦: straight        9
[] 2♦ 3♠ 4♠ 5♠: straight        6
[] 2♦ [] 4♠ 5♠: straight        6
A♥ 2♦ 3♣ 4♣ 5♦: straight        5
[] 7♥ 2♦ 3♠ 3♦: three-of-a-kind 3 [tiebreak: 7, 2]
2♥ 2♦ 2♣ K♣ Q♦: three-of-a-kind 2 [tiebreak: K, Q]
[] 2♦ 2♠ K♠ Q♦: three-of-a-kind 2 [tiebreak: K, Q]
2♥ 7♥ 2♦ 3♣ 3♦: two-pair        3 [tiebreak: 2, 7]
[] 4♣ K♣ 5♦ T♠: one-pair        K [tiebreak: T, 5, 4]
4♥ 4♠ K♠ 5♦ T♠: one-pair        4 [tiebreak: K, T, 5]
2♥ 5♥ 7♦ 8♣ 9♠: high-card       9 [tiebreak: 8, 7, 5, 2]

Raku

(formerly Perl 6) This solution handles jokers. It has been written to use a Raku grammar.

use v6;
 
grammar PokerHand {
 
    # Raku Grammar to parse and rank 5-card poker hands
    # E.g. PokerHand.parse("2♥ 3♥ 2♦ 3♣ 3♦");
    # 2013-12-21: handle 'joker' wildcards; maximum of two
 
    rule TOP {
         :my %*PLAYED;
         { %*PLAYED = () }
         [ <face-card> | <joker> ]**5
    }
 
    token face-card {<face><suit> <?{
            my $card = ~$/.lc;
            # disallow duplicates
            ++%*PLAYED{$card} <= 1;
       }>
    }
 
    token joker {:i 'joker' <?{
            my $card = ~$/.lc;
            # allow two jokers in a hand
            ++%*PLAYED{$card} <= 2;
        }>
    }
 
    token face {:i <[2..9 jqka]> | 10 }
    token suit {<[♥ ♦ ♣ ♠]>}
}

class PokerHand::Actions {
    method TOP($/) {
        my UInt @n    = n-of-a-kind($/);
        my $flush     = 'flush' if flush($/);
        my $straight  = 'straight' if straight($/);
        make rank(@n[0], @n[1], $flush, $straight);
    }
    multi sub rank(5,*@)                    { 'five-of-a-kind' }
    multi sub rank($,$,'flush','straight')  { 'straight-flush' }
    multi sub rank(4,*@)                    { 'four-of-a-kind' }
    multi sub rank($,$,'flush',$)           { 'flush' }
    multi sub rank($,$,$,'straight')        { 'straight' }
    multi sub rank(3,2,*@)                  { 'full-house' }
    multi sub rank(3,*@)                    { 'three-of-a-kind' }
    multi sub rank(2,2,*@)                  { 'two-pair' }
    multi sub rank(2,*@)                    { 'one-pair' }
    multi sub rank(*@)                      { 'high-card' }
  
    sub n-of-a-kind($/) {
        my %faces := bag @<face-card>.map: -> $/ {~$<face>.lc};
        my @counts = %faces.values.sort.reverse;
        @counts[0] += @<joker>;
        return @counts;
    }
 
    sub flush($/) {
        my @suits = unique @<face-card>.map: -> $/ {~$<suit>};
        return +@suits == 1;
    }
 
    sub straight($/) {
        # allow both ace-low and ace-high straights
        constant @Faces = [ "a 2 3 4 5 6 7 8 9 10 j q k a".split: ' ' ];
        constant @Possible-Straights = [ (4 ..^ @Faces).map: { set @Faces[$_-4 .. $_] } ];

        my $faces = set @<face-card>.map: -> $/ {~$<face>.lc};
        my $jokers = +@<joker>;
 
        return ?( @Possible-Straights.first: { +($faces$_) + $jokers == 5 } );
    }
}

my PokerHand::Actions $actions .= new;

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
     "a♥ a♥ 3♣ 4♣ 5♦",   # invalid
     ## EXTRA CREDIT ##
     "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
    ) {
    my $rank = do with PokerHand.parse($_, :$actions) {
        .ast;
    }
    else {
        'invalid';
    }
    say "$_: $rank";
}
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
a♥ a♥ 3♣ 4♣ 5♦: invalid
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

REXX

version 1

/* 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
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

version 2 with suit glyphs

This REXX version supports:

  •   upper/lower/mixed case for suits and pips
  •   allows commas or blanks for card separation
  •   alternate names for aces and tens
  •   alphabetic letters for suits and/or glyphs
  •   specification of number of cards in a hand
  •   the dealt hands can be in a file   (blank lines are ignored)
  •   dealt hands in the file can have comments after a semicolon (;)
/*REXX program analyzes an  N─card  poker hand,  and displays  what  the poker hand is. */
parse arg iFID .;       if iFID=='' | iFID==","  then iFID= 'POKERHAN.DAT'
                                                 /* [↓] read  the poker hands dealt.    */
      do  while lines(iFID)\==0;      ox= linein(iFID);       if ox=''  then iterate
      say right(ox, max(30, length(ox) ) )       ' ◄─── '       analyze(ox)
      end   /*while*/                            /* [↑]  analyze/validate the poker hand*/
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
analyze: procedure; arg x ';',mc;      hand= translate(x, '♥♦♣♠1', "HDCSA,");    flush= 0
kinds= 0;    suit.= 0;    pairs= 0;    @.= 0;         run= copies(0, 13);        pips= run
if mc==''  then mc= 5;    n= words(hand);  if n\==mc  then  return  'invalid'
                                                 /* [↓]  PIP can be  1 or 2  characters.*/
   do j=1  for n;      _= word(hand, j)          /*obtain a card from the dealt hand.   */
   pip= left(_, length(_) - 1);  ws= right(_, 1) /*obtain the card's pip;  and the suit.*/
   if pip==10  then pip= 'T'                     /*allow an alternate form for a "TEN". */
   @._= @._ + 1                                  /*bump the card counter for this hand. */
   #= pos(pip, 123456789TJQK)                    /*obtain the pip index for the card.   */
   if pos(ws, "♥♣♦♠")==0  then return 'invalid suit in card:'     _
   if #==0                then return 'invalid pip in card:'      _
   if @._\==1             then return 'invalid, duplicate card:'  _
   suit.ws= suit.ws + 1                          /*count the suits for a possible flush.*/
     flush= max(flush, suit.ws)                  /*count number of cards in the suits.  */
       run= overlay(., run, #)                   /*convert runs to a series of periods. */
         _= substr(pips, #, 1) + 1               /*obtain the number of the pip in hand.*/
      pips= overlay(_, pips, #)                  /*convert the pip to legitimate number.*/
     kinds= max(kinds, _)                        /*convert certain pips to their number.*/
   end   /*i*/                                   /* [↑]  keep track of  N─of─a─kind.    */

     run= run || left(run, 1)                    /*An  ace  can be  high  ─or─  low.    */
   pairs= countstr(2, pips)                      /*count number of pairs  (2s  in PIPS).*/
straight= pos(....., run || left(run, 1) ) \== 0 /*does the  RUN  contains a straight?  */
if flush==5 & straight  then return  'straight-flush'
if kinds==4             then return  'four-of-a-kind'
if kinds==3 & pairs==1  then return  'full-house'
if flush==5             then return  'flush'
if            straight  then return  'straight'
if kinds==3             then return  'three-of-a-kind'
if kinds==2 & pairs==2  then return  'two-pair'
if kinds==2             then return  'one-pair'
                             return  'high-card'

Programming note: some older REXXes don't have the countstr BIF, so that REXX statement (above, line 48) can be replaced with:

pairs= 13 - length( space( translate( pips, , 2), 0) )   /*count # of  2's  in PIPS.*/
input   file:
  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♣  t♣  7♣  6♣  4♣
  J♥  Q♦  K♠  A♠ 10♠

  ah  2h  3h  4h
output   when using the (above) input file
            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♣  t♣  7♣  6♣  4♣  ◄───  flush
            J♥  Q♦  K♠  A♠ 10♠  ◄───  straight
                ah  2h  3h  4h  ◄───  invalid

version 3 with suit glyphs and jokers

This REXX version has three additional features:

  •   "invalid" hands have additional diagnostic information
  •   supports up to two jokers
  •   the joker card may be abbreviated (and can be in upper/lower/mixed case)
/*REXX program analyzes an  N-card  poker hand, and displays what the poker hand is,    */
/*──────────────────────────────────────────── poker hands may contain up to two jokers.*/
parse arg iFID .;       if iFID=='' | iFID==","  then iFID= 'POKERHAJ.DAT'
                                                 /* [↓] read  the poker hands dealt.    */
      do  while lines(iFID)\==0;      ox= linein(iFID);         if ox=''  then iterate
      say right(ox, max(30, length(ox) ) )       ' ◄─── '       analyze(ox)
      end   /*while*/                            /* [↑]  analyze/validate the poker hand*/
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
analyze: procedure; arg x ';',mc;       hand=translate(x, '♥♦♣♠1', "HDCSA,");    flush= 0
kinds= 0;    suit.= 0;    pairs= 0;     @.= 0;         run= copies(0 ,13);       pips= run
if mc==''  then mc= 5;    n= words(hand)         /*N   is the number of cards in hand.  */
if n\==mc  then return  'invalid number of cards, must be' mc
                                                 /* [↓]  the PIP can be  1 or 2  chars. */
   do j=1  for n;     _= word(hand, j)           /*obtain a card from the dealt hand.   */
   pip= left(_, length(_) - 1);  ws= right(_, 1) /*obtain card's pip; obtain card's suit*/
   if pip==10   then pip= 'T'                    /*allow alternate form for a  TEN  pip.*/
   if abbrev('JOKER', _, 1)  then _= "JK"        /*allow altername forms of JOKER names.*/
   @._= @._ + 1                                  /*bump the card counter for this hand. */
   #= pos(pip, 123456789TJQK)                    /*obtain the pip index for the card.   */
   if _=='JK'  then do;  if @.j>2  then return 'invalid, too many jokers'
                         iterate
                    end
   if pos(ws, "♥♣♦♠")==0  then return 'invalid suit in card:'     _
   if #==0                then return 'invalid pip in card:'      _
   if @._\==1             then return 'invalid, duplicate card:'  _
   suit.ws= suit.ws + 1                          /*count the suits for a possible flush.*/
     flush= max(flush, suit.ws)                  /*count number of cards in the suits.  */
       run= overlay(., run, #)                   /*convert runs to a series of periods. */
         _= substr(pips, #, 1) + 1               /*obtain the number of the pip in hand.*/
      pips= overlay(_, pips, #)                  /*convert the pip to legitimate number.*/
     kinds= max(kinds, _)                        /*convert certain pips to their number.*/
   end   /*i*/                                   /* [↑]  keep track of  N─of─a─kind.    */

run= run || left(run, 1)                         /*An  ace  can be  high  ─or─  low.    */
jok= @.jk;   kinds= kinds+jok;  flush= flush+jok /*N─of─a─kind;  adjustments for jokers.*/
straight= pos(..... , run)\==0           |,      /*does the RUN contain a straight?     */
         (pos(....  , run)\==0 & jok>=1) |,      /*  "   "   "     "    "     "         */
         (pos(..0.. , run)\==0 & jok>=1) |,      /*  "   "   "     "    "     "         */
         (pos(...0. , run)\==0 & jok>=1) |,      /*  "   "   "     "    "     "         */
         (pos(.0... , run)\==0 & jok>=1) |,      /*  "   "   "     "    "     "         */
         (pos(...   , run)\==0 & jok>=2) |,      /*  "   "   "     "    "     "         */
         (pos(..0.  , run)\==0 & jok>=2) |,      /*  "   "   "     "    "     "         */
         (pos(.0..  , run)\==0 & jok>=2) |,      /*  "   "   "     "    "     "         */
         (pos(.00.. , run)\==0 & jok>=2) |,      /*  "   "   "     "    "     "         */
         (pos(..00. , run)\==0 & jok>=2) |,      /*  "   "   "     "    "     "         */
         (pos(.0.0. , run)\==0 & jok>=2)         /*  "   "   "     "    "     "         */
pairs= countstr(2, pips)                         /*count number of pairs  (2s in PIPS). */
if jok\==0  then pairs= pairs - 1                /*adjust number of pairs with jokers.  */
if kinds>=5             then return  'five-of-a-kind'
if flush>=5 & straight  then return  'straight-flush'
if kinds>=4             then return  'four-of-a-kind'
if kinds>=3 & pairs>=1  then return  'full-house'
if flush>=5             then return  'flush'
if            straight  then return  'straight'
if kinds>=3             then return  'three-of-a-kind'
if kinds==2 & pairs==2  then return  'two-pair'
if kinds==2             then return  'one-pair'
                             return  'high-card'

Programming note:   the method used for analyzing hands that contain jokers are limited to a maximum of two jokers.

A different methodology would be needed for a generic number of jokers (and/or wild cards [such as deuces and one─eyed jacks]).

input   file:
   joker  2♦  2♠  k♠  q♦
   joker  5♥  7♦  8♠  9♦
   joker  2♦  3♠  4♠  5♠
   joker  3♥  2♦  3♠  3♦
   joker  7♥  2♦  3♠  3♦
   joker  7♥  7♦  7♠  7♣
   joker  j♥  q♥  k♥  A♥
   joker  4♣  k♣  5♦ 10♠
   joker  t♣  7♣  6♣  4♣
   joker  Q♦  K♠  A♠ 10♠

   joker  2h  3h  4h

      2♥  2♦  2♠  k♠  jok
      2♥  5♥  7♦  8♠  jok
      a♥  2♦  5♠  4♠  jok
      2♥  3♥  2♦  3♠  jok
      2♥  7♥  2♦  3♠  jok
      2♥  7♥  7♦  7♠  jok
     10♥  j♥  q♥  k♥  jok
      4♥  4♣  k♣  5♦  jok
      q♣  t♣  7♣  6♣  jok
      J♥  Q♦  K♠  A♠  jok 
output   when using the (above) input file
         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  t♣  7♣  6♣  4♣  ◄───  flush
         joker  Q♦  K♠  A♠ 10♠  ◄───  straight
             joker  2h  3h  4h  ◄───  invalid number of cards, must be 5
           2♥  2♦  2♠  k♠  jok  ◄───  four-of-a-kind
           2♥  5♥  7♦  8♠  jok  ◄───  one-pair
           a♥  2♦  5♠  4♠  jok  ◄───  straight
           2♥  3♥  2♦  3♠  jok  ◄───  full-house
           2♥  7♥  2♦  3♠  jok  ◄───  three-of-a-kind
           2♥  7♥  7♦  7♠  jok  ◄───  four-of-a-kind
          10♥  j♥  q♥  k♥  jok  ◄───  straight-flush
           4♥  4♣  k♣  5♦  jok  ◄───  three-of-a-kind
           q♣  t♣  7♣  6♣  jok  ◄───  flush
           J♥  Q♦  K♠  A♠  jok  ◄───  straight 

RPL

Works with: HP version 48G
≪ → hand
   ≪ { }
      1 15 FOR j
        "A23456789TJQK" hand j DUP SUB POS 1 - 
        "CDHS" hand j 1 + DUP SUB POS 13 * +
        + 3 STEP
≫ ≫ 'HANDCODE' STO 

≪ → diffs
   ≪ { } 1
      1 4 FOR j
        IF diffs j GET THEN + 1 ELSE 1 + END NEXT
      + SORT REVLIST 1 2 SUB
≫ ≫ 'GROUPS' STO 

≪ DUP ΠLIST 1 ==
   SWAP { 9 1 1 1 } 1 == OR
≫ 'STRAIGHT?' STO 

≪ HANDCODE
   DUP 13 / IP  ≪ == ≫ DOSUBS ΠLIST
   SWAP 13 MOD SORT ΔLIST
   DUP GROUPS SWAP STRAIGHT?
   → flush groups straight
   ≪ CASE
        straight THEN flush "Straight flush" "Straight" IFTE END
        groups { 4 1 } == THEN "Four of a kind" END
        groups { 3 2 } == THEN "Full house" END
        groups { 3 1 } == THEN "Three of a kind" END
        groups { 2 2 } == THEN "Two pairs" END   
        groups { 2 1 } == THEN "One pair" END       
        flush "Flush" "High card" IFTE END
≫ ≫ '→HAND' STO 
{ "2H 2D 2S KS QD" "2H 5H 7D 8S 9D" "AH 2D 3S 4S 5S" "2H 3H 2D 3S 3D" "2H 7H 2D 3S 3D" "2H 7H 7D 7S 7C" "TH JH QH KH AH" "4H 4C KC 5D TC" "QC TC 7C 6C 4C" }
1 ≪ →HAND ≫ DOLIST
Output:
1: { "Three of a kind" "High card" "Straight" "Full house" "Two pairs" "Four of a kind" "Straight flush" "One pair" "Flush" }

Ruby

Joker-less hands are sorted high to low.

class Card
  include Comparable
  attr_accessor :ordinal
  attr_reader :suit, :face 
  
  SUITS = %i(   )
  FACES = %i(2 3 4 5 6 7 8 9 10 j q k a)
  
  def initialize(str)
    @face, @suit = parse(str)
    @ordinal = FACES.index(@face)
  end
  
  def <=> (other) #used for sorting
    self.ordinal <=> other.ordinal
  end
  
  def to_s
    "#@face#@suit"
  end
  
  private
  def parse(str)
    face, suit = str.chop.to_sym, str[-1].to_sym
    raise ArgumentError, "invalid card: #{str}" unless FACES.include?(face) && SUITS.include?(suit)
    [face, suit]
  end
end

class Hand
  include Comparable
  attr_reader :cards, :rank
  
  RANKS       = %i(high-card one-pair two-pair three-of-a-kind straight flush
                   full-house four-of-a-kind straight-flush five-of-a-kind)
  WHEEL_FACES = %i(2 3 4 5 a)
  
  def initialize(str_of_cards)
    @cards = str_of_cards.downcase.tr(',',' ').split.map{|str| Card.new(str)}
    grouped = @cards.group_by(&:face).values
    @face_pattern = grouped.map(&:size).sort
    @rank = categorize
    @rank_num = RANKS.index(@rank)
    @tiebreaker = grouped.map{|ar| [ar.size, ar.first.ordinal]}.sort.reverse
  end
  
  def <=> (other)    # used for sorting and comparing
    self.compare_value <=> other.compare_value
  end
  
  def to_s
    @cards.map(&:to_s).join(" ")
  end
  
  protected          # accessible for Hands
  def compare_value
    [@rank_num, @tiebreaker]
  end
  
  private
  def one_suit?
    @cards.map(&:suit).uniq.size == 1
  end
  
  def consecutive?
    sort.each_cons(2).all? {|c1,c2| c2.ordinal - c1.ordinal == 1 }
  end
  
  def sort
    if @cards.sort.map(&:face) == WHEEL_FACES
      @cards.detect {|c| c.face == :a}.ordinal = -1
    end 
    @cards.sort
  end
  
  def categorize
    if consecutive?
      one_suit? ? :'straight-flush' : :straight
    elsif one_suit?
      :flush
    else
      case @face_pattern
        when [1,1,1,1,1] then :'high-card'
        when [1,1,1,2]   then :'one-pair'
        when [1,2,2]     then :'two-pair'
        when [1,1,3]     then :'three-of-a-kind'
        when [2,3]       then :'full-house'
        when [1,4]       then :'four-of-a-kind'
        when [5]         then :'five-of-a-kind'
      end
    end
  end
end

# Demo
test_hands = <<EOS
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♥ 6♥ 2♦ 3♣ 3♦
10♥ j♥ q♥ k♥ a♥
4♥ 4♠ k♠ 2♦ 10♠
4♥ 4♠ k♠ 3♦ 10♠
q♣ 10♣ 7♣ 6♣ 4♣
q♣ 10♣ 7♣ 6♣ 3♣
9♥ 10♥ q♥ k♥ j♣
2♥ 3♥ 4♥ 5♥ a♥
2♥ 2♥ 2♦ 3♣ 3♦
EOS

hands = test_hands.each_line.map{|line| Hand.new(line) }
puts "High to low"
hands.sort.reverse.each{|hand| puts "#{hand}\t #{hand.rank}" }
puts

str = <<EOS
joker  2♦  2♠  k♠  q♦
joker  5♥  7♦  8♠  9♦
joker  2♦  3♠  4♠  5♠
joker  3♥  2♦  3♠  3♦
joker  7♥  2♦  3♠  3♦
joker  7♥  7♦  7♠  7♣
joker  j♥  q♥  k♥  A♥
joker  4♣  k♣  5♦ 10♠
joker  k♣  7♣  6♣  4♣
joker  2♦  joker  4♠  5♠
joker  Q♦  joker  A♠ 10♠
joker  Q♦  joker  A♦ 10♦
joker  2♦  2♠  joker  q♦
EOS

# Neither the Card nor the Hand class supports jokers
# but since hands are comparable, they are also sortable.
# Try every card from a deck for a joker and pick the largest hand:

DECK = Card::FACES.product(Card::SUITS).map(&:join)
str.each_line do |line|
  cards_in_arrays = line.split.map{|c| c == "joker" ? DECK.dup : [c]} #joker is array of all cards
  all_tries  = cards_in_arrays.shift.product(*cards_in_arrays).map{|ar| Hand.new(ar.join" ")} #calculate the Whatshisname product
  best = all_tries.max
  puts "#{line.strip}: #{best.rank}"
end
Output:
High to low
10♥ j♥ q♥ k♥ a♥	 straight-flush
2♥ 3♥ 4♥ 5♥ a♥	 straight-flush
2♥ 3♥ 2♦ 3♣ 3♦	 full-house
2♥ 2♥ 2♦ 3♣ 3♦	 full-house
q♣ 10♣ 7♣ 6♣ 4♣	 flush
q♣ 10♣ 7♣ 6♣ 3♣	 flush
9♥ 10♥ q♥ k♥ j♣	 straight
a♥ 2♦ 3♣ 4♣ 5♦	 straight
2♥ 2♦ 2♣ k♣ q♦	 three-of-a-kind
2♥ 7♥ 2♦ 3♣ 3♦	 two-pair
2♥ 6♥ 2♦ 3♣ 3♦	 two-pair
4♥ 4♠ k♠ 3♦ 10♠	 one-pair
4♥ 4♠ k♠ 2♦ 10♠	 one-pair
2♥ 5♥ 7♦ 8♣ 9♠	 high-card

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

Rust

Unicode version with jokers. Also checks for Royal Flush (AKQJ10 suited).

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)
}
Output:
🂡 🂮 🂭 🂫 🂪 royal-flush
🃏 🃂 🂢 🂮 🃍 three-of-a-kind
🃏 🂵 🃇 🂨 🃉 straight
🃏 🃂 🂣 🂤 🂥 straight
🃏 🂳 🃂 🂣 🃃 four-of-a-kind
🃏 🃂 🂢 🂮 🃍 three-of-a-kind
🃏 🂵 🃇 🂨 🃉 five-of-a-kind
🃏 🃂 🂣 🂤 🂥 straight-flush
🃏 🂳 🃂 🂣 🃃 one-pair
🃏 🃂 🂢 🂮 🃍 flush
🃏 🂵 🃇 🂨 🃉 straight
🃏 🃂 🂣 🂤 🂥 straight
🃏 🂳 🃂 🂣 🃃 straight-flush
🃏 🃂 🂢 🂮 🃍 four-of-a-kind
🃏 🂵 🃇 🂨 🃉 invalid
🃏 🃞 🃍 🃁 🃊 high-card

Scala

Including jokers, but not special suit characters. Aiming for readability more than performance.

val faces = "23456789TJQKA"
val suits = "CHSD"
sealed trait Card
object Joker extends Card
case class RealCard(face: Int, suit: Char) extends Card
val allRealCards = for {
  face <- 0 until faces.size
  suit <- suits
} yield RealCard(face, suit)

def parseCard(str: String): Card = {
  if (str == "joker") {
    Joker
  } else {
    RealCard(faces.indexOf(str(0)), str(1))
  }
}

def parseHand(str: String): List[Card] = {
  str.split(" ").map(parseCard).toList
}

trait HandType {
  def name: String
  def check(hand: List[RealCard]): Boolean
}

case class And(x: HandType, y: HandType, name: String) extends HandType {
  def check(hand: List[RealCard]) = x.check(hand) && y.check(hand)
}

object Straight extends HandType {
  val name = "straight"
  def check(hand: List[RealCard]): Boolean = {
    val faces = hand.map(_.face).toSet
    faces.size == 5 && (faces.min == faces.max - 4 || faces == Set(0, 1, 2, 3, 12))
  }
}

object Flush extends HandType {
  val name = "flush"
  def check(hand: List[RealCard]): Boolean = {
    hand.map(_.suit).toSet.size == 1
  }
}

case class NOfAKind(n: Int, name: String = "", nOccur: Int = 1) extends HandType {
  def check(hand: List[RealCard]): Boolean = {
    hand.groupBy(_.face).values.count(_.size == n) >= nOccur
  }
}

val allHandTypes = List(
  NOfAKind(5, "five-of-a-kind"),
  And(Straight, Flush, "straight-flush"),
  NOfAKind(4, "four-of-a-kind"),
  And(NOfAKind(3), NOfAKind(2), "full-house"),
  Flush,
  Straight,
  NOfAKind(3, "three-of-a-kind"),
  NOfAKind(2, "two-pair", 2),
  NOfAKind(2, "one-pair")
)

def possibleRealHands(hand: List[Card]): List[List[RealCard]] = {
  val realCards = hand.collect { case r: RealCard => r }
  val nJokers = hand.count(_ == Joker)
  allRealCards.toList.combinations(nJokers).map(_ ++ realCards).toList
}

def analyzeHand(hand: List[Card]): String = {
  val possibleHands = possibleRealHands(hand)
  allHandTypes.find(t => possibleHands.exists(t.check)).map(_.name).getOrElse("high-card")
}
val testHands = List(
  "2H 2D 2S KS QD",
  "2H 5H 7D 8S 9D",
  "AH 2D 3S 4S 5S",
  "2H 3H 2D 3S 3D",
  "2H 7H 2D 3S 3D",
  "2H 7H 7D 7S 7C",
  "TH JH QH KH AH",
  "4H 4C KC 5D TC",
  "QC TC 7C 6C 4C",
  "QC TC 7C 7C TD",
  "2H 2D 2S KS joker",
  "2H 5H 7D 8S joker",
  "AH 2D 3S 4S joker",
  "2H 3H 2D 3S joker",
  "2H 7H 2D 3S joker",
  "2H 7H 7D joker joker",
  "TH JH QH joker joker",
  "4H 4C KC joker joker",
  "QC TC 7C joker joker",
  "QC TC 7H joker joker"
)

for (hand <- testHands) {
  println(s"$hand -> ${analyzeHand(parseHand(hand))}")
}
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
QC TC 7C 7C TD -> two-pair
2H 2D 2S KS joker -> four-of-a-kind
2H 5H 7D 8S joker -> one-pair
AH 2D 3S 4S joker -> straight
2H 3H 2D 3S joker -> full-house
2H 7H 2D 3S joker -> three-of-a-kind
2H 7H 7D joker joker -> four-of-a-kind
TH JH QH joker joker -> straight-flush
4H 4C KC joker joker -> four-of-a-kind
QC TC 7C joker joker -> flush
QC TC 7H joker joker -> three-of-a-kind

Seed7

$ include "seed7_05.s7i";
  include "console.s7i";

const string: face is "A23456789TJQK";
const string: suit is "♥♦♣♠";

const func string: analyzeHand (in array integer: faceCnt, in array integer: suitCnt) is func
  result
    var string: handValue is "";
  local
    var boolean: pair1 is FALSE;
    var boolean: pair2 is FALSE;
    var boolean: three is FALSE;
    var boolean: four is FALSE;
    var boolean: flush is FALSE;
    var boolean: straight is FALSE;
    var integer: sequence is 0;
    var integer: x is 0;
  begin
    for x range 1 to 13 do
      case faceCnt[x] of
        when {2}: if pair1 then pair2 := TRUE; else pair1 := TRUE; end if;
        when {3}: three := TRUE;
        when {4}: four := TRUE;
      end case;
    end for;
    for x range 1 to 4 until flush do
      if suitCnt[x] = 5 then
        flush := TRUE;
      end if;
    end for;
    if not pair1 and not three and not four then
      for x range 1 to 13 until sequence = 5 do
        if faceCnt[x] <> 0 then incr(sequence); else sequence := 0; end if;
      end for;
      straight := sequence = 5 or (sequence = 4 and faceCnt[1] <> 0);
    end if;
    if straight and flush then handValue := "straight-flush";
    elsif four            then handValue := "four-of-a-kind"; 
    elsif pair1 and three then handValue := "full-house";
    elsif flush           then handValue := "flush";
    elsif straight        then handValue := "straight";
    elsif three           then handValue := "three-of-a-kind";
    elsif pair1 and pair2 then handValue := "two-pair";
    elsif pair1           then handValue := "one-pair";
    else                       handValue := "high-card";
    end if;
  end func;
 
const proc: analyze (in string: cards) is func
  local
    var array integer: faceCnt is 13 times 0;
    var array integer: suitCnt is 4 times 0;
    var string: card is "";
  begin
    for card range split(upper(cards), ' ') do
      incr(faceCnt[pos(face, card[1])]);
      incr(suitCnt[pos(suit, card[2])]);
    end for;
    writeln(cards <& ": " <& analyzeHand(faceCnt, suitCnt));
  end func;

const proc: main is func
  begin
    OUT := STD_CONSOLE;
    analyze("2♥ 2♦ 2♠ k♠ q♦");
    analyze("2♥ 5♥ 7♦ 8♠ 9♦");
    analyze("a♥ 2♦ 3♠ 4♠ 5♠");
    analyze("2♥ 3♥ 2♦ 3♠ 3♦");
    analyze("2♥ 7♥ 2♦ 3♠ 3♦");
    analyze("2♥ 7♥ 7♦ 7♠ 7♣");
    analyze("t♥ j♥ q♥ k♥ a♥");
    analyze("4♥ 4♣ k♣ 5♦ t♣");
    analyze("q♣ t♣ 7♣ 6♣ 4♣");
  end func;
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
t♥ j♥ q♥ k♥ a♥: straight-flush
4♥ 4♣ k♣ 5♦ t♣: one-pair
q♣ t♣ 7♣ 6♣ 4♣: flush

Standard ML

local
val rec ins = fn x : int*'a => fn [] => [x]
                  |  ll as h::t      => if #1 x<= (#1 h) then x::ll else h::ins x t
val rec acount = fn 
 (n,a::(b::t)) =>  if a=b then acount (n+1,b::t) else (n,a)::acount(1,b::t) 
   |  (n,[t])  =>  [(n,t)]
in                                                                             (* helper count and sort functions *)
  val rec sortBy1st = fn [] => [] | h::t => ins h (sortBy1st t)
  val addcount      = fn ll => acount (1,ll)
end;



val showHand = fn input =>
 let
 exception Cheat of string 
                                                                              (* replace a j q k by their numbers *)
 val translateCardstrings = fn inputstr =>
     String.tokens (fn #" "=>true|_=>false) (String.translate (fn #"a"=>"1"| #"j"=>"11"| #"q"=>"12"| #"k"=>"13" | a=>str a ) inputstr )

                                                                 (* parse numbers and characters into int*strings and order those *)
 val parseFacesSuits =  fn cardcodes =>
  sortBy1st (List.map (fn el => (valOf (Int.fromString el ),String.extract (el,String.size el -1,NONE) )) cardcodes )
     handle Option => raise Cheat "parse"

                                                                (* replace the list of face numbers by a list of every face with its count and order it / descending count *)
 val countAndSort =fn li =>
   let   val hand = ListPair.unzip li   in  (rev (sortBy1st (addcount (#1 hand))) , #2 hand ) end;


 val score = fn
   ( (4,_)::t , _ )        => "four-of-a-kind"
 | ( (3,_)::(2,_)::t , _ ) => "full-house"
 | ( (3,_)::t,_)           => "three-of-a-kind"
 | ( (2,_)::(2,_)::t,_)    => "two-pair"
 | ( (2,_)::t,_)           => "one-pair"
 | (x as (1,_)::t,ll)      => if  #2 (hd x ) - (#2 (hd (rev x))) =4 orelse ( #2 (hd (rev x))  = 1 andalso #2 (hd (tl (rev x))) =10 )
                                 then
                                    if  List.all (fn x => hd ll=x) ll
			              then "straight-flush"
			              else "straight"
			         else if  List.all (fn x => hd ll=x) ll then "flush" else  "high-card" 
 | _                       => "invalid"



                                                                      (* return 'invalid' if any duplicates or invalid codes *)
 val validate = fn lpair : (int * string) list =>
   let val rec uniq = fn ([],y) =>true|(x,y) => List.filter (fn a=>a= hd x) y = [hd x] andalso uniq(tl x,y) 
   in
    if         List.all (fn x :int*string  =>  #1 x > 0 andalso  #1 x < 14 )  lpair
      andalso  List.all (fn (x) => Option.isSome ( List.find (fn a=> a= #2x) ["c","d","h","s"] ) )  lpair
      andalso  uniq (lpair ,lpair) 
    then lpair
    else raise Cheat "value"
   end

in


   ( score o countAndSort  o  validate  o  parseFacesSuits  o  translateCardstrings )  input      handle Cheat ch => "invalid"
   
end;

Example (interpreter):

val rec printio = fn 
    [] => ()
 |  s::t  => (print (s^"  :  "); print ((showHand s)^"\n") before printio t) ;
 
printio 
[ "2h 2d 2c kc qd" ,
  "2h 5h 7d 8c 9s",
  "ah 2d 3c 4c 5d" ,
  "2h 3h 2d 3s 3d",
  "2h 7h 2d 3c 3d",
  "2h 7h 7d 7c 7s",
  "10h jh qh kh ah",
  "4h 4s ks 5d 10s",
  "qc 10c 7c 6c 4c",
  "ac ah ac ad 10h"] ;

2h 2d 2c kc qd  :  three-of-a-kind
2h 5h 7d 8c 9s  :  high-card
ah 2d 3c 4c 5d  :  straight
2h 3h 2d 3s 3d  :  full-house
2h 7h 2d 3c 3d  :  two-pair
2h 7h 7d 7c 7s  :  four-of-a-kind
10h jh qh kh ah  :  straight-flush
4h 4s ks 5d 10s  :  one-pair
qc 10c 7c 6c 4c  :  flush
ac ah ac ad 10h  :  invalid
val it = (): unit

Tcl

Works with: Tcl version 8.6
package require Tcl 8.6
namespace eval PokerHandAnalyser {
    proc analyse {hand} {
	set norm [Normalise $hand]
	foreach type {
	    invalid straight-flush four-of-a-kind full-house flush straight
	    three-of-a-kind two-pair one-pair
	} {
	    if {[Detect-$type $norm]} {
		return $type
	    }
	}
	# Always possible to use high-card if the hand is legal at all
	return high-card
    }

    # This normalises to an internal representation that is a list of pairs,
    # where each pair is one number for the pips (ace == 14, king == 13,
    # etc.) and another for the suit. This greatly simplifies detection.
    proc Normalise {hand} {
	set PipMap {j 11 q 12 k 13 a 14}
	set SuitMap { 2 h 2  1 d 1  0 c 0  3 s 3}
	set hand [string tolower $hand]
	set cards [regexp -all -inline {(?:[akqj98765432]|10)[hdcs♥♦♣♠]} $hand]
	lsort -command CompareCards [lmap c [string map {} $cards] {
	    list [string map $PipMap [string range $c 0 end-1]] \
		    [string map $SuitMap [string index $c end]]
	}]
    }
    proc CompareCards {a b} {
	lassign $a pipA suitA
	lassign $b pipB suitB
	expr {$pipA==$pipB ? $suitB-$suitA : $pipB-$pipA}
    }

    # Detection code. Note that the detectors all assume that the preceding
    # detectors have been run first; this simplifies the logic a lot, but does
    # mean that the individual detectors are not robust on their own.
    proc Detect-invalid {hand} {
	if {[llength $hand] != 5} {return 1}
	foreach c $hand {
	    if {[incr seen($c)] > 1} {return 1}
	}
	return 0
    }
    proc Detect-straight-flush {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    if {[info exist prev] && $prev-1 != $pip} {
		# Special case: ace low straight flush ("steel wheel")
		if {$prev != 14 && $suit != 5} {
		    return 0
		}
	    }
	    set prev $pip
	    incr seen($suit)
	}
	return [expr {[array size seen] == 1}]
    }
    proc Detect-four-of-a-kind {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    if {[incr seen($pip)] > 3} {return 1}
	}
	return 0
    }
    proc Detect-full-house {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    incr seen($pip)
	}
	return [expr {[array size seen] == 2}]
    }
    proc Detect-flush {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    incr seen($suit)
	}
	return [expr {[array size seen] == 1}]
    }
    proc Detect-straight {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    if {[info exist prev] && $prev-1 != $pip} {
		# Special case: ace low straight ("wheel")
		if {$prev != 14 && $suit != 5} {
		    return 0
		}
	    }
	    set prev $pip
	}
	return 1
    }
    proc Detect-three-of-a-kind {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    if {[incr seen($pip)] > 2} {return 1}
	}
	return 0
    }
    proc Detect-two-pair {hand} {
	set pairs 0
	foreach c $hand {
	    lassign $c pip suit
	    if {[incr seen($pip)] > 1} {incr pairs}
	}
	return [expr {$pairs > 1}]
    }
    proc Detect-one-pair {hand} {
	foreach c $hand {
	    lassign $c pip suit
	    if {[incr seen($pip)] > 1} {return 1}
	}
	return 0
    }
}

Demonstrating:

foreach hand {
   "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♣"
} {
    puts "${hand}: [PokerHandAnalyser::analyse $hand]"
}
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

VBScript

option explicit
class playingcard
 dim suit
 dim pips
  'public property get gsuit():gsuit=suit:end property
  'public property get gpips():gpips=pips:end property
  public sub print
		dim s,p
		select case suit
		case "S":s=chrW(&h2660)
		case "D":s=chrW(&h2666)
		case "C":s=chrW(&h2663)
		case "H":s=chrW(&h2665)
		end select

		select case pips
		case 1:p="A"
		case 11:p="J"
		case 12:p="Q"
		case 13:p="K"
		case else: p=""& pips
		end select

		wscript.stdout.write  right("   "&p & s,3)&" "
  end sub
end class

sub printhand(byref h,start)
   dim i
   for i =start to ubound(h)
     h(i).print
   next
   'wscript.stdout.writeblanklines(1)
end sub

function checkhand(byval arr)
  dim ss,i,max,last,uses,toppip,j,straight, flush,used,ace
  redim nn(13)
  
  ss=arr(0).suit   '?????
  straight=true:flush=true
  max=0:last=0:used=0:toppip=0:ace=0
  for i=0 to ubound(arr)
      j=arr(i).pips
      if arr(i).suit<>ss then flush=false
      if j>toppip then toppip=j
      if j=1 then ace=1
      nn(j)=nn(j)+1
      if nn(j)>max then max= nn(j) 
      if abs(j-toppip)>=4  then straight=0
   next
   for i=1 to ubound(nn)
      if nn(i) then used=used+1
   next   
   if max=1 then
      if nn(1) and nn(10) and nn(11) and nn(12) and nn(13) then straight=1
   end if
   if flush and straight and max=1 then
      checkhand= "straight-flush"
   elseif flush then  
      checkhand= "flush"
   elseif straight and max=1 then   
      checkhand= "straight"
   elseif max=4 then
     checkhand= "four-of-a-kind"
   elseif max=3 then
     if used=2 then
       checkhand= "full-house"
     else  
       checkhand= "three-of-a-kind"
     end if
  elseif max=2 then
     if used=3 then
       checkhand= "two-pair"
     else  
       checkhand= "one-pair"
     end if
  else
     checkhand= "Top "& toppip
  End If
end function


function readhand(h)
  dim i,b,c,p
  redim a(4)
  for i=0 to ubound(a)
    b=h(i)
    set c=new playingcard
    p=left(b,1)
    select case p
    case "j": c.pips=11
    case "q": c.pips=12
    case "k": c.pips=13
    case "t": c.pips=10
    case "a": c.pips=1
    case else c.pips=cint(p)
    end select
    c.suit=ucase(right(b,1))
    set a(i)=c
  next
  readhand=a
  erase a
end function

dim hands,hh,i
 hands = Array(_
Array("2h", "5h", "7d", "8c", "9s"),_ 
Array("2h", "2d", "2c", "kc", "qd"),_
Array("ah", "2d", "3c", "4c", "5d"),_
Array("2h", "3h", "2d", "3c", "3d"),_
Array("2h", "7h", "2d", "3c", "3d"),_
Array("th", "jh", "qh", "kh", "ah"),_
Array("4h", "4s", "ks", "5d", "ts"),_
Array("qc", "tc", "7c", "6c", "4c"),_
Array("ah", "ah", "7c", "6c", "4c"))

for i=1 to ubound(hands)
   hh=readhand(hands(i))
   printhand hh,0
   wscript.stdout.write vbtab & checkhand(hh)
   wscript.stdout.writeblanklines(1)
   'exit for
next
Output:
 2♥  2♦  2♣  K♣  Q♦     three-of-a-kind
 A♥  2♦  3♣  4♣  5♦     straight
 2♥  3♥  2♦  3♣  3♦     full-house
 2♥  7♥  2♦  3♣  3♦     two-pair
10♥  J♥  Q♥  K♥  A♥     straight-flush
 4♥  4♠  K♠  5♦ 10♠     one-pair
 Q♣ 10♣  7♣  6♣  4♣     flush
 A♥  A♥  7♣  6♣  4♣     one-pair

Wren

Translation of: Kotlin
Library: Wren-dynamic
Library: Wren-sort
Library: Wren-str
Library: Wren-seq

Basic Version

import "./dynamic" for Tuple
import "./sort" for Sort
import "./str" for Str
import "./seq" for Lst

var Card = Tuple.create("Card", ["face", "suit"])

var FACES = "23456789tjqka"
var SUITS = "shdc"

var isStraight = Fn.new { |cards|
    var cmp = Fn.new { |i, j| (i.face - j.face).sign }
    var sorted = Sort.merge(cards, cmp)
    if (sorted[0].face + 4 == sorted[4].face) return true
    if (sorted[4].face == 14 && sorted[0].face == 2 && sorted[3].face == 5) return true
    return false
}

var isFlush = Fn.new { |cards|
    var suit = cards[0].suit
    if (cards.skip(1).all { |card| card.suit == suit }) return true
    return false
}

var analyzeHand = Fn.new { |hand|
    var h = Str.lower(hand)
    var split = Lst.distinct(h.split(" ").where { |c| c != "" }.toList)
    if (split.count != 5) return "invalid"
    var cards = []
    for (s in split) {
        if (s.count != 2) return "invalid"
        var fIndex = FACES.indexOf(s[0])
        if (fIndex == -1) return "invalid"
        var sIndex = SUITS.indexOf(s[1])
        if (sIndex == -1) return "invalid"
        cards.add(Card.new(fIndex + 2, s[1]))
    }
    var groups = Lst.groups(cards) { |card| card.face }
    if (groups.count == 2) {
        if (groups.any { |g| g[1].count == 4 }) return "four-of-a-kind"
        return "full-house"
    } else if (groups.count == 3) {
        if (groups.any { |g| g[1].count == 3 }) return "three-of-a-kind"
        return "two-pair"
    } else if (groups.count == 4) {
        return "one-pair"
    } else {
        var flush = isFlush.call(cards)
        var straight = isStraight.call(cards)
        if (flush && straight) return "straight-flush"
        if (flush)             return "flush"
        if (straight)          return "straight"
        return "high-card"
    }
}

var hands = [
    "2h 2d 2c kc qd",
    "2h 5h 7d 8c 9s",
    "ah 2d 3c 4c 5d",
    "2h 3h 2d 3c 3d",
    "2h 7h 2d 3c 3d",
    "2h 7h 7d 7c 7s",
    "th jh qh kh ah",
    "4h 4s ks 5d ts",
    "qc tc 7c 6c 4c",
    "ah ah 7c 6c 4c"
]
for (hand in hands) {
    System.print("%(hand): %(analyzeHand.call(hand))")
}
Output:
2h 2d 2c kc qd: three-of-a-kind
2h 5h 7d 8c 9s: high-card
ah 2d 3c 4c 5d: straight
2h 3h 2d 3c 3d: full-house
2h 7h 2d 3c 3d: two-pair
2h 7h 7d 7c 7s: four-of-a-kind
th jh qh kh ah: straight-flush
4h 4s ks 5d ts: one-pair
qc tc 7c 6c 4c: flush
ah ah 7c 6c 4c: invalid

Extra Credit Version

import "./dynamic" for Tuple
import "./sort" for Sort
import "./seq" for Lst

var Card = Tuple.create("Card", ["face", "suit"])

var cmp = Fn.new { |i, j| (i.face - j.face).sign }

var isStraight = Fn.new { |cards, jokers|
    var sorted = Sort.merge(cards, cmp)
    if (jokers == 0) {
        if (sorted[0].face + 4 == sorted[4].face) return true
        if (sorted[4].face == 14 && sorted[3].face == 5) return true 
        return false
    } else if (jokers == 1) {
        if (sorted[0].face + 3 == sorted[3].face) return true
        if (sorted[0].face + 4 == sorted[3].face) return true
        if (sorted[3].face == 14 && sorted[2].face == 4) return true 
        if (sorted[3].face == 14 && sorted[2].face == 5) return true
        return false
    } else {
        if (sorted[0].face + 2 == sorted[2].face) return true
        if (sorted[0].face + 3 == sorted[2].face) return true
        if (sorted[0].face + 4 == sorted[2].face) return true
        if (sorted[2].face == 14 && sorted[1].face == 3) return true 
        if (sorted[2].face == 14 && sorted[1].face == 4) return true
        if (sorted[2].face == 14 && sorted[1].face == 5) return true
        return false
    }
}

var isFlush = Fn.new { |cards|
    var sorted = Sort.merge(cards, cmp)
    var suit = sorted[0].suit
    if (sorted.skip(1).all { |card| card.suit == suit || card.suit == "j" }) return true
    return false
}

var analyzeHand = Fn.new { |hand|
    var split = Lst.distinct(hand.split(" ").where { |c| c != "" }.toList)
    if (split.count != 5) return "invalid"
    var cards = []
    var jokers = 0
    for (s in split) {
        if (s.bytes.count != 4) return "invalid"
        var cp = s.codePoints[0]
        var card =
             cp == 0x1f0a1 ? Card.new(14, "s") :
             cp == 0x1f0b1 ? Card.new(14, "h") :
             cp == 0x1f0c1 ? Card.new(14, "d") :
             cp == 0x1f0d1 ? Card.new(14, "c") :
             cp == 0x1f0cf ? Card.new(15, "j") : // black joker
             cp == 0x1f0df ? Card.new(16, "j") : // white joker
            (cp >= 0x1f0a2 && cp <= 0x1f0ab) ? Card.new(cp - 0x1f0a0, "s") :
            (cp >= 0x1f0ad && cp <= 0x1f0ae) ? Card.new(cp - 0x1f0a1, "s") :
            (cp >= 0x1f0b2 && cp <= 0x1f0bb) ? Card.new(cp - 0x1f0b0, "h") :
            (cp >= 0x1f0bd && cp <= 0x1f0be) ? Card.new(cp - 0x1f0b1, "h") :
            (cp >= 0x1f0c2 && cp <= 0x1f0cb) ? Card.new(cp - 0x1f0c0, "d") :
            (cp >= 0x1f0cd && cp <= 0x1f0ce) ? Card.new(cp - 0x1f0c1, "d") :
            (cp >= 0x1f0d2 && cp <= 0x1f0db) ? Card.new(cp - 0x1f0d0, "c") :
            (cp >= 0x1f0dd && cp <= 0x1f0de) ? Card.new(cp - 0x1f0d1, "c") :
                                               Card.new(0, "j") // invalid
        if (card.face == 0) return "invalid"
        if (card.suit == "j") jokers = jokers + 1
        cards.add(card)
    }
    var groups = Lst.groups(cards) { |card| card.face }
    if (groups.count == 2) {
        if (groups.any { |g| g[1].count == 4 }) {
            if (jokers == 0) return "four-of-a-kind"
            return "five-of-a-kind"
        }
        return "full-house"
    } else if (groups.count == 3) {
        if (groups.any { |g| g[1].count == 3 }) {
            if (jokers == 0) return "three-of-a-kind"
            if (jokers == 1) return "four-of-a-kind"
            return "five-of-a-kind"
        }
        return (jokers == 0) ? "two-pair" : "full-house"
    } else if (groups.count == 4) {
        if (jokers == 0) return "one-pair"
        if (jokers == 1) return "three-of-a-kind"
        return "four-of-a-kind"
    } else {
        var flush = isFlush.call(cards)
        var straight = isStraight.call(cards, jokers)
        if (flush && straight) return "straight-flush"
        if (flush)             return "flush"
        if (straight)          return "straight"
        return (jokers == 0) ? "high-card" : "one-pair"
    }
}

var hands = [
    "🃏 🃂 🂢 🂮 🃍",
    "🃏 🂵 🃇 🂨 🃉",
    "🃏 🃂 🂣 🂤 🂥",
    "🃏 🂳 🃂 🂣 🃃",
    "🃏 🂷 🃂 🂣 🃃",
    "🃏 🂷 🃇 🂧 🃗",
    "🃏 🂻 🂽 🂾 🂱",
    "🃏 🃔 🃞 🃅 🂪",
    "🃏 🃞 🃗 🃖 🃔",
    "🃏 🃂 🃟 🂤 🂥",
    "🃏 🃍 🃟 🂡 🂪",
    "🃏 🃍 🃟 🃁 🃊",
    "🃏 🃂 🂢 🃟 🃍",
    "🃏 🃂 🂢 🃍 🃍",
    "🃂 🃞 🃍 🃁 🃊"
]
for (hand in hands) {
    System.print("%(hand): %(analyzeHand.call(hand))")
}
Output:
🃏 🃂 🂢 🂮 🃍: three-of-a-kind
🃏 🂵 🃇 🂨 🃉: straight
🃏 🃂 🂣 🂤 🂥: straight
🃏 🂳 🃂 🂣 🃃: four-of-a-kind
🃏 🂷 🃂 🂣 🃃: three-of-a-kind
🃏 🂷 🃇 🂧 🃗: five-of-a-kind
🃏 🂻 🂽 🂾 🂱: straight-flush
🃏 🃔 🃞 🃅 🂪: one-pair
🃏 🃞 🃗 🃖 🃔: flush
🃏 🃂 🃟 🂤 🂥: straight
🃏 🃍 🃟 🂡 🂪: straight
🃏 🃍 🃟 🃁 🃊: straight-flush
🃏 🃂 🂢 🃟 🃍: four-of-a-kind
🃏 🃂 🂢 🃍 🃍: invalid
🃂 🃞 🃍 🃁 🃊: high-card