Compare commits
2 commits
f32bff0ec5
...
302095d982
| Author | SHA1 | Date | |
|---|---|---|---|
| 302095d982 | |||
| 1e2b24d616 |
4 changed files with 220 additions and 4 deletions
|
|
@ -8,7 +8,7 @@ defmodule PokerEx.Card do
|
|||
@valid_suits [:spades, :hearts, :diamonds, :clubs]
|
||||
|
||||
@type suit() :: :hearts | :diamonds | :clubs | :spades
|
||||
@type rank() :: integer | :jack | :queen | :king | :ace
|
||||
@type rank() :: integer() | :jack | :queen | :king | :ace
|
||||
@type t() :: %Card{suit: suit(), rank: rank()}
|
||||
|
||||
defstruct [:suit, :rank]
|
||||
|
|
@ -37,7 +37,7 @@ defmodule PokerEx.Card do
|
|||
iex> PokerEx.Card.valid? %PokerEx.Card{suit: :diamonds, rank: 11}
|
||||
false
|
||||
"""
|
||||
@spec valid?(Card.t()) :: boolean
|
||||
@spec valid?(Card.t()) :: boolean()
|
||||
def valid?(%Card{suit: suit, rank: rank}) when is_valid_rank(rank) and is_valid_suit(suit),
|
||||
do: true
|
||||
|
||||
|
|
@ -78,6 +78,24 @@ defmodule PokerEx.Card do
|
|||
|> Enum.map(fn rank -> %Card{suit: suit, rank: rank} end)
|
||||
end
|
||||
|
||||
@spec compare(Card.t(), Card.t()) :: :lt | :eq | :gt
|
||||
def compare(first, second) do
|
||||
case [first.rank, second.rank]
|
||||
|> Enum.map(
|
||||
&case &1 do
|
||||
:jack -> 11
|
||||
:queen -> 12
|
||||
:king -> 13
|
||||
:ace -> 14
|
||||
n -> n
|
||||
end
|
||||
) do
|
||||
[f, s] when f < s -> :lt
|
||||
[f, s] when f == s -> :eq
|
||||
[f, s] when f > s -> :gt
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Makes a card struct from the given integers. Mostly used when parsing
|
||||
the unicode glyph into a card.
|
||||
|
|
@ -98,7 +116,7 @@ defmodule PokerEx.Card do
|
|||
iex> PokerEx.Card.from_integers(0, 15)
|
||||
{:error, "Invalid integer rank: 15"}
|
||||
"""
|
||||
@spec from_integers(integer, integer) :: {:ok, Card.t()} | {:error, String.t()}
|
||||
@spec from_integers(integer(), integer()) :: {:ok, Card.t()} | {:error, String.t()}
|
||||
def from_integers(suit, _rank)
|
||||
when suit < 0
|
||||
when suit >= 4 do
|
||||
|
|
|
|||
194
poker_ex/lib/poker_ex/combination.ex
Normal file
194
poker_ex/lib/poker_ex/combination.ex
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
defmodule PokerEx.Combination do
|
||||
alias PokerEx.Card, as: Card
|
||||
|
||||
@type high_card() :: {:high_card, Card.t()}
|
||||
@type pair() :: {:pair, Card.t()}
|
||||
@type two_pair() :: {:two_pair, {Card.t(), Card.t()}}
|
||||
@type three_of_a_kind() :: {:three_of_a_kind, Card.t()}
|
||||
@type straight() :: {:straight, {Card.rank(), Card.rank()}}
|
||||
@type flush() :: {:flush, Card.suit()}
|
||||
@type full_house() :: {:full_house, {Card.t(), Card.t()}}
|
||||
@type four_of_a_kind() :: {:four_of_a_kind, Card.t()}
|
||||
@type straight_flush() :: {:straight_flush, {Card.suit(), Card.rank(), Card.rank()}}
|
||||
@type royal_flush() :: {:royal_flush, Card.suit()}
|
||||
|
||||
@type score() ::
|
||||
high_card()
|
||||
| pair()
|
||||
| two_pair()
|
||||
| three_of_a_kind()
|
||||
| straight()
|
||||
| flush()
|
||||
| full_house()
|
||||
| four_of_a_kind()
|
||||
| straight_flush()
|
||||
| royal_flush()
|
||||
|
||||
@spec straight?(ranks: [Card.rank()]) :: boolean
|
||||
defp straight?(ranks) when length(ranks) < 5, do: false
|
||||
|
||||
defp straight?(ranks) do
|
||||
ranks
|
||||
|> Enum.map(
|
||||
&case &1 do
|
||||
:jack -> 11
|
||||
:queen -> 12
|
||||
:king -> 13
|
||||
:ace -> 14
|
||||
n -> n
|
||||
end
|
||||
)
|
||||
|> Enum.chunk_every(2, 1, :discard)
|
||||
|> Enum.map(
|
||||
&case &1 do
|
||||
[14, 2] -> true
|
||||
[a, b] -> b - a == 1
|
||||
_ -> false
|
||||
end
|
||||
)
|
||||
|> Enum.all?()
|
||||
end
|
||||
|
||||
@spec n_equals(%{Card.t() => integer()}, integer()) :: [Card.t()]
|
||||
defp n_equals(frequencies, n) do
|
||||
frequencies
|
||||
|> Enum.filter(fn {_key, val} -> val == n end)
|
||||
|> Enum.map(fn {key, _val} -> key end)
|
||||
|> Enum.sort({:desc, Card})
|
||||
end
|
||||
|
||||
@spec straight([Card.t()]) :: straight() | nil
|
||||
defp straight(sorted_hand) when length(sorted_hand) < 5, do: nil
|
||||
|
||||
defp straight(sorted_hand) do
|
||||
case sorted_hand
|
||||
|> Enum.uniq()
|
||||
|> Enum.drop_while(&(&1.rank != :ace))
|
||||
|> Enum.concat(sorted_hand)
|
||||
|> Enum.map(& &1.rank)
|
||||
|> Enum.chunk_every(5, 1, :discard)
|
||||
|> Enum.find(&straight?/1) do
|
||||
nil -> nil
|
||||
[begin, _, _, _, final] -> {:straight, {begin, final}}
|
||||
end
|
||||
end
|
||||
|
||||
@spec flush([Card.t()]) :: flush() | nil
|
||||
defp flush(hand) when length(hand) < 5, do: nil
|
||||
|
||||
defp flush(hand) do
|
||||
case hand
|
||||
|> Enum.map(& &1.suit)
|
||||
|> Enum.frequencies()
|
||||
|> Enum.find(fn {_, freq} -> freq >= 5 end) do
|
||||
nil -> nil
|
||||
{suit, _freq} -> {:flush, suit}
|
||||
end
|
||||
end
|
||||
|
||||
@spec straight_flush([Card.t()]) :: straight_flush() | nil
|
||||
defp straight_flush(sorted_hand) do
|
||||
case {straight(sorted_hand), flush(sorted_hand)} do
|
||||
{{:straight, {begin, final}}, {:flush, suit}} -> {:straight_flush, {suit, begin, final}}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec royal_flush([Card.t()]) :: royal_flush() | nil
|
||||
defp royal_flush(sorted_hand) do
|
||||
case straight_flush(sorted_hand) do
|
||||
{:straight_flush, {suit, begin, _}} when begin == 10 -> {:royal_flush, suit}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec pair(%{Card.t() => integer()}) :: pair() | nil
|
||||
defp pair(frequencies) do
|
||||
case n_equals(frequencies, 2) do
|
||||
[] -> nil
|
||||
[p | _] -> {:pair, p}
|
||||
end
|
||||
end
|
||||
|
||||
@spec two_pair(%{Card.t() => integer()}) :: two_pair() | nil
|
||||
defp two_pair(frequencies) do
|
||||
case n_equals(frequencies, 2) do
|
||||
[f | [s | _]] -> {:two_pair, {f, s}}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec three_of_a_kind(%{Card.t() => integer()}) :: three_of_a_kind() | nil
|
||||
defp three_of_a_kind(frequencies) do
|
||||
case n_equals(frequencies, 3) do
|
||||
[] -> nil
|
||||
[tok | _] -> {:three_of_a_kind, tok}
|
||||
end
|
||||
end
|
||||
|
||||
@spec four_of_a_kind(%{Card.t() => integer()}) :: four_of_a_kind() | nil
|
||||
defp four_of_a_kind(frequencies) do
|
||||
case n_equals(frequencies, 4) do
|
||||
[] -> nil
|
||||
[fok | _] -> {:four_of_a_kind, fok}
|
||||
end
|
||||
end
|
||||
|
||||
@spec full_house(%{Card.t() => integer()}) :: full_house() | nil
|
||||
defp full_house(frequencies) do
|
||||
case {three_of_a_kind(frequencies), pair(frequencies)} do
|
||||
{{:three_of_a_kind, tok}, {:pair, p}} -> {:full_house, {tok, p}}
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
Finds the highest combination that can be made using the hand and the cards
|
||||
that are present.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"D2", ~p"H3", ~p"S4", ~p"C5"]
|
||||
{:straight, {:ace, 5}}
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"C10", ~p"CJ", ~p"CQ", ~p"CK", ~p"CA"]
|
||||
{:royal_flush, :clubs}
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"CA", ~p"CK", ~p"CK"]
|
||||
{:full_house, {%PokerEx.Card{suit: :clubs, rank: :ace}, %PokerEx.Card{suit: :clubs, rank: :king}}}
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"CA", ~p"D2", ~p"D3"]
|
||||
{:three_of_a_kind, %PokerEx.Card{suit: :clubs, rank: :ace}}
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"D2", ~p"D3", ~p"D4"]
|
||||
{:pair, %PokerEx.Card{suit: :clubs, rank: :ace}}
|
||||
|
||||
iex> import PokerEx.Card
|
||||
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CK", ~p"DJ", ~p"D2", ~p"H7"]
|
||||
{:high_card, %PokerEx.Card{suit: :clubs, rank: :ace}}
|
||||
"""
|
||||
@spec evaluate_hand([Card.t()]) :: score()
|
||||
def evaluate_hand(hand) do
|
||||
sorted_hand = hand |> Enum.sort({:asc, Card})
|
||||
freq = hand |> Enum.frequencies()
|
||||
|
||||
[
|
||||
royal_flush(sorted_hand),
|
||||
straight_flush(sorted_hand),
|
||||
four_of_a_kind(freq),
|
||||
full_house(freq),
|
||||
flush(sorted_hand),
|
||||
straight(sorted_hand),
|
||||
three_of_a_kind(freq),
|
||||
two_pair(freq),
|
||||
pair(freq),
|
||||
{:high_card, sorted_hand |> List.last()}
|
||||
]
|
||||
|> Enum.find(&(&1 != nil))
|
||||
end
|
||||
end
|
||||
|
|
@ -48,6 +48,6 @@ defmodule PokerEx.Deck do
|
|||
iex> PokerEx.Deck.valid? []
|
||||
true
|
||||
"""
|
||||
@spec valid?(deck()) :: boolean
|
||||
@spec valid?(deck()) :: boolean()
|
||||
def valid?(deck), do: deck |> Enum.all?(&Card.valid?/1)
|
||||
end
|
||||
|
|
|
|||
4
poker_ex/test/poker_ex/combination_test.exs
Normal file
4
poker_ex/test/poker_ex/combination_test.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
defmodule PokerEx.CombinationTest do
|
||||
use ExUnit.Case
|
||||
doctest PokerEx.Combination
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue