DEV Community

dev.to staff
dev.to staff

Posted on

6 4

Daily Challenge #258 - Ranking Poker Hands

A famous casino is suddenly faced with a sharp decline of their revenues. They decide to offer Texas hold'em also online. Can you help them by writing an algorithm that can rank poker hands?

Create a poker hand that has a method to compare itself to another poker hand:
PokerHand.prototype.compareWith = function(hand){...};

A poker hand has a constructor that accepts a string containing 5 cards:
var hand = new PokerHand("KS 2H 5C JD TD");

The characteristics of the string of cards are:

  • Each card consists of two characters, where
  • The first character is the value of the card: 2, 3, 4, 5, 6, 7, 8, 9, T(en), J(ack), Q(ueen), K(ing), A(ce)
  • The second character represents the suit: S(pades), H(earts), D(iamonds), C(lubs)
  • A space is used as card separator between cards

The result of your poker hand compare can be one of these 3 options:

var Result = 
{
    "win": 1,
    "loss": 2,
    "tie": 3
}

Notes

  • Apply the Texas Hold'em rules for ranking the cards.
  • Low aces are NOT valid in this challenge.
  • There is no ranking for the suits.

Examples
("2H 3H 4H 5H 6H", "AS AD AC AH JD") => WIN "Straight flush wins of 4 of a kind"
("2H 3H 4H 5H 6H", "KS AS TS QS JS") => LOSS "Highest straight flush wins"

Tests
("2H 3H 5H 6H 7H", "2S 3H 4H 5S 6C")
("2S 3H 4H 5S 6C", "3D 4C 5H 6H 2S")
("2S AH 4H 5S KC", "AH AC 5H 6H 7S")

Good luck!


This challenge comes from FrankK on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (6)

Collapse
 
sam_ferree profile image
Sam Ferree • Edited

Sad this is a bit too difficult for me to do with Befunge, even Funge++, but I did think it could work quite well if done functionally.

This C# solution seems to work, have not tested beyond the initial to test cases

Edit: Did not add HighCard rules....

using System;
using System.Collections.Generic;
using System.Linq;
using HandRule = System.Func<string[], bool>;
namespace ConsoleSandbox
{
  public class PokerRanker
  {
    public static List<char> Ranks = new List<char> { '2', '3', '4', '5', '6', '7', '8',
      '9', 'T', 'J', 'Q', 'K', 'A' };
    public static List<char> Suits = new List<char> { 'S', 'H', 'D', 'C' };

    public List<HandRule> Rules = new List<HandRule>();

    public PokerRanker()
    {
      var ranksRanked = new List<char>(Ranks);
      ranksRanked.Reverse();

      char[] invalidStraightStarts = { 'J', 'Q', 'K', 'A' };

      Rules.AddRange(ranksRanked.Except(invalidStraightStarts).Select(rank =>
        StraightFlush(rank)));

      Rules.AddRange(ranksRanked.SelectMany(fourRank =>
        ranksRanked.Except(new[] {fourRank}).Select(highCard =>
          FourOfAKind(fourRank, highCard))));

      Rules.Add(Flush());

      Rules.AddRange(ranksRanked.Except(invalidStraightStarts).Select(rank =>
        Straight(rank)));

      Rules.AddRange(ranksRanked.SelectMany(threeRank =>
        ranksRanked.Except(new[] { threeRank }).Select(highCard =>
        ThreeOfAKind(threeRank, highCard))));

      Rules.AddRange(ranksRanked.SelectMany(pairRank =>
        ranksRanked.Except(new[] { pairRank }).Select(highCard =>
        Pair(pairRank, highCard))));

      Rules.AddRange(ranksRanked.Select(rank =>
        HighCard(rank)));
    }

    public string Rank(string[] myHand, string[] theirHand)
    {
      var mine = Rules.FindIndex(rule => rule(myHand));
      var theirs = Rules.FindIndex(rule => rule(theirHand));
      return mine < theirs
        ? "Win!"
        : theirs < mine
          ? "Lose!"
          : "Draw!";
    }

    public HandRule HighCard(char rank) => hand =>
      hand.Any(card => card[0] == rank);

    public HandRule Pair(char pairRank, char highCard) => hand =>
      hand.Count(card => card[0] == pairRank) == 2
      && HighCard(highCard)(hand);

    public HandRule ThreeOfAKind(char threeRank, char highCard) => hand =>
      hand.Count(card => card[0] == threeRank) == 3
      && HighCard(highCard)(hand);

    public HandRule Straight(char startingRank) => hand =>
    {
      var start = Ranks.IndexOf(startingRank);
      var ranks = Ranks.GetRange(start, 5);
      return ranks.All(rank => hand.Any(card => card[0] == rank));
    };

    public HandRule Flush() => hand =>
      hand.All(card => card[1] == hand[0][1]);

