previndexinfo

code guessing, round #67 (completed)

started at ; stage 2 at ; ended at

specification

I'm all in! that's what she said, you know. let's calculate poker odds. submissions can be written in any language.

you can represent cards in any format. this specification will use the convention of rank followed by suit, both as single letters, 10 represented as T. (for example, AS is the ace of spades.)

poker1 has several types of 5-card "hands", which are ranked by how rare they are. you'll need to know about all of these, so quick overview GO! from highest to lowest:

aces are the rank above kings, unless registering as the rank below 2 would cause a straight to be formed. this means that AS KS QS JS TS and 5S 4S 3S 2S AS are both straight flushes.

within a category, hands are ranked by first grouping them by rank, ordering the groups by length then by value, and comparing lexicographically. id est:

suits are not ranked in any way, so 2 hands are able to rank exactly the same. that'll come up later!

now! we are playing texas hold 'em, which means that several players with 2 secret cards each are trying to form the best hand from a mix of their cards and 5 community cards. for instance, if the community cards are 2D 9S 9C 4H 5C, and Paul is holding 9H 3D, he has three of a kind (9S 9C 9H 5C 4H). Vanessa sitting next to him could have him beat if she holds a straight (like 6D 3C) or full house (like 9D 5H). the deck does not have duplicate cards, so she cannot have four of a kind.

not all of the community cards are revealed at once. on the television, the chance of each player winning (given that each player's hand is known) is displayed in real-time as the community cards are revealed. that is our task for today.

let's return to the example before. if the only visible cards are the first 3 community cards ("flop"), 2D 9S 9C, Paul has 9H 3D, and Vanessa has 6D 3C, the chance of Paul being the winner when the next 2 cards are revealed is 98.38%! when the four of hearts is revealed, that chance falls to 90.91%, and when the five of clubs is revealed, it becomes 0.0% (because Vanessa has now won). what a bad beat!

note that tying is not winning. if no cards are revealed, Paul has AC AH, and Vanessa has AS AD, the chance of either one winning (due to a flush) is only 2.17%. in most cases, the suits of the cards won't matter, and Paul and Vanessa will tie, sharing the pot.

your challenge, given a list of 2-card hands and face-up community cards (either 0, 3, 4, or 5 of them), is to calculate the odds of each hand winning. you may also accept a list of cards known not to be in the deck (perhaps because a player folded them), but this is not a requirement.

  1. specifically, the rules listed are for "high rules", as we are playing texas hold 'em. some things change under "low rules", where lower hands are considered better.

results

  1. 👑 jetison333 +2 -1 = 1
    1. LyricLy
    2. moshikoi
  2. moshikoi +2 -1 = 1
    1. LyricLy
    2. jetison333
  3. LyricLy +0 -2 = -2
    1. jetison333 (was moshikoi)
    2. moshikoi (was jetison333)

entries

you can download all the entries

entry #1

written by LyricLy
submitted at
0 likes

guesses
comments 0

post a comment


i_hate_myself.py ASCII text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import itertools
from collections import Counter, defaultdict

def find_straight(many):
    run_next = None
    have_ace = False
    for key, _ in itertools.groupby(many):
        if key == 14:
            have_ace = True
        if run_next == key:
            if run_length == 4 or run_length == 3 and key == 2 and have_ace:
                return run_next + run_length
        else:
            run_length = 0
        run_length += 1
        run_next = key - 1

# GOD THIS FLUMMERY IS SO TARNATION ANNOYING
def eval_hand(cards):
    cards.sort(reverse=True)
    ranks = [card[0] for card in cards]

    # flushes
    by_suit = defaultdict(list)
    for card in cards:
        suit = by_suit[card[1]]
        suit.append(card[0])
        if len(suit) == 5:
            # FLUSH!
            if straight := find_straight(suit):
                # STRAIGHT FLUSH!!!
                return [5], straight
            return [3, 1.75], suit

    # straights
    if straight := find_straight(ranks):
        return [3, 1.25], straight

    # everything else
    counts = []
    values = []
    s = 0
    for value, count in Counter(ranks).most_common():
        count = min(count, 5-s)
        if not count:
            break
        s += count
        counts.append(count)
        values.append(value)
    return counts, values

def entry(hands, commune, out_of_play=()):
    deck = set(itertools.product(range(2, 15), "HDCS"))
    deck.difference_update(out_of_play)
    deck.difference_update(commune)
    for hand in hands:
        deck.difference_update(hand)

    c = 0
    winners = Counter()
    for rest in itertools.combinations(deck, 5 - len(commune)):
        c += 1
        best_hand = None
        winner = None
        for idx, hand in enumerate(hands):
            us = eval_hand([*commune, *rest, *hand]) 
            if not best_hand or us > best_hand:
                best_hand = us
                winner = idx
            elif us == best_hand:
                winner = None
        if winner is not None:
            winners[winner] += 1

    return {x: y / c for x, y in winners.items()}

entry #2

written by moshikoi
submitted at
0 likes

guesses
comments 0

post a comment


i hardly know her.py ASCII text, with CRLF line terminators
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from __future__ import annotations
from dataclasses import dataclass
from enum import auto, Enum, IntEnum
from functools import cmp_to_key, total_ordering
from itertools import combinations, groupby
from operator import attrgetter, itemgetter
from collections import defaultdict
from collections.abc import Iterable

class Suit(Enum):
   Hearts = auto()
   Spades = auto()
   Diamonds = auto()
   Clubs = auto()

@dataclass(frozen=True)
class Card:
   suit: Suit
   rank: int

class HandType(IntEnum):
   HighCard = auto()
   Pair = auto()
   TwoPair = auto()
   ThreeOfAKind = auto()
   Straight = auto()
   Flush = auto()
   FullHouse = auto()
   FourOfAKind = auto()
   StraightFlush = auto()

class Hand:
   _type: HandType
   _cards: list[tuple[int, int]]

   @staticmethod
   def getHand(cards: Iterable[Card]) -> Hand:
      groups = sorted(
         ((g[0], len(list(g[1]))) for g in groupby(sorted(cards, key=attrgetter('rank')), key=attrgetter('rank'))),
         key=cmp_to_key(lambda a, b: a[1] - b[1] if a[1] != b[1] else a[0] - b[0]), reverse=True)

      match [g[1] for g in groups]:
         case [4, 1]:
            return Hand(HandType.FourOfAKind, groups)
         case [3, 2]:
            return Hand(HandType.FullHouse, groups)
         case [3, 1, 1]:
            return Hand(HandType.ThreeOfAKind, groups)
         case [2, 2, 1]:
            return Hand(HandType.TwoPair, groups)
         case [2, 1, 1, 1]:
            return Hand(HandType.Pair, groups)
         case _:
            flush = len(set(card.suit for card in cards)) == 1
            straight = (groups[1][0] == groups[0][0] - 1 and
               groups[2][0] == groups[0][0] - 2 and
               groups[3][0] == groups[0][0] - 3 and
               groups[4][0] == groups[0][0] - 4)

            if straight and flush:
               return Hand(HandType.StraightFlush, groups)
            elif straight:
               return Hand(HandType.Straight, groups)
            elif flush:
               return Hand(HandType.Flush, groups)
            else:
               return Hand(HandType.HighCard, groups)

   @staticmethod
   def getBestHand(cards: Iterable[Card]):
      ace14 = [Card(card.suit, 14) if card.rank == 1 else card for card in cards]

      return max(Hand.getHand(cards), Hand.getHand(ace14))
   
   def __init__(self: Hand, type: HandType, cards: list[list[Card]]):
      self._type = type
      self._cards = cards

   def __lt__(self: Hand, other: Hand):
      if (self._type != other._type):
         return self._type < other._type
      else:
         for i, j in zip(self._cards, other._cards):
            if i != j:
               return i < j


def bestHand(player_cards: list[Card], community_cards: list[Card]):
   return max(Hand.getBestHand(player_cards + list(community)) for community in combinations(community_cards, 3))

def entry(players: dict[str, list[Card]], community_cards: list[Card]):
   remaining_cards = []
   for suit in Suit:
      for rank in range(1, 14):
         card = Card(suit, rank)
         if card not in community_cards and not any(card in hand for hand in players.values()):
            remaining_cards.append(card)

   remaining_cards_count = 5 - len(community_cards)
   
   n = 0
   win_count = defaultdict(lambda: 0)
   for community_cards2 in combinations(remaining_cards, remaining_cards_count):
      n += 1
      hands = sorted(((player, bestHand(players[player], community_cards + list(community_cards2))) for player in players),
         key=itemgetter(1), reverse=True)
      if hands[0][1] > hands[1][1]:
         win_count[hands[0][0]] += 1

   return { p: win_count[p] / n for p in win_count }

def cardsFromStr(str: str) -> list[Card]:
   suit_map = { 'H': Suit.Hearts, 'S': Suit.Spades, 'D': Suit.Diamonds, 'C': Suit.Clubs }
   value_map = { 'K': 13, 'Q': 12, 'J': 11, 'T': 10, 'A': 1 }
   return list(Card(suit_map[s[1]], value_map[s[0]] if s[0] in value_map else int(s[0])) for s in str.split(' '))

if __name__ == '__main__':
   players = {
      'paul': cardsFromStr('9H 3D'),
      'vanessa': cardsFromStr('6D 3C')
   }

   print(entry(players, cardsFromStr('2D 9S 9C')))
   print(entry(players, cardsFromStr('2D 9S 9C 4H')))
   print(entry(players, cardsFromStr('2D 9S 9C 4H 5C')))

entry #3

written by jetison333
submitted at
0 likes

guesses
comments 0

post a comment


texasholdem.factor ASCII text
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
USING: accessors arrays assocs classes grouping hash-sets kernel lists
math math.combinatorics math.order prettyprint sequences
sequences.deep sorting strings vectors ;
IN: texasholdem

: char>string ( char -- str ) 1 swap <string> ;

: 2char>string ( a b -- string ) 2array >string ;


TUPLE: card rank suit ;

: <card> ( cardstr -- card ) [ first ] [ second ] bi card boa ;

: <cards> ( seq -- seq ) [ <card> ] map ;

: worst-hand ( -- seq ) { "2C" "4D" "5D" "6C" "7H" } <cards> ;

: print-card ( card -- ) [ rank>> ] [ suit>> ] bi 2char>string . ;

: =-rank ( card card -- ? ) rank>> swap rank>> = ;

: =-suit ( card card -- ? ) suit>> swap suit>> = ;

: rank>value ( card -- a ) rank>> char>string H{ { "2" 2 } { "3" 3 } { "4" 4 } { "5" 5 } { "6" 6 } { "7" 7 } { "8" 8 } { "9" 9 } { "T" 10 } { "J" 11 } { "Q" 12 } { "K" 13 } { "A" 14 } } at ;

M: card <=> [ rank>value ] bi@ <=> ;

: deck ( -- deck ) "23456789TJQKA" "SCDH" [ 2char>string ] cartesian-map flatten [ <card> ] map ;

: group-by-rank ( hand -- assoc ) H{ } clone swap [ dupd rank>value swap [ dup [ 1 + ] [ drop 1 ] if ] change-at ] each ;

: cmp-length-rank ( x y -- <=> ) 2dup [ second ] bi@ >=< dup +eq+ = [ drop [ first ] bi@ >=< ] [ 2nip ] if ;

: rank-ordered ( hand -- seq ) group-by-rank >alist [ cmp-length-rank ] sort-with ; 

: rank-ordered-rank ( hand -- seq ) rank-ordered [ first ] map ; 

: rank-ordered-num ( hand -- seq ) rank-ordered [ second ] map ;


: n-of-kind? ( hand n -- ? ) <combinations> [ [ =-rank ] monotonic? ] any? ;

: four-of-kind? ( hand -- ? ) 4 n-of-kind? ;

: three-of-kind? ( hand -- ? ) 3 n-of-kind? ;

: pair? ( hand -- ? ) 2 n-of-kind? ;

: full-house? ( hand -- ? ) rank-ordered-num [ first 3 = ] [ second 2 = ] bi and ;

: flush? ( hand -- ? ) [ =-suit ] monotonic? ;

: straight? ( hand -- ? ) sort [ rank>value swap rank>value swap - -1 = ] monotonic? ;

: straight-flush? ( hand -- ? ) [ straight? ] [ flush? ] bi and ;

: two-pair? ( hand -- ? ) <permutations> [ [ [ first ] [ second ] bi =-rank ] [ [ third ] [ fourth ] bi =-rank ] bi and ] any? ;



: keep-not-eq ( <=> <=> -- <=> ) swap dup +eq+ = not [ nip ] [ drop ] if ;

: cmp-same-hands ( hand hand -- <=> ) [ rank-ordered-rank ] bi@ [ <=> ] [ keep-not-eq ] 2map-reduce ;

: cmp-step ( hand hand ? ? quote -- <=> ) -rot 2dup and [ 3drop cmp-same-hands ] [ 2dup [ not ] bi@ and [ 2drop call( hand hand -- <=> ) ] [ [ 4drop ] dip [ +lt+ ] [ +gt+ ] if ] if ] if ;

: cmp-pair ( hand hand -- <=> ) 2dup [ pair? ] bi@ [ cmp-same-hands ] cmp-step ;

: cmp-two-pair ( hand hand -- <=> ) 2dup [ two-pair? ] bi@ [ cmp-pair ] cmp-step ;

: cmp-three-kind ( hand hand -- <=> ) 2dup [ three-of-kind? ] bi@ [ cmp-two-pair ] cmp-step ;

: cmp-straight ( hand hand -- <=> ) 2dup [ straight? ] bi@ [ cmp-three-kind ] cmp-step ;

: cmp-flush ( hand hand -- <=> ) 2dup [ flush? ] bi@ [ cmp-straight ] cmp-step ;

: cmp-full-house ( hand hand -- <=> ) 2dup [ full-house? ] bi@ [ cmp-flush ] cmp-step ;

: cmp-four-kind ( hand hand -- <=> ) 2dup [ four-of-kind? ] bi@ [ cmp-full-house ] cmp-step ;

: cmp-hands ( hand hand -- <=> ) 2dup [ straight-flush? ] bi@ [ cmp-four-kind ] cmp-step ;

: keep-best-hand ( hand hand -- hand ) 2dup cmp-hands +gt+ = [ drop ] [ nip ] if ;


: num-wins ( -- assoc ) H{ } ;

: clear-num-wins ( -- ) num-wins keys [ num-wins delete-at ] each ;

: best-com-hand ( com hand -- hand ) append 5 <combinations> dup first [ keep-best-hand ] reduce ;

: remove-seq ( seq seq -- seq ) [ swap remove ] each ;

: remove-used-cards ( com hands discard deck -- com hands deck ) swap remove-seq [ 2dup ] dip swap [ remove-seq ] each swap remove-seq ;

: ident ( -- ident ) "tie" worst-hand 2array ;

: add-wins ( hands besthands -- ) [ 2array ] 2map ident [ 2dup [ second ] bi@ cmp-hands dup +eq+ = [ 3drop ident ] [ +gt+ = [ drop ] [ nip ] if ] if ] reduce first num-wins [ dup [ 1 + ] [ drop 1 ] if ] change-at ;

: eval-hands ( com hands -- ) tuck [ over best-com-hand ] map nip add-wins ;

: extend ( com hands deck -- ) rot dup length 5 < [ over [ [ 3dup ] dip dup [ 1array append ] dip rot remove swapd extend ] each 3drop ] [ rot eval-hands drop ] if ; recursive

: total-wins ( -- x ) 0 num-wins keys [ num-wins at + ] each ;

: output-wins ( -- ) num-wins keys [ dup [ print-card ] each num-wins at total-wins / 100 * >float . ] each ;

: calculate-probability ( com hands discard -- ) deck remove-used-cards extend output-wins clear-num-wins ;