Simply Scheme Project – Scoring Poker Hands

The idea of this project is to invent a procedure `poker-value` that works like this:

Counting The Distribution of Cards Among Suits

`count-suits` takes the hand as an argument. It returns a sentence of numbers representing the number of cards in a suit, with the digits of the sentence representing spades, hearts, clubs, and diamonds, respectively

`count-suits` uses `count-suit` to achieve its output. `count-suit` takes a suit and a hand as input and returns the number of times that suit appears in the hand.

`get-flush-suit` takes the output of `count-suits` as an argument and returns the name of the suit if there is a flush, and an empty word otherwise.

`flush?` takes the output of `get-flush-suit` as an input, and returns true if it’s not empty and false otherwise.

Counting the Number of Times a Rank Appears in a Hand

`compute-ranks` takes the hand as an argument and outputs a sentence containing the ranks that appear in that hand in the format of a spelled-out number representing the number of appearances followed by the rank (see image for example). `compute-ranks` invokes two procedures to help do the job.

`compute-ranks-helper` takes the hand as an argument. Returns a sentence where the numbers in the sentence represent the number of times a hand rank appears in the hand, going from A to K. Uses an `every` and a `lambda` to go through the entire list of possible ranks.

`compute-rank` takes a given rank and a hand as arguments, and returns the number of times that rank appears in the hand.

`computed-ranks-to-sent`takes a sentence representing the number of times a card rank appears in a hand and a list of hand ranks as arguments. It returns a sentence containing the ranks that appear in the format of a spelled-out number representing the number of appearances followed by the rank (see image for example)

Recursively goes through the computed-ranks and list-of-ranks. The list of ranks being empty is the base case. Hand ranks that have 0 appearances in the hand are skipped over. Otherwise, `num-to-word` is invoked to turn the number of appearances into a word, and that is combined with the rank into a sentence, and the procedure is recursively called again.

`computed-ranks-names` takes a spelled-out-number representing the number of instances a rank appears in a hand and the results of running `compute-ranks` on a hand as arguments. Returns sentence containing the spelled-out-name of the rank or ranks that appear a given number of instances. This is a recursive procedure in order to be able to handle the case of having a hand with two pairs – in that case, cards of different ranks (fours and sixes, for example) appear twice in the hand.

I followed the SS book’s suggestion about how to format the output of `compute-ranks`. It’s important to keep in mind that the output of `computed-ranks-names` is the spelled out names of the rank you are searching for. So e.g. if you give it `'one` as the first argument, you’re looking for `one` in the hand. If `computed-ranks-names` finds `one 3` in the hand, as in the last example in the above image, the procedure will output `three`. It takes the `3` and turns it into a `three` using `card-rank-to-name`. Given that the initial data structure is a spelled out number representing instances followed by a numeric number representing hand rank, and we’re taking that and spelling out the rank for a given number of instances, it could get confusing, so I wanted to write this note about what’s going on.

`card-rank-to-name` converts a card rank to a spelled out name. pretty straightforward. I don’t think the need for converting A-J-K-Q straight from letters to words actually arises in my program, but I decided to include it when I wrote this cuz it seemed trivial to do so.

Finding Straights

`sort` takes a hand as input and returns a sentence consisting of the ranks converted to numbers and sorted from least to greatest. aces are represented by two numbers – 1 and 14 – in order to represent the two ways in which they might form a straight in a given hand.

Here’s `sort`, and its support procedures. `sort` is for sorting numbers from least to greatest. It is based on some work from an earlier chapter:

`convert-all-ranks` takes a sentence of hand ranks as input and returns a sentence of numbers. It converts any non-numeric ranks to numbers and converts aces to both 1 and 14.

`convert-rank` takes a card rank as input. Returns a number representing the rank if the rank is non-numeric and returns the same rank if it is numeric. Returns 1 and 14 for aces.

`hand-ranks` takes the hand as an argument and returns the ranks by calling butfirst on each card within the hand

`straight?` takes a sorted ranking of hands as input, and returns the values that represent the subset that constitutes a straight. The values that represent a straight are not necessarily the same as identical to the hand because of how I handle aces (i.e. by giving them two values).

If the number of value in the sorted ranking of hands drops below 5, the procedure returns false, as at that point the procedure will be unable to find five values in a row to form a straight.

If the subprocedure `five-in-a-row?` returns true, the `straight?` procedure will return the (true) value of the straight hand.

Otherwise, the `straight?` procedure is recursively called with all but the first value of the sorted ranks.

`five-in-a-row?` a sentence consisting of five card rankings and returns true if the five cards increase by 1 from the first card to the last (indicating a straight).

Royal Flushes and Full Houses

`broadway?` takes the sorted numerical hand ranks as an argument and checks whether the final five values in sorted-ranks are the ranks for 10-J-Q-K-A. Returns true if so or false otherwise. This is part of the test for finding a royal flush.

`full-house?` takes sentences containing the names of the ranks for which there are 3 cards and 2 cards present in the hand, respectively. If such ranks exist (i.e. if a full house exists within the hand), `full-house?` returns true. Otherwise, it returns false.

`highcard` takes a sentence with the hand ranks within a hand that represent a straight as an argument. Returns the spelled-out rank of the high card within the straight with “-high” appended.

The Main Procedure

`poker-value` takes a sentence consisting of individual cards with suit and rank information as an argument, and returns ranking information about the hand.

Defining and Using Terms

Numerous values are defined in two sets of `let` expressions.

The first `let`:

• `sorted-ranks` consists of a sentence consisting of words of card hand ranks, without any suit information The purpose of sorted-ranks is to enable the detection of straights.
• `suit-counts` consists of a sentence of numbers representing the number of cards in a suit, in the following order: spades, hearts, clubs, diamonds. The purpose of suit-counts is to enable the detection of flushes.
• `computed-ranks` is a sentence containing the ranks that appear in the format of a spelled-out number representing the number of appearances followed by the rank, e.g. `(three 2 two q)`for the result of `(compute-ranks '(h2 c2 s2 dq hq))`. The purpose of computed-ranks is to enable me to detect hand ranks that involve the presence of multiple cards of the same rank, such as full houses, x-of-a-kind, and pairs.

The second `let`:

• `flush-suit` is a word containing the name of the suit of a flush, if a flush is present for a given hand. if a flush is not present, flush-suit will be an empty word
• `4cards` is a sentence with the names of the hand rank within a hand for which there is four copies, if any.
• `3cards` is a sentence with the names of the hand rank within a hand for which there are three copies, if any.
• `2cards` is a sentence with the names of the hand ranks within a hand for which there are two copies, if any.
• `straighthand` is a sentence representing the hand ranks within a hand that represent a straight.

The structure of `poker-value` is a large `cond` with tests for particular hands arranged in order of the ranking of hands in poker.

• Royal flushes are tested for first, using a combination of `flush?` and `broadway?`.
• Straight flushes are tested for using a combination of `straight?` and `flush?`.
• Fours-of-a-kind are tested for using for by checking that the `count` of `4cards` is equal to 1.
• `full-house?` tests for full houses in a similar manner as fours-of-a-kind, but this time checking the count of `3cards` and `2cards`.
• Flushes and straights are tested for using the `flush?` and `straight?` procedures, respectively.
• threes-of-a-kind, two pairs, and pairs are tested for in a similar manner as to 4 of a kind, making the appropriate adjustments in terms of looking for the correct number of instances.

Helper Procedures

here are some general “helper” procedures I used. Note in particular `pf`, which comes up often and which is just a shorthand for calling plural on the first element of some input.

Tree & Closing Thoughts

While I have generally pasted full programs in one place in my blog post, for this one I’m going to link to my github for the full program since it is pretty large.

Here’s a partial tree of my program:

I’m skipping the “Extra Work for Hotshots” for now since I found the basic program challenging to write. One of the suggestions was to try scoring a 7 hand variant. I might try it later.

I don’t appear to have attempted the poker problem in previous Simply Scheme attempts. First time beating this level 🙂

I searched and only found one person who posted their poker hands project online, but it’s a 7 hands solution, and I don’t want to read it yet cuz I might want to try the 7 hands version later.

I thought this exercise was interesting. It caused me to mentally shift my focus a bit. Oftentimes I am just thinking about how to solve the problem in some “direct” way. With this exercise, I wound up thinking more in terms of “what format of data would be useful for solving the problem, and how do we generate the data in that format?” The book gave some guidance on that, but that was still “the hard part”. Once you had the data in the right format, actually finding the answers you want is pretty easy.

I first used the tree to help organize my program as I was writing it. I then translated my tree and program into this blog post. Both the tree-making stage and the writing stage led to me finding errors or problems which I then was able to correct. The step of writing out your thoughts explicitly helps error correct, and doing that in different formats helps you find different errors, since you notice different things. For example, the vertical structure of trees might help me figure out that the way i’m imagining data flowing through a process doesn’t make sense, while the linear format of writing might help me more with figuring out that I failed to update a procedure later in my program in light of an earlier change. So getting different angles and using different tools is useful.

There still may be errors cuz I didn’t really proofread.