diff --git a/poker_ex/lib/poker_ex/card.ex b/poker_ex/lib/poker_ex/card.ex index 0a9750c..77da71d 100644 --- a/poker_ex/lib/poker_ex/card.ex +++ b/poker_ex/lib/poker_ex/card.ex @@ -1,17 +1,14 @@ defmodule PokerEx.Card do - @doc ~S""" - Holds functions that operate and symbolise a playing card. - """ - alias __MODULE__ @valid_suits [:spades, :hearts, :diamonds, :clubs] @type suit() :: :hearts | :diamonds | :clubs | :spades @type rank() :: integer() | :jack | :queen | :king | :ace - @type t() :: %Card{suit: suit(), rank: rank()} - defstruct [:suit, :rank] + @type t() :: %Card{suit: suit(), rank: rank()} + defstruct suit: :required, + rank: :required defguard is_valid_suit(suit) when suit in @valid_suits @@ -19,6 +16,17 @@ defmodule PokerEx.Card do when (is_integer(rank) and 2 <= rank and rank <= 10) or rank in [:jack, :queen, :king, :ace] + @spec rank_to_number(rank()) :: integer() + def rank_to_number(rank) do + case rank do + :jack -> 11 + :queen -> 12 + :king -> 13 + :ace -> 14 + _ -> rank + end + end + @doc ~S""" Checks if a given card is valid. To be valid, it should be the card string with a valid suit and rank, which are specified as module types. @@ -80,19 +88,11 @@ defmodule PokerEx.Card do @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 + [f_rank, s_rank] = [first.rank, second.rank] |> Enum.map(&rank_to_number/1) + cond do + f_rank < s_rank -> :lt + f_rank == s_rank -> :eq + f_rank > s_rank -> :gt end end diff --git a/poker_ex/lib/poker_ex/combination.ex b/poker_ex/lib/poker_ex/combination.ex index 73b6d71..1622311 100644 --- a/poker_ex/lib/poker_ex/combination.ex +++ b/poker_ex/lib/poker_ex/combination.ex @@ -1,5 +1,6 @@ defmodule PokerEx.Combination do - alias PokerEx.Card, as: Card + import Bitwise + alias PokerEx.Card @type high_card() :: {:high_card, Card.t()} @type pair() :: {:pair, Card.t()} @@ -24,20 +25,49 @@ defmodule PokerEx.Combination do | straight_flush() | royal_flush() + @spec means_to_number(score()) :: integer() + defp means_to_number({:high_card, %Card{rank: rank}}), do: Card.rank_to_number(rank) + + defp means_to_number({:pair, %Card{rank: rank}}), do: Card.rank_to_number(rank) + + defp means_to_number({:two_pair, {%Card{rank: rank1}, %Card{rank: rank2}}}), + do: [rank1, rank2] |> Enum.map(&Card.rank_to_number/1) |> Enum.sum() + + defp means_to_number({:three_of_a_kind, %Card{rank: rank}}), do: Card.rank_to_number(rank) + + defp means_to_number({:straight, {_begin, final}}), do: Card.rank_to_number(final) + + defp means_to_number({:full_house, {%Card{rank: rank1}, %Card{rank: rank2}}}), + do: Card.rank_to_number(rank1) <<< (4 + Card.rank_to_number(rank2)) + + defp means_to_number({:four_of_a_kind, %Card{rank: rank}}), do: Card.rank_to_number(rank) + + defp means_to_number({:straight_flush, {_suit, _begin, final}}), do: Card.rank_to_number(final) + + defp means_to_number(_score), do: 0 + + @spec score_to_number(score()) :: integer() + defp score_to_number({score, means}) do + case score do + :high_card -> 0x000 + :pair -> 0x100 + :two_pair -> 0x200 + :three_of_a_kind -> 0x300 + :straight -> 0x400 + :flush -> 0x500 + :full_house -> 0x600 + :four_of_a_kind -> 0x700 + :straight_flush -> 0x800 + :royal_flush -> 0x900 + end + means_to_number({score, means}) + end + @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.map(&Card.rank_to_number/1) |> Enum.chunk_every(2, 1, :discard) |> Enum.map( &case &1 do @@ -142,6 +172,51 @@ defmodule PokerEx.Combination do end end + @doc ~S""" + Compares two scores and returns their equality. + + When players have the same score, the one with the highest means for + this score (i.e., the cards with which the score is obtained) is the winner. + + ## Examples + + iex> import PokerEx.Card + iex> hand1 = [~p"S3", ~p"C4", ~p"H5", ~p"D6", ~p"C7"] + iex> hand2 = [~p"C4", ~p"H5", ~p"D6", ~p"C7", ~p"S8"] + iex> [score1, score2] = [hand1, hand2] |> Enum.map(&PokerEx.Combination.evaluate_hand/1) + iex> [score1, score2] + [{:straight, {3, 7}}, {:straight, {4, 8}}] + iex> compare(score1, score2) + :lt + + iex> import PokerEx.Card + iex> hand1 = [~p"S3", ~p"S5", ~p"S6", ~p"S7", ~p"S8"] + iex> hand2 = [~p"C4", ~p"H5", ~p"D6", ~p"C7", ~p"S8"] + iex> [score1, score2] = [hand1, hand2] |> Enum.map(&PokerEx.Combination.evaluate_hand/1) + iex> [score1, score2] + [{:flush, :spades}, {:straight, {4, 8}}] + iex> compare(score1, score2) + :gt + + iex> import PokerEx.Card + iex> hand1 = [~p"S3", ~p"C4", ~p"H5", ~p"D6", ~p"C7"] + iex> hand2 = [~p"S3", ~p"C4", ~p"H5", ~p"D6", ~p"C7"] + iex> [score1, score2] = [hand1, hand2] |> Enum.map(&PokerEx.Combination.evaluate_hand/1) + iex> [score1, score2] + [{:straight, {3, 7}}, {:straight, {3, 7}}] + iex> compare(score1, score2) + :eq + """ + @spec compare(score(), score()) :: :lt | :eq | :gt + def compare(score1, score2) do + [s1, s2] = [score1, score2] |> Enum.map(&score_to_number/1) + cond do + s1 < s2 -> :lt + s1 == s2 -> :eq + s1 > s2 -> :gt + end + end + @doc ~S""" Finds the highest combination that can be made using the hand and the cards that are present. @@ -155,19 +230,19 @@ defmodule PokerEx.Combination do 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}}