    public HandRule FullHouse(char threeRank, char pairRank) => hand =>
      ThreeOfAKind(threeRank, threeRank)(hand) &&
      Pair(pairRank, pairRank)(hand);

    public HandRule FourOfAKind(char fourRank, char highCard) => hand =>
      hand.Count(card => card[0] == fourRank) == 4
      && hand.Any(card => card[0] == highCard);

    public HandRule StraightFlush(char startingRank) => hand =>
      Straight(startingRank)(hand)
      && Flush()(hand);
  }
}
Collapse
 
sam_ferree profile image
Sam Ferree

I think I also missed flush with high card... hrmm

Collapse
 
mellen profile image
Matt Ellen-Tsivintzeli • Edited

Created a python version

link to gist if embed isn't working

use it like:

python rank.py AS AC 2H 2C KD JC JD QH JH AS
VALUE_ORDER = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']
class PokerHand:
def __init__(self, cards):
self.cards = []
self.rankName = ''
self.original = cards
for card_str in cards:
self.cards.append(Card(card_str.upper()))
self.highest = self.cards[0]
self.multiHigh = None
self.lowPair = None
self.cards.sort()
self.highest = self.cards[-1]
def __str__(self):
return ' '.join(self.original)
def isRoyalFlush(self):
if self.isFlush():
return ''.join([card.value for card in self.cards]) == 'TJQKA'
else:
return False
def isStraightFlush(self):
if self.isFlush():
return self.isStraight()
else:
return False
def isFourOfAKind(self):
isTrue, value = self.isNOfAKind(4)
self.multiHigh = value
return isTrue
def isFullHouse(self):
last_card = self.cards[0]
triple = False
pair = False
count = 1
for card in self.cards[1:]:
if card.value == last_card.value:
count += 1
else:
if count == 2:
pair = True
elif count == 3:
triple = True
self.multiHigh = last_card
count = 1
last_card = card
if count == 2:
pair = True
elif count == 3:
triple = True
self.multiHigh = last_card
return triple and pair
def isStraight(self):
last_index = VALUE_ORDER.index(self.cards[0].value)
for card in self.cards[1:]:
index = VALUE_ORDER.index(card.value)
if index != last_index + 1:
return False
last_index = index
return True
def isFlush(self):
suit = self.cards[0].suit
for card in self.cards[1:]:
if card.suit != suit:
return False
return True
def isThreeOfAKind(self):
isTrue, value = self.isNOfAKind(3)
self.multiHigh = value
return isTrue
def isTwoPair(self):
pair1 = False
pair2 = False
last_card = self.cards[0]
count = 1
for card in self.cards[1:]:
if card.value == last_card.value:
count += 1
else:
if count == 2 and not pair1:
pair1 = True
self.multiHigh = last_card
elif count == 2 and pair1:
if last_card > self.multiHigh:
self.lowPair = self.multiHigh
self.multiHigh = last_card
else:
self.lowPair = last_card
pair2 = True
count = 1
last_card = card
if count == 2 and pair1:
if last_card > self.multiHigh:
self.lowPair = self.multiHigh
self.multiHigh = last_card
else:
self.lowPair = last_card
pair2 = True
return pair1 and pair2
def isAPair(self):
isTrue, value = self.isNOfAKind(2)
self.multiHigh = value
return isTrue
def isNOfAKind(self, n):
count = 1
high_count = 1
last_card = self.cards[0]
value = last_card
for card in self.cards[1:]:
if card.value == last_card.value:
count += 1
value = last_card
else:
if count > high_count:
high_count = count
count = 1
last_card = card
if count > high_count:
high_count = count
return (high_count == n, value)
def getRank(self):
rank = 10
if self.isRoyalFlush():
self.rankName = 'Royal Flush'
rank = 1
elif self.isStraightFlush():
self.rankName = 'Straight Flush'
rank = 2
elif self.isFourOfAKind():
self.rankName = 'Four of a Kind'
rank = 3
elif self.isFullHouse():
self.rankName = 'Full House'
rank = 4
elif self.isFlush():
self.rankName = 'Flush'
rank = 5
elif self.isStraight():
self.rankName = 'Straight'
rank = 6
elif self.isThreeOfAKind():
self.rankName = 'Three of a Kind'
rank = 7
elif self.isTwoPair():
self.rankName = 'Two Pair'
rank = 8
elif self.isAPair():
self.rankName = 'Pair'
rank = 9
if rank > 1 and rank < 10:
self.rankName = f'{self.highest.value} High {self.rankName}'
elif rank == 10:
self.rankName = f'{self.highest.value} High'
return rank
def compareWith(self, other):
leftRank = self.getRank()
rightRank = other.getRank()
if leftRank < rightRank:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif rightRank < leftRank:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
if leftRank == 1:
return f'{self} ({self.rankName}) draws with {other} ({other.rankName})'
elif leftRank == 2 or leftRank == 5 or leftRank == 6 or leftRank == 10:
if self.highest > other.highest:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.highest < other.highest:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
return f'{self} ({self.rankName}) draws with {other} ({other.rankName})'
elif leftRank == 3 or leftRank == 4 or leftRank == 7 or leftRank == 9:
if self.multiHigh > other.multiHigh:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.multiHigh < other.multiHigh:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
elif leftRank == 9:
if self.highest > other.highest:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.highest < other.highest:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
return f'{self} ({self.rankName}) draws with {other} ({other.rankName})'
else:
return f'{self} ({self.rankName}) draws with {other} ({other.rankName})'
elif leftRank == 8:
if self.multiHigh > other.multiHigh:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.multiHigh < other.multiHigh:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
if self.lowPair > other.lowPair:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.lowPair < other.lowPair:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
if self.highest > other.highest:
return f'{self} ({self.rankName}) beats {other} ({other.rankName})'
elif self.highest < other.highest:
return f'{self} ({self.rankName}) loses to {other} ({other.rankName})'
else:
return f'{self} ({self.rankName}) draws with {other} ({other.rankName})'
class Card:
def __init__(self, card_str):
self.value, self.suit = list(card_str)
def __lt__(self, other):
return VALUE_ORDER.index(self.value) < VALUE_ORDER.index(other.value)
def __gt__(self, other):
return VALUE_ORDER.index(self.value) > VALUE_ORDER.index(other.value)
def __eq__(self, other):
return VALUE_ORDER.index(self.value) == VALUE_ORDER.index(other.value)
def __le__(self, other):
return VALUE_ORDER.index(self.value) <= VALUE_ORDER.index(other.value)
def __ge__(self, other):
return VALUE_ORDER.index(self.value) >= VALUE_ORDER.index(other.value)
def __ne__(self, other):
return VALUE_ORDER.index(self.value) != VALUE_ORDER.index(other.value)
view raw poker.py hosted with ❤ by GitHub
import argparse
from poker import PokerHand
parser = argparse.ArgumentParser(description='rank two poker hands')
parser.add_argument('lefthand', nargs=5, help='The cards in the left hand')
parser.add_argument('righthand', nargs=5, help='The cards in the right hand')
def rank(lefthand, righthand):
LH = PokerHand(lefthand)
RH = PokerHand(righthand)
print(LH.compareWith(RH))
if __name__ == '__main__':
args = parser.parse_args()
rank(args.lefthand, args.righthand)
view raw rank.py hosted with ❤ by GitHub
Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

