Clean up code. Add a way to compare scores.

This commit is contained in:
Hans Goor 2024-08-17 12:41:13 +00:00
parent 337219ebf3
commit 3d3106ddd1
No known key found for this signature in database
2 changed files with 108 additions and 33 deletions

View file

@ -1,17 +1,14 @@
defmodule PokerEx.Card do defmodule PokerEx.Card do
@doc ~S"""
Holds functions that operate and symbolise a playing card.
"""
alias __MODULE__ alias __MODULE__
@valid_suits [:spades, :hearts, :diamonds, :clubs] @valid_suits [:spades, :hearts, :diamonds, :clubs]
@type suit() :: :hearts | :diamonds | :clubs | :spades @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] @type t() :: %Card{suit: suit(), rank: rank()}
defstruct suit: :required,
rank: :required
defguard is_valid_suit(suit) when suit in @valid_suits 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 when (is_integer(rank) and 2 <= rank and rank <= 10) or
rank in [:jack, :queen, :king, :ace] 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""" @doc ~S"""
Checks if a given card is valid. To be valid, it should be the card 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. 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 @spec compare(Card.t(), Card.t()) :: :lt | :eq | :gt
def compare(first, second) do def compare(first, second) do
case [first.rank, second.rank] [f_rank, s_rank] = [first.rank, second.rank] |> Enum.map(&rank_to_number/1)
|> Enum.map( cond do
&case &1 do f_rank < s_rank -> :lt
:jack -> 11 f_rank == s_rank -> :eq
:queen -> 12 f_rank > s_rank -> :gt
: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
end end

View file

@ -1,5 +1,6 @@
defmodule PokerEx.Combination do defmodule PokerEx.Combination do
alias PokerEx.Card, as: Card import Bitwise
alias PokerEx.Card
@type high_card() :: {:high_card, Card.t()} @type high_card() :: {:high_card, Card.t()}
@type pair() :: {:pair, Card.t()} @type pair() :: {:pair, Card.t()}
@ -24,20 +25,49 @@ defmodule PokerEx.Combination do
| straight_flush() | straight_flush()
| royal_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 @spec straight?(ranks: [Card.rank()]) :: boolean
defp straight?(ranks) when length(ranks) < 5, do: false defp straight?(ranks) when length(ranks) < 5, do: false
defp straight?(ranks) do defp straight?(ranks) do
ranks ranks
|> Enum.map( |> Enum.map(&Card.rank_to_number/1)
&case &1 do
:jack -> 11
:queen -> 12
:king -> 13
:ace -> 14
n -> n
end
)
|> Enum.chunk_every(2, 1, :discard) |> Enum.chunk_every(2, 1, :discard)
|> Enum.map( |> Enum.map(
&case &1 do &case &1 do
@ -142,6 +172,51 @@ defmodule PokerEx.Combination do
end end
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""" @doc ~S"""
Finds the highest combination that can be made using the hand and the cards Finds the highest combination that can be made using the hand and the cards
that are present. that are present.
@ -155,19 +230,19 @@ defmodule PokerEx.Combination do
iex> import PokerEx.Card iex> import PokerEx.Card
iex> PokerEx.Combination.evaluate_hand [~p"C10", ~p"CJ", ~p"CQ", ~p"CK", ~p"CA"] iex> PokerEx.Combination.evaluate_hand [~p"C10", ~p"CJ", ~p"CQ", ~p"CK", ~p"CA"]
{:royal_flush, :clubs} {:royal_flush, :clubs}
iex> import PokerEx.Card iex> import PokerEx.Card
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"CA", ~p"CK", ~p"CK"] 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}}} {:full_house, {%PokerEx.Card{suit: :clubs, rank: :ace}, %PokerEx.Card{suit: :clubs, rank: :king}}}
iex> import PokerEx.Card iex> import PokerEx.Card
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"CA", ~p"D2", ~p"D3"] 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}} {:three_of_a_kind, %PokerEx.Card{suit: :clubs, rank: :ace}}
iex> import PokerEx.Card iex> import PokerEx.Card
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"D2", ~p"D3", ~p"D4"] iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CA", ~p"D2", ~p"D3", ~p"D4"]
{:pair, %PokerEx.Card{suit: :clubs, rank: :ace}} {:pair, %PokerEx.Card{suit: :clubs, rank: :ace}}
iex> import PokerEx.Card iex> import PokerEx.Card
iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CK", ~p"DJ", ~p"D2", ~p"H7"] iex> PokerEx.Combination.evaluate_hand [~p"CA", ~p"CK", ~p"DJ", ~p"D2", ~p"H7"]
{:high_card, %PokerEx.Card{suit: :clubs, rank: :ace}} {:high_card, %PokerEx.Card{suit: :clubs, rank: :ace}}