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
@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

View file

@ -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}}