function PokerHand(hand) {
    this.hand = hand
}
PokerHand.prototype.compareWith = function(hand) {
    return compareHands(this.hand, hand.hand || hand)
}

const order = "23456789TJQKA"
function getHandDetails(hand) {
    const cards = hand.split(" ")
    const faces = cards.map(a => String.fromCharCode([77 - order.indexOf(a[0])])).sort()
    const suits = cards.map(a => a[1]).sort()
    const counts = faces.reduce(count, {})
    const duplicates = Object.values(counts).reduce(count, {})
    const flush = suits[0] === suits[4]
    const first = faces[0].charCodeAt(0)
    //Also handle low straight
    const lowStraight = faces.join("") === "AJKLM"
    const straight = lowStraight || faces.every((f, index) => f.charCodeAt(0) - first === index)
    let rank =
        (flush && straight && 1) ||
        (duplicates[4] && 2) ||
        (duplicates[3] && duplicates[2] && 3) ||
        (flush && 4) ||
        (straight && 5) ||
        (duplicates[3] && 6) ||
        (duplicates[2] > 1 && 7) ||
        (duplicates[2] && 8) ||
        9

    return { rank, value: faces.sort(byCountFirst).join("") }

    function byCountFirst(a, b) {
        //Counts are in reverse order - bigger is better
        const countDiff = counts[b] - counts[a]
        if (countDiff) return countDiff // If counts don't match return
        if (lowStraight) {
            a = a === "A" ? "N" : a
            b = b === "A" ? "N" : b
        }
        return b > a ? -1 : b === a ? 0 : 1
    }
    function count(c, a) {
        c[a] = (c[a] || 0) + 1
        return c
    }
}

function compareHands(h1, h2) {
    let d1 = getHandDetails(h1)
    let d2 = getHandDetails(h2)
    if (d1.rank === d2.rank) {
        if (d1.value < d2.value) {
            return "WIN"
        } else if (d1.value > d2.value) {
            return "LOSE"
        } else {
            return "DRAW"
        }
    }
    return d1.rank < d2.rank ? "WIN" : "LOSE"
}

Collapse
 
miketalbot profile image
Mike Talbot ⭐
Collapse
 
bravemaster619 profile image
bravemaster619

I once implemented with Node.js. It was a little bit complicated as I remember. I think there is no quick and easy solution.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️