Last active
August 25, 2020 18:16
-
-
Save noel-yap/b6ed2c6cb3825957679a3501443051b4 to your computer and use it in GitHub Desktop.
Poker hands: https://projecteuler.net/problem=54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
from collections import Counter | |
from enum import Enum | |
from functools import total_ordering | |
from typing import List, TextIO, Dict | |
@total_ordering | |
class Card: | |
face: str | |
suit: str | |
def __init__(self, card: str): | |
self.face = card[0] | |
self.suit = card[1] | |
def __eq__(self, other): | |
return self.faceValue() == other.faceValue() | |
def __ne__(self, other): | |
return not (self == other) | |
def __lt__(self, other): | |
return self.faceValue() < other.faceValue() | |
def __str__(self): | |
return self.face + { | |
'C': '♣', | |
'H': '♥', | |
'D': '♦', | |
'S': '♠' | |
}.get(self.suit) | |
def __repr__(self): | |
return str(self) | |
def faceValue(self) -> int: | |
return {f: i for i, f in enumerate('23456789TJQKA', 2)}[self.face] | |
class CategoryPredicates: | |
@staticmethod | |
def is_flush(hand) -> bool: | |
first_suit = hand.cards[0].suit | |
return all(first_suit == hand.cards[ordinal].suit for ordinal in range(1, 5)) | |
@staticmethod | |
def is_straight(hand) -> bool: | |
first_face_value = hand.cards[0].faceValue() | |
return all(hand.cards[ordinal].faceValue() == first_face_value + ordinal for ordinal in range(1, 5)) \ | |
or all(hand.cards[ordinal].faceValue() == ordinal + 2 for ordinal in range(0, 4)) \ | |
and hand.cards[4].faceValue() == 14 | |
@staticmethod | |
def is_four_of_a_kind(hand) -> bool: | |
return 4 in hand.face_count.values() | |
@staticmethod | |
def is_full_house(hand) -> bool: | |
return 3 in hand.face_count.values() and 2 in hand.face_count.values() | |
@staticmethod | |
def is_three_of_a_kind(hand): | |
return 3 in hand.face_count.values() and 1 in hand.face_count.values() | |
@staticmethod | |
def is_two_pair(hand): | |
return list(hand.face_count.values()).count(2) == 2 | |
@staticmethod | |
def is_one_pair(hand): | |
return list(hand.face_count.values()).count(2) == 1 and list(hand.face_count.values()).count(1) == 3 | |
@staticmethod | |
def is_high_card(hand): | |
return len(hand.face_count) == 5 | |
@total_ordering | |
class Category(Enum): | |
STRAIGHT_FLUSH = (9, lambda hand: CategoryPredicates.is_flush(hand) and CategoryPredicates.is_straight(hand)) | |
FOUR_OF_A_KIND = (8, CategoryPredicates.is_four_of_a_kind) | |
FULL_HOUSE = (7, CategoryPredicates.is_full_house) | |
FLUSH = (6, CategoryPredicates.is_flush) | |
STRAIGHT = (5, CategoryPredicates.is_straight) | |
THREE_OF_A_KIND = (4, CategoryPredicates.is_three_of_a_kind) | |
TWO_PAIR = (3, CategoryPredicates.is_two_pair) | |
ONE_PAIR = (2, CategoryPredicates.is_one_pair) | |
HIGH_CARD = (1, CategoryPredicates.is_high_card) | |
def __eq__(self, other): | |
return self.value[0] == other.value[0] | |
def __ne__(self, other): | |
return not (self == other) | |
def __lt__(self, other): | |
return self.value[0] < other.value[0] | |
@staticmethod | |
def category(hand): | |
for c in list(Category): | |
if c.value[1](hand): | |
return c | |
else: | |
raise ValueError | |
@total_ordering | |
class Hand: | |
cards: List[Card] | |
face_count: Dict[int, int] | |
reverse_face_count: Dict[int, List[int]] | |
def __init__(self, cards: List[Card]): | |
self.cards = sorted(cards) | |
self.face_count = Counter([c.faceValue() for c in cards]) | |
self.reverse_face_count = {} | |
for k, v in self.face_count.items(): | |
self.reverse_face_count[v] = sorted(self.reverse_face_count.get(v, []) + [k], reverse=True) | |
def __str__(self): | |
return str(self.cards) | |
def __repr__(self): | |
return str(self) | |
def __eq__(self, other): | |
return self.category() == other.category() \ | |
and all(self.highest_card(ordinal) == other.highest_card(ordinal) for ordinal in range(0, 5)) | |
def __ne__(self, other): | |
return not (self == other) | |
def __lt__(self, other): | |
if self.category() < other.category(): | |
return True | |
elif self.category() > other.category(): | |
return False | |
else: | |
for ordinal in range(5): | |
if self.highest_card(ordinal) < other.highest_card(ordinal): | |
return True | |
elif self.highest_card(ordinal) > other.highest_card(ordinal): | |
return False | |
else: | |
return False | |
def highest_card(self, ordinal: int): | |
keys = self.reverse_face_count.keys() | |
return self.reverse_face_count[sorted(keys, reverse=True)[min(ordinal, len(keys))]][0] | |
def category(self) -> Category: | |
return Category.category(self) | |
def read_cards(line: str) -> List[Card]: | |
return [Card(c) for c in line.split(' ')] | |
try: | |
poker_hands: TextIO = open("poker.txt") | |
lines = [line.rstrip() for line in poker_hands.readlines()] | |
cards = [read_cards(line) for line in lines] | |
hands = [[Hand(c[:5]), Hand(c[5:])] for c in cards] | |
# print(hands) | |
print(sum(hand[0] > hand[1] for hand in hands)) | |
finally: | |
poker_hands.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment