% P-P-P-Pokerface
https://github.com/iloveponies/p-p-p-pokerface
Here are the instructions if you need them. Be sure to fork the repository behind the link above.
A traditional playing card has a rank and a suit. The rank is a number from 2 to 10, J, Q, K or A and the suit is Clubs, Diamonds, Hearts or Spades.
We want a simple way to represent poker hands and cards. A card is simply
going to be a string of the form "5C"
where the first character represents
the rank and the second character represents the suit. To keep the
representation at 2 characters, we'll use the following coding for values
between 10 and 14:
Rank Character
10 T 11 J 12 Q 13 K 14 A
So, for example, the Queen of Hearts is "QH"
and the Ace of Spades is
"AS"
.
We'll want a couple of helper functions to read the rank and suit of a card.
A useful thing to note is that Strings are sequencable, so you can use sequence destructuring on them:
(let [[fst snd] "5H"]
[fst snd]) ;=> [\5 \H]
If you are only interested in some destructured values, it is idiomatic to use
the name _
for ignored values:
(let [[_ snd] "AH"]
snd) ;=> \H
And finally, remember that you can use (str value)
to turn anything into its
string representation, including characters.
(str \C) ;=> "C"
You should now be able to write the (suit card)
function that returns the
suit of a card.
(suit "2H") ;=> "H"
(suit "2D") ;=> "D"
(suit "2C") ;=> "C"
(suit "3S") ;=> "S"
To get the rank, you'll need to convert a character into an integer. To see if
a character is a digit, like \5
or \2
, you can use (Character/isDigit char)
:
(Character/isDigit \5) ;=> true
(Character/isDigit \A) ;=> false
If a character is a digit, you can use (Integer/valueOf string)
to convert
it to an integer. You will first have to convert the character into a string.
(Integer/valueOf "12") ;=> 12
(Integer/valueOf (str \5)) ;=> 5
Finally, to turn the characters T
, J
, Q
, K
and A
into integers,
using a map to store the values is very useful:
(get {\A 100, \B 20} \B) ;=> 20
({\A 100, \B 20} \B) ;=> 20
(def replacements {\A 100, \B 20})
(replacements \B) ;=> 20
You can now write the (rank card)
function.
(rank "2H") ;=> 2
(rank "4S") ;=> 4
(rank "TS") ;=> 10
(rank "JS") ;=> 11
(rank "QS") ;=> 12
(rank "KS") ;=> 13
(rank "AS") ;=> 14
Here's a couple of functions that should prove useful.
(frequencies sequence)
is used to see how many times an element appears in a
sequence. It returns a map where elements are mapped to their appearance
counts:
(frequencies [4 7 7 4 7]) ;=> {4 2, 7 3}
In this case, we had three sevens and two fours.
If you are only interested in the keys or values of a map, you can get them
with (keys a-map)
and (vals a-map)
:
(vals (frequencies [4 7 7 4 7]))
;=> (2 3)
; ^-- now that looks a lot like a full house
(max num1 num2 num3 ...)
returns its largest parameter and (min num1 num2 num3 ...)
returns its smallest paremeter.
(max 1 5 4 2) ;=> 5
(min 1 5 4 2) ;=> 1
But what should you do if you have a sequence of numbers, like the vector [1 -4 2 3 5]
, and you want its smallest or largest value? There is a very useful
special form called apply
for this: (apply function parameter-sequence)
calls function
with the parameters from parameter-sequence
.
(apply str ["Over " 9000 "!"])
;=> (str "Over " 9000 "!")
;=> "Over 9000!"
(apply max [5 3 2])
;=> (max 5 3 2)
;=> 5
That's quite a lot to remember, but these should provide useful when detecting different hands. If you get stuck, the functions introduced above might help.
If you don't remember a hand, the Poker hands article at Wikipedia has them listed and explained.
Our representation for a poker hand is simply a vector of cards:
(def high-seven ["2H" "3S" "4C" "5C" "7D"])
Here's a bunch of hands to use for testing:
(def high-seven ["2H" "3S" "4C" "5C" "7D"])
(def pair-hand ["2H" "2S" "4C" "5C" "7D"])
(def two-pairs-hand ["2H" "2S" "4C" "4D" "7D"])
(def three-of-a-kind-hand ["2H" "2S" "2C" "4D" "7D"])
(def four-of-a-kind-hand ["2H" "2S" "2C" "2D" "7D"])
(def straight-hand ["2H" "3S" "6C" "5D" "4D"])
(def low-ace-straight-hand ["2H" "3S" "4C" "5D" "AD"])
(def high-ace-straight-hand ["TH" "AS" "QC" "KD" "JD"])
(def flush-hand ["2H" "4H" "5H" "9H" "7H"])
(def full-house-hand ["2H" "5D" "2D" "2C" "5S"])
(def straight-flush-hand ["2H" "3H" "6H" "5H" "4H"])
(def low-ace-straight-flush-hand ["2D" "3D" "4D" "5D" "AD"])
(def high-ace-straight-flush-hand ["TS" "AS" "QS" "KS" "JS"])
(pair? pair-hand) ;=> true
(pair? high-seven) ;=> false
(three-of-a-kind? two-pairs-hand) ;=> false
(three-of-a-kind? three-of-a-kind-hand) ;=> true
(four-of-a-kind? two-pairs-hand) ;=> false
(four-of-a-kind? four-of-a-kind-hand) ;=> true
(flush? pair-hand) ;=> false
(flush? flush-hand) ;=> true)
(sort a-seq)
returns a sequence with the elements of a-seq
in a sorted
order.
(sort [5 -1 3 17 -10]) ;=> (-10 -1 3 5 17)
(sort [6 4 5 7 3]) ;=> (3 4 5 6 7)
; ^
; |
;kind of looks like a straight---
(range lower-bound upper-bound)
takes two integers and returns a sequence
with all integers from lower-bound
to upper-bound
, but does not include
upper-bound
.
(range 1 5) ;=> (1 2 3 4)
(range 5) ;=> (0 1 2 3 4)
You can test for equality between sequences with =
.
(= [3 4 5 6 7] (range 3 (+ 3 5)))
;=> (= [3 4 5 6 7]
; (3 4 5 6 7))
;=> (and (= 3 3) (= 4 4) (= 5 5) (= 6 6) (= 7 7))
;=> true
(= [1 2 3] (seq [1 2])) ;=> false
Two sequences are equal if their elements are equal and in the same order.
Write the function `(full-house? hand)` that returns `true` if `hand` is a full house, and otherwise `false`.(full-house? three-of-a-kind-hand) ;=> false
(full-house? full-house-hand) ;=> true
Note that a four of a kind is also two pairs.
(two-pairs? two-pairs-hand) ;=> true
(two-pairs? pair-hand) ;=> false
(two-pairs? four-of-a-kind-hand) ;=> true
In a straight, an ace is accepted as either 1 or 14, so both of the following hands have a straight:
["2H" "3S" "4C" "5D" "AD"]
["TH" "AS" "QC" "KD" "JD"]
A useful function here is (replace replace-map a-seq)
. It takes a map of
replacements and a sequence and replaces the keys of replace-map
in a-seq
with their associated values.
(replace {1 "a", 2 "b"} [1 2 3 4]) ;=> ["a" "b" 3 4]
Finally we can implement straight?
.
Note that an ace is accepted both as a rank 1 and rank 14 card in straights.
(straight? two-pairs-hand) ;=> false
(straight? straight-hand) ;=> true
(straight? low-ace-straight-hand) ;=> true
(straight? ["2H" "2D" "3H" "4H" "5H"]) ;=> false
(straight? high-ace-straight-hand) ;=> true
And finally, there's straight flush. This shouldn't be very difficult after having already defined flush and straight.
Write the function `(straight-flush? hand)` which returns `true` if the hand is a straight flush, that is both a straight and a flush, and otherwise `false`.(straight-flush? straight-hand) ;=> false
(straight-flush? flush-hand) ;=> false
(straight-flush? straight-flush-hand) ;=> true
(straight-flush? low-ace-straight-flush-hand) ;=> true
(straight-flush? high-ace-straight-flush-hand) ;=> true
Now that we have functions that check for each hand type, it would be nice to be able to assign a value to each hand. We're going to use the following values:
Hand Value
High card (nothing) 0 Pair 1 Two pairs 2 Three of a kind 3 Straight 4 Flush 5 Full house 6 Four of a kind 7 Straight flush 8
Write the function `(value hand)`, which returns the value of a hand according to the table above.(value high-seven) ;=> 0
(value pair-hand) ;=> 1
(value two-pairs-hand) ;=> 2
(value three-of-a-kind-hand) ;=> 3
(value straight-hand) ;=> 4
(value flush-hand) ;=> 5
(value full-house-hand) ;=> 6
(value four-of-a-kind-hand) ;=> 7
(value straight-flush-hand) ;=> 8
It might be helpful to add a checker (high-card? hand)
:
(defn high-card? [hand]
true) ; All hands have a high card.
You can create a sequence of [matcher value]
pairs like so:
(let [checkers #{[high-card? 0] [pair? 1]
[two-pairs? 2] [three-of-a-kind? 3]
[straight? 4] [flush? 5]
[full-house? 6] [four-of-a-kind? 7]
[straight-flush? 8]}]
...)
You can now use filter
, map
and apply max
to get the highest value that a
hand has. The function second
can be useful. Remember to use let
to give the
intermediate results readable names.
(second [:i :am :a :sequence]) ;=> :am
(second [two-pairs? 2]) ;=> 2
Our representation for poker hands is rather simple, but it allows us to use existing sequence and other functions to work with them. However, the current API is bound to the representation. This has the unfortunate consequence that we can not change the internal representation of cards or hands without changing the external API. There can be good reasons to change the internal representation, including efficiency.
No worries, this is fixable. Let's create two functions for creating a card and creating a hand from the representation we already use:
(defn hand [a-hand]
a-hand)
(defn card [a-card]
a-card)
Which you would use like this:
(card "2H")
(hand ["AH" "AC" "AD" "AS" "4H"])
While these functions in their current form don't do anything, it allows us to change the internal representation if we want to. You would use the existing functions like this:
(rank (card "2H")) ;=> 2
(value (hand ["2H" "3H" "4H" "5H" "6H"])) ;=> 4
Now the functions card
, hand
, rank
and suit
form the abstraction for a playing
card. They are the only functions that need to know about the internal
representatin of the card. This means that we would now be able to change the
internal representation to something like this, if we wanted to:
(card "2H") ;=> {:rank 2, :suit :hearts}
Now that you have mastered evaluating poker hands, it's time to understand recursion.