Edge cases in computer programs

August 23, 2010

Introduction

We programmers like when problems can be solved with a single, general formula and we generally dislike when we need to manually handle small edge cases. I want to talk about such an edge case that occurred in a little poker program I am writing.

Order please!

My poker program is a hand simulator: given two starting hands, it’ll compute their probabilities of winning at showdown (this is going to be pretty much the same thing as PokerStove). One of the thing I need to do in my program is determine what kind of hand a player has. There are 9 different types of hands in poker. Here they are in decreasing strength order:

  • Straight flush
  • Four of a kind
  • Full house
  • Flush
  • Straight
  • Three of a kind
  • Two pairs
  • One pair
  • High card

(Note: a royal flush is a kind of straight flush.)

Before a hand is properly classified, the cards are sorted to make the classification functions easier to code. The cards are first ordered by the number of cards of the same rank that appear in the hand and in decreasing order of strength. Here are some examples:

  • 5-5-5-T-T: The three fives come first because there’s three of them, then the tens
  • 9-9-3-3-6: We have two pairs, so we put the highest one first, the second pair next and the lonely card last
  • A-8-7-4-3: There are no two cards of the same rank here, so the cards are simply ordered by rank

The hand is then tested for all types of hands from strongest to weakest and the classification stops as soon as a match has been found. For example, to test for four of a kind we verify that card[0] = card[1] = card[2] = card[3] and later we can test for two pairs with card[0] = card[1] and card[2] = card[3] without having to test that card[2] and card[3] are different from card[0] and card[1].

This ordering also makes it easy to determine the winner when two hands have the same type: we just go from the first card to the last, testing which one is the higher card. If after the fifth card no card was higher, we have a tie. This works for all types of hands. Almost.

Why did they invent the wheel?

In poker terms, a wheel is a 5-high straight; the ace is considered both high and low, so A-2-3-4-5 is actually a straight. And this hand, which occurs less than 1% of the time, is the one that requires the most attention.

First, it makes testing for a straight harder. The function for testing a straight is easy with our ordering: starting at the second card, we test that the current card is exactly one rank lower than the card before. This work from A-K-Q-J-T down to 6-5-4-3-2. With a wheel however, we have A-5-4-3-2 and 5 is not one rank lower than an ace and a straight isn’t found. If no straight was found, we need to manually check if the hand is a wheel.

Second, if we used a simple card-by-card analysis to determine which of two straights (or straight flushes) is the higher one, the wheel would actually lose only to broadway (A-K-Q-J-T); it would (incorrectly) win against a straight such as 7-6-5-4-3, because the ace is higher than the 7, even though a 7-high straight beats a 5-high straight. To fix this problem, we must add extra code to our comparison function when we are dealing with straights and straight flushes:

  • If the two hands are wheel, we have a tie
  • If the first hand is a wheel, then the second hand is the winner
  • If the second hand is a wheel, then the first hand is the winner

If none of these tests pass, we can use our card-by-card analysis. (Hands that aren’t straights or straight flushes just skip this part, of course.)

The net result of the wheel is that my functions to test for straights and for comparing hands are twice as long as they would be if wheels were not considered straights. The little less-than-1% case causes the code of two functions to increase by 100%.


Follow

Get every new post delivered to your Inbox.