Design Of Computer Programs(1):A Poker Program

Time:2021-1-20

Source: Design of computer programs by Peter Norvig

This is a python implementation of the Texas Hold’em program, and the explanation is actually Peter Norvig
! However, the rules of poker really confused me… Make some notes here in order to straighten out the logic.

1、 Basic settings

  • Hand: hand. There are five playing cards in the number one card.
  • Rank & suit. Like one ♦ 5. Suit is a diamond and rank is 5.
  • Hand rank. There are several types of cards, ranging in size from large to small

    1. straight flush: flush. Shunzi of the same design and color.
    2. 4-kindThere are four cards in the hand with the same number of points.
    3. full house: three same points + two same points, such as 10.06.
    4. flushFive in the same suit.
    5. straightShunzi. For example, 5 6 7 8 9.
    6. 3-kindThree cards of the same number in the hand.
    7. two pairsTwo pairs of cards with the same number of points plus a scatter card.
    8. pairA pair of cards with the same number of points plus three scattered cards.
    9. high card: not in accordance with any of the above. The size is determined by the card with the largest number of points. The order of size is a K Q J 109 8 7 6 5 4 3 2.

2、 Procedure subject

  • The main procedures are as followspoker(hands:list)->handpokerYou can accept a hand list and return the largest hand.
  • hand_rank(hand)hand_rankYou can point out the type of hand. For example, for a pair of ♥ J ♠ J ♦ two ♣ two ♦ 5,hand_rankIt will be pointed out that this is a pairtwo pair

3、 Questions

(1) Q: the built-in functions of Python include andpokerAre functions similar in function?

A: Yes,maxmaxInkeyParameters allow the mapping of the subjects to be compared, such as usingabsChange the original value to absolute value. You can use it herepokerreturnmax(hands, key=hand_rank)Map the hand to the card type. thus,pokerThe logic of can be simply expressed as:

def poker(hands):
    "return the best hand: poker([hand,...]) => hand"
    return max(hands, key=hand_rank)

(2) How to represent a hand(hand)?

If you use a string to represent a card, the string list is good.["TC","9D","10D","JH","5C"]In the middle,"TC"It means Club 10,"JH"Represents Jack heart.

(3) Q: how to express the card type so that it can be usedmaxFunction comparison? For example, thestraight flushThe value is 8,4-kind7,high cardThe value is 0. Is that all right?

A: No way.4-kindHow to compare (10 10 7 and 9 9 6)? A tuple can be used to represent the card type. For example, 10, 10, 7 is (7,10,7) and 9, 9, 6 is (7,9,6). In Python, meta groups can also be compared with each other! The comparison method is to compare each element in turn. For example, (7,10,7) is greater than (7,9,6), because the first position is 10 > 9.
How to express other cards in a similar way?

  • straight flush“Straight flush, Jack high!” which means J is the biggest card in the flush. Know the biggest card, you can compare the size of flush. (8,11) can completely describe the hand.
  • 4-kindFour aces, and a queen killer.
  • full house“Full house, eight over kings!” (6,8,13) means three eights with two KS.
  • flush: this needs to list all the card points to compare the size. (5, [10,8,7,5,3]) denotes 10,8,7,5,3 hands of the same suit and number of points.
  • straight“Straight, Jack high!” this also only needs the hand with the largest number of points to completely compare the size – (4,11).
  • 3-kind(3, 7, [7, 7, 5, 2]) denotes 7, 7, 5, 2.
  • two pair(2, 11, 3, [13, 11, 11, 3, 3]) denotes 11, 11, 3, 3, 13.
  • pair(1, 2, [11, 6, 3, 2, 2]) denotes 2, 2, 3, 11, 6.
  • high card(0,7,5,4,3,2) denotes 7,5,4,3,2.

These rules are represented by codes

def hand_rank(hand):
    "Return a value indicating the ranking of a hand"
    ranks = card_ Ranks (hand) # ranks refers to all hand points
    if straight(ranks) and flush(hand):
        return (8, max(ranks))
    Elif kind (4, ranks): # that is, there are four cards with the same number of points in the hand
        return (7, kind(4, ranks), kind(1, ranks))
    elif kind(3, ranks) and kind(2, ranks):
        return (6, kind(3, ranks), kind(2, ranks))
    elif flush(hand):
        return (5, ranks)
    elif straight(ranks):
        return (4, max(ranks))
    elif two_pair(ranks):
        return (2, two_pair(ranks), ranks)
    elif kind(2, ranks):
        return (1, kind(2, ranks), ranks)
    else:
        return (0, ranks)

####(4) Q: how to realize itcard_ranksHow many points do you give the hand?
A:card_ranksGive the number of cards in the hand. In short, just extract the first character of the card string. For example:

def card_ranks(hand):
    ranks = [r for r,s in cards]
    ranks.sort(reverse=True)
   return ranks

But what about “AC”?
The initial idea is this: get a dictionary to store the value of tjqka.

def card_ranks(cards):
    "Return a list of the ranks, sorted with higher first."
    modifier_dict={
        'T':10,
        'J':11,
        'Q':12,
        'K':13,
        'A':14
    }
    def modifier(x):
        if x in modifier_dict.keys():
            return modifier_dict[x]
        else:
            return int(x)
    ranks = [r for r,s in cards]
    ranks = list(map(modifier, ranks))
    ranks.sort(reverse=True)
    return ranks            

It’s over. Then I also thought that this kind of “if you can find the key, return the corresponding value of the key, and if you can’t find it, return another value” is not very similardict.get()It should be changed to:

def modifier(x):
    modifier_dict={
        'T':10,
        'J':11,
        'Q':12,
        'K':13,
        'A':14
    }
    return modifier_dict.get(x, int(x))

Well, he can’t. There is no errorint('A')This kind of operation. Because whether you can find the key or notgetAll of them willintIt’s done. You’re smart.

Peter, however, used only one line:

def card_ranks(hand):
    "Return a list of the ranks, sorted with higher first."
    ranks = ['--23456789TJQKA'.index(r) for r,s in hand]
    ranks.sort(reverse=True)
    return ranks

Use the position in the list corresponding to the number of hand points, it is very beautiful.

(4) How to judge straight and flush?

In other words, the number of points is five consecutive integers. The more ingenious method is to judge that these five numbers are different from each other, and the maximum minus the minimum is 4.
max(ranks) - min(ranks) == 4 and len(set(ranks)) == 5
The same color, that is to say, only one element can be transformed into a set.
len(set(suit))==1

This set of rules can not only be used to judge the flush in poker, but also to judge the win or lose of Gobang: if the abscissa of five pieces is the same, and the ordinate is five continuous values, then it is a win.

(5) How to realize the kind (ranks, n) function and give n cards in the hand?

The easiest way is to traverse+ list.count ()。 I think it’s the same. I’ll use it collections.Counter Count it. But there is no need to count all the elements.

def kind(n, ranks):
    for r in ranks:
        if ranks.count(r) == n:
            return r
    return None

(6) How to realize two_ Pair, judge whether there are two pairs of cards in the hand, and return the points of the two groups of cards if there are?

My idea: first the opponent card with a kind (ranks, 2), get the return value of R0, and then pop up all the cards with the number of R0 from the list, and then the rest of the list with a kind (ranks, 2), get the return value of R1.
Peter’s idea: the opponent card with a kind (ranks, 2) get R0, the list in reverse order, and then a kind (ranks, 2) get R1, R0 if not equal to R1 is two_ pair。
Well, make full use of the characteristics of data and functions… The characteristic is that kind only returns the number of points with exactly N cards in the first hand, and the ranks are arranged in order.

(7) How to deal with the draw?

pokerFunction usesmaxTo compare the hand size, butmaxOnly the first maximum will be returned, which is unfair in the case of a draw. So we need to achieve our own goalsmaxIt can return all the maximum values. It’s not hard:

def allmax(iterable, key=None):
    result, maxval = [], None
    key = key or (lambda x: x)
    for x in iterable:
        xval = key(x)
        if not result or xval > maxval:
            result, maxval = [x], xval
        elif xval == maxval:
            result.append(x)
    return result

(8) How to deal?

We need to give each player a random number of N cards.
Complex writing:

def deal(numhands, n=5, deck=None):
    deck = copy(deck) or [r + s for r in '23456789TJQKA' for s in 'SHDC']
    hands = []
    for num in range(0, numhands):
        hand = []
        for _ in range(0, n):
            card = random.choice(deck)
            if deck:
                deck.remove(card)
                hand.append(card)
        hands.append(hand)
    return hands

Simple writing:

def deal(numhands, n=5, deck=None):
    deck = copy(deck) or [r + s for r in '23456789TJQKA' for s in 'SHDC']
    random.shuffle(deck)
    return [deck[n*i: n*(i+1)] for i in range(numhands)]

The following writing method is not only simple in the algorithm implementation, but also more in line with the situation in daily life: who will randomly choose when drawing cards? It must be that the whole card group will be disrupted first, and then each person will give n cards.

4、 Testing

Wikipedia gives the probability of each card type. Among them, the rarest flush probability is 0.0015%, and the most common miscellaneous card probability is 50.11%. We can get the frequency of all the cards by running the program many times. If the result is consistent with that of Wikipedia, then our program is correct. So how many times should we run the program?
Although the more times, the better, but in practice must consider the burden on the machine. The key is to let the rarest card also appear enough times to ensure the robustness of the results. We can expect to make the flush appear about 10 times, then the program should run 10 / 0.0015% times, that is about 700000 times.

5、 Refactoring

hand_rankFunction repeated used many times rank, made the taboo of self repetition: there are four cards with the same number? Oh, No. are there three cards with the same number? Oh, no… it’s better to know what cards you have in your hand and how many cards you have.

def group(items):
    groups = [(items.count(x), x) for x in set(items)]
    Return sorted (groups, reverse = true) # put the one with the largest number in the front. If the number is the same, put the one with the largest number in the front


def hand_rank(hand):
    groups = group(['--23456789TJQKA'.index(r) for r,s in hand])
    counts, ranks = unzip(groups)  #7 10 7 9 7 => (3, 1, 1), (7, 10, 9)
    if ranks == (14, 5, 4, 3, 2):
        Ranks = (5, 4, 3, 2, 1) # rules in special cases
    straight = (len(ranks) == 5) and (max(ranks) - min(ranks) == 4)
    flush = (len(set([s for r, s in hand])) == 1)
    return (9 if (5,) == counts else
            8 if straight and flush else
            7 if (4, 1) == counts else
            6 if (3, 2) == counts else
            5 if flush else
            4 if straight else
            3 if (3, 1, 1) == counts else
            2 if (2, 2, 1) == counts else
            1 if (2, 1, 1, 1) == counts else
            0), ranks

It’s not only more efficient, it’s also a clear demonstration of what Texas Hold’em is all about: it splits the number five in an orderly way.
5 = 4 + 1
   = 3 + 2
   = 3 + 1 + 1
   = 2 + 2 + 1
   = 2 + 1 + 1 + 1
   = 1 + 1 + 1 + 1 + 1。

Recommended Today

DK7 switch’s support for string

Before JDK7, switch can only support byte, short, char, int or their corresponding encapsulation classes and enum types. After JDK7, switch supports string type. In the switch statement, the value of the expression cannot be null, otherwise NullPointerException will be thrown at runtime. Null cannot be used in the case clause, otherwise compilation errors will […]