• Basic set solution by sixthgear
  • 5 years, 2 months ago
  • Download | Raw
# coding: utf-8
"""
poker.py by sixthgear.

A set of handy functions and utilities to implement poker games and calculators.
LICENSE: buy me a beer sometime.
"""

import sys
import itertools
import random
import collections

def deck():
    """
    A simple deck generator used to deal from a single deck of 52 cards.
    If the end of the deck is reached, a StopIteration exception will be thrown.
        
    # create the generator
    >>> d = deck() 
    
    # deal one card
    >>> d.next() 
    Card(value=6, suit=3)
     
    # deal a hand of 5 cards
    >>> hand_output([d.next() for card in range(5)])
    [Q♡] [3♡] [8♢] [9♢] [6♡]
    
    # deal a hand of 5 cards
    >>> hand = [card for i, card in zip(d, range(5))] 
    [Q♡] [3♡] [8♢] [9♢] [6♡]    
    """    
    cards = [Card(i,j) for j in range(4) for i in range(1,14)]
    random.shuffle(cards)
    for c in cards:
        yield c

Card = collections.namedtuple('Card', 'value suit')
Hand = collections.namedtuple('Hand', 'rank cards desc')
Symbol = collections.namedtuple('Symbol', 'symbol name')

VALUES = [
    Symbol('A', 'low ace'),
    Symbol('2', 'duece'),
    Symbol('3', 'three'),
    Symbol('4', 'four'),
    Symbol('5', 'five'),
    Symbol('6', 'six'),
    Symbol('7', 'seven'),
    Symbol('8', 'eight'),
    Symbol('9', 'nine'),
    Symbol('T', 'ten'),
    Symbol('J', 'jack'),
    Symbol('Q', 'queen'),
    Symbol('K', 'king'),
    Symbol('A', 'ace'),
]

SUITS = [
    Symbol(u'h', 'hearts'),
    Symbol(u'd', 'diamonds'),
    Symbol(u'c', 'clubs'),
    Symbol(u's', 'spades'),
]

HANDS = [
    (0x100000, 'royal flush'),
    (0x200000, 'straight flush'),
    (0x300000, 'quad %s'),
    (0x400000, 'full house - %s full of %s'),
    (0x500000, 'flush - %s'),
    (0x600000, 'straight - %s high'),
    (0x700000, 'trip %s'),
    (0x800000, 'two pair - %s and %s'),
    (0x900000, 'pair of %s'),
    (0xA00000, '%s high'),
]

def card_value_name(card, plural=True): 
    name = VALUES[card.value].name
    if not plural: 
        return name
    if card_value_sym(card) == '6': 
        return name + 'es'
    return name + 's'
        
def card_suit_name(card): return SUITS[card.suit].name
def card_value_sym(card): return VALUES[card.value].symbol
def card_suit_sym(card): return SUITS[card.suit].symbol
def card_output(card): return '[' + card_value_sym(card) + card_suit_sym(card) + ']'
def hand_output(hand, total=0):
    cards = map(card_output, hand)
    if len(cards) < total:
        cards += ['[  ]'] * (total-len(cards))
    return str.join(' ', cards)
    
def card_from_str(str):
    """
    Creates a card from a string like "Ah" or "5c"
    """
    value = [i+1 for i, v in enumerate(VALUES[1:]) if str[0].upper() == v[0]]
    suit = [i for i, s in enumerate(SUITS) if str[1].lower() == s[1][0]]
    if value and suit:
        return Card(value[0], suit[0])
    else:
        return None

        
def chk_straight(cards):
    """
    check a list of cards for straight sequences.
    """
    # might be redundant, but left for safety.
    cards.sort(key=lambda c: c.value, reverse=True)
    
    # add low aces for straights
    for c in [c for c in cards if c.value == 13]:
        low = Card(0, c.suit)
        cards.append(low)
        
    # straight detection variables
    straight_counter = 1
    straight = []    
    last = cards[0].value
    
    for c in cards:        
        delta = last - c.value
        last = c.value
        if delta == 0:
            straight.append(c)
        elif delta == 1:
            straight_counter += 1
            straight.append(c)
        elif delta > 1 and straight_counter < 5:
            straight_counter = 1
            straight = [c]
    
    if straight_counter >= 5:
        return straight 
    else:
        return []
    
    
def hand_build(cards):
    """
    Find the best possible poker hand from a list of cards and return it along
    with a rank and description. Returns a named tuple called Hand with the
    following properties:
    
    cards:  a list of the 5 cards that compose the best possible poker hand.
    rank:   an integer that can be used to sort or compare any hand returned by
            this function.
    desc:   a plain-language description of the hand. 
            Eg. "full house - threes full of aces"
        
    HAND SELECTION
    
    The hand is built by following a number of steps.
    
    1.  sort the cards in reverse rank order. (A high, 2 low).
    
    2.  build a dict of groupings of either rank or suit.
    
    3.  find rank groupings of 2 or more cards. These represent pairs, sets and 
        quads.
        
    4.  find suit groupings of 5 or more cards. There should realistically only
        be one of these if a 7 card (hold 'em) hand is passed in, but it may
        be larger than five cards. This represents a flush.
        
    5.  run a straight check algorithm. the chk_straight function takes a list
        of cards and iterates through them checking if the difference in value
        between any two cards is 1 or 0. If a decending sequence of 5 or more is
        found, it is returned (along with any pairs that might exist within that
        sequence, since we don't yet know if a straight flush is possible).
        
    6.  if there is both a straight AND a flush, rerun chk_straight on the flush
        cards to see if the two groups intersect. If so, we have a straight 
        flush (or royal if the high card is an ace.) If this intersection
        suceeds, there should be no more rank duplicates (since the flush check 
        would have removed any pairs that don't match suit).
        
    7.  Now we that all tests have been run, we just go through the list
        of possible hands from high to low, and inesert the required amount
        of cards that we need into the final hand that we need.
        
    8.  Now if our hand is less than 5 cards longs, we loop through the original
        (sorted) list of cards and insert them into the hand as kickers (if they
        aren't already being used in the hand). This assures the best possible 
        5-card hand is created with the highest kickers available.
        
    9.  Finally, we can run our rank calculation.
            
    RANK CALCULATION
    
    The rank is calculated by packing 6 integers into a 6-digit hexadecimal int.
    the most significant digit represents the rank of the hand, with royal flush 
    being 1 (0x100000), and high pair being 10 (0xA00000). 
    
    The remiainging 5 digits represent the number values (1 to 13) of each card 
    in the hand. For this to give an accurate ranking, the hand MUST be sorted 
    by card significance (use of a card to make up a hand) and then by card 
    value. In other words, the cards that make up the hand always come before any
    kickers. So a two-pair hand with aces and fives, along with a 9 kicker would
    be sorted AA559. That hand would rank 0x800884. 
    """
    
    # this wont work without at least five
    assert len(cards) >= 5
    
    # this is what we'll build for output, should be 5 cards
    hand = []    
    # order high to low to find patterns easier
    cards.sort(key=lambda c: c.value, reverse=True)
    
    # dicts for groupings (sets, flushes)
    vgroups = collections.defaultdict(list)
    sgroups = collections.defaultdict(list)    
        
    for c in cards:
        # build a hashmap of value groups and suit groups
        vgroups[c.value].append(c)        
        sgroups[c.suit].append(c)                

    # sort this so we grab the highest groups first. OrderDicts would make this
    # nicer. TODO: switch to 2.7.
    vgroup_values = sorted(vgroups.values(), key=lambda g: g[0].value, reverse=True)    
    # calculate groupings    
    pairs = [g for g in vgroup_values if len(g) == 2]
    trips = [g for g in vgroup_values if len(g) == 3]
    quads = [g for g in vgroup_values if len(g) == 4]
    flush = [g for g in sgroups.values() if len(g) >= 5]
    
    if len(trips) > 1 or len(trips) > 0 and len(pairs) > 0:
        full_house = trips[0]
        full_house += sorted(trips[1:] + pairs, key=lambda x: x[0].value, reverse=True)[0][:2]
    else:
        full_house = []

    two_pair = len(pairs) >= 2            
    # check for straights
    straight = chk_straight(cards)
    straight_flush = []
    royal_flush = []
            
    # straight flush detection by taking an intersection between the set of flush
    # cards and the set of straight cards. Any resultant set >= 5 cards is our 
    # straight flush.
    if straight and flush:        
        # intersection = list(set(straight) & set(flush[0]))
        straight_flush = chk_straight(flush[0]) # intersection
    
    # royal flush detection
    if straight_flush and straight_flush[0].value == 13:
        royal_flush = straight_flush
    
    # finally return each hand if it exists    
    if royal_flush:
        hand += royal_flush[:5]
        rank, desc = HANDS[0]
        
    elif straight_flush:
        hand += straight_flush[:5]
        rank, desc = HANDS[1]
        
    elif quads:        
        hand += quads[0]
        rank, desc = HANDS[2][0], HANDS[2][1] % card_value_name(quads[0][0])
        
    elif full_house:
        hand += full_house[:5]
        rank, desc = HANDS[3][0], HANDS[3][1] % \
            (card_value_name(full_house[0]), card_value_name(full_house[3]))
            
    elif flush:
        hand += flush[0][:5]
        rank, desc = HANDS[4][0], HANDS[4][1] % card_suit_name(flush[0][0])
        
    elif straight:
        for i, c in enumerate(straight):
            if i == 0 or c.value != straight[i-1].value:
                hand.append(c)
            if len(hand) == 5: 
                break            
        rank, desc = HANDS[5][0], HANDS[5][1] % card_value_name(straight[0], False)
            
    elif trips:
        hand += trips[0]
        rank, desc = HANDS[6][0], HANDS[6][1] % card_value_name(trips[0][0])
        
    elif two_pair:
        hand += pairs[0] + pairs[1]
        rank, desc = HANDS[7][0], HANDS[7][1] % \
            (card_value_name(pairs[0][0]),card_value_name(pairs[1][0]))
            
    elif pairs:
        hand += pairs[0]
        rank, desc = HANDS[8][0], HANDS[8][1] % card_value_name(pairs[0][0])
        
    else:    
        hand += hand[:5]
        rank, desc = HANDS[9][0], HANDS[9][1] % card_value_name(cards[0], False)
        
    # add highcards to the end to make five.    
    while len(hand) < 5:
        card = cards.pop(0)
        if card not in hand:             
            hand.append(card)            
    
    # calculate rank for each card
    for i, c in enumerate(hand):
        rank |= (13 - c.value) << (4 * (4-i))
        
    return Hand(rank, hand, desc)
    
    
    
def hand_build_omaha(hole, board):
    """
    Analyze and rank a 9-card OMAHA hand. Omaha hands have the tricky requirement
    that they be built using EXACTLY two of the four hole cards, and three of
    the community cards from the board.
    
    Given this requirement, to support omaha hands, we wrap our hand_build 
    function with one that creates all 60 possible 5-card hands from a list of 
    holecards and board cards. (6 possible combinations of hole cards x 10
    possible combinations of board cards)
    
    Each 5-card hand is then fed through the ranker, and then the best one is 
    selected and returned.
    """    
    
    assert len(hole) == 4
    assert len(board) == 5
    
    best = Hand([], 0xFFFFFF, '')
    
    # 6 combinations of hole cards
    hole_hands = [list(c) for c in itertools.combinations(hole, 2)]     
    # 10 combinations of house cards
    board_hands = [list(c) for c in itertools.combinations(board, 3)] 
    # and total possible hands is the product of both sets
    hands = (hand_build(a+b) for a, b in itertools.product(hole_hands, board_hands))
    
    # test each hand, find the lowest rank.
    for h in hands:
        if h.rank < best.rank: best = h
    
    # done.
    return best


if __name__ == '__main__':
 
    nh = None
    np = None
    board = None
    hands = []
    for line in sys.stdin.readlines():
        if nh == None:
            nh = int(line)
            continue
        elif np == None:
            np = int(line)
            continue
        elif board == None:
            board = [card_from_str(x) for x in line.split()]            
            continue
        else:
            hole = [card_from_str(x) for x in line.split()]
            if len(hole) == 4:
                hand = hand_build_omaha(hole, board)
            else:                
                hand = hand_build(hole+board)
            hands.append(hand)
            if len(hands) == np:                
                top = sorted(enumerate(hands), key=lambda x: x[1].rank)[0][1].rank                
                top_hands = filter(lambda x: x[1].rank == top, enumerate(hands))
                print str.join(' ', [str(h[0]) for h in top_hands])                
                np = None
                board = None
                hands = []

Scoreboard

lzm bas adv bon 200
sirpengi bas adv bon 200
sixthgear bas adv bon 190
mserrano bas adv bon 190
robbinsr bas adv bon 190
synx bas adv bon 50

What do?

  1. Write your program according to the problem description
  2. Download the basic input set. The timer will start.
  3. Feed the input file to your program and save the output.
  4. Upload the output file along with your source code. If it is correct, you will receive points. If not you may try again until the timer expires.
  5. If the timer expires. You may try again, but a new input file will be generated.
  6. Repeat fot each set.

Note: The input sets use unix line endings (\n) and the verifier expects them as well.


Come Chat!

Join channel ##proggit on freenode.

Bugs

Having issues with the the site? Let us know.