225 lines
6.3 KiB
Elixir
225 lines
6.3 KiB
Elixir
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]
|
|
|
|
defguard is_valid_suit(suit) when suit in @valid_suits
|
|
|
|
defguard is_valid_rank(rank)
|
|
when (is_integer(rank) and 2 <= rank and rank <= 10) or
|
|
rank in [:jack, :queen, :king, :ace]
|
|
|
|
@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.
|
|
|
|
Valid suits: :hearts, :diamonds, :clubs, :spades
|
|
Valid ranks: Numbers 2-10, :jack, :queen, :king, :ace
|
|
|
|
## Examples
|
|
|
|
iex> PokerEx.Card.valid? %PokerEx.Card{suit: :spades, rank: :ace}
|
|
true
|
|
|
|
iex> PokerEx.Card.valid? %PokerEx.Card{suit: :non_existent, rank: 2}
|
|
false
|
|
|
|
iex> PokerEx.Card.valid? %PokerEx.Card{suit: :diamonds, rank: 11}
|
|
false
|
|
"""
|
|
@spec valid?(Card.t()) :: boolean
|
|
def valid?(%Card{suit: suit, rank: rank}) when is_valid_rank(rank) and is_valid_suit(suit),
|
|
do: true
|
|
|
|
def valid?(_card), do: false
|
|
|
|
@doc ~S"""
|
|
Returns all valid suits as array.
|
|
"""
|
|
@spec all_suits() :: [suit()]
|
|
def all_suits(), do: @valid_suits
|
|
|
|
@doc ~S"""
|
|
Returns all cards with a given suit. This means all the numbers
|
|
2 through 10, the jack, queen, king, and ace.
|
|
|
|
## Examples
|
|
|
|
iex> PokerEx.Card.all_cards_for_suit :diamonds
|
|
[
|
|
%PokerEx.Card{suit: :diamonds, rank: 2},
|
|
%PokerEx.Card{suit: :diamonds, rank: 3},
|
|
%PokerEx.Card{suit: :diamonds, rank: 4},
|
|
%PokerEx.Card{suit: :diamonds, rank: 5},
|
|
%PokerEx.Card{suit: :diamonds, rank: 6},
|
|
%PokerEx.Card{suit: :diamonds, rank: 7},
|
|
%PokerEx.Card{suit: :diamonds, rank: 8},
|
|
%PokerEx.Card{suit: :diamonds, rank: 9},
|
|
%PokerEx.Card{suit: :diamonds, rank: 10},
|
|
%PokerEx.Card{suit: :diamonds, rank: :jack},
|
|
%PokerEx.Card{suit: :diamonds, rank: :queen},
|
|
%PokerEx.Card{suit: :diamonds, rank: :king},
|
|
%PokerEx.Card{suit: :diamonds, rank: :ace}
|
|
]
|
|
"""
|
|
@spec all_cards_for_suit(suit()) :: [Card.t()]
|
|
def all_cards_for_suit(suit) when is_valid_suit(suit) do
|
|
[2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace]
|
|
|> Enum.map(fn rank -> %Card{suit: suit, rank: rank} end)
|
|
end
|
|
|
|
@doc ~S"""
|
|
Makes a card struct from the given integers. Mostly used when parsing
|
|
the unicode glyph into a card.
|
|
|
|
The jack, queen, king, and ace are 11, 13, 14, 1.
|
|
|
|
## Examples
|
|
|
|
iex> PokerEx.Card.from_integers(0, 5)
|
|
{:ok, %PokerEx.Card{suit: :spades, rank: 5}}
|
|
|
|
iex> PokerEx.Card.from_integers(5, 5)
|
|
{:error, "Invalid integer suit: 5"}
|
|
|
|
iex> PokerEx.Card.from_integers(0, 1)
|
|
{:ok, %PokerEx.Card{suit: :spades, rank: :ace}}
|
|
|
|
iex> PokerEx.Card.from_integers(0, 15)
|
|
{:error, "Invalid integer rank: 15"}
|
|
"""
|
|
@spec from_integers(integer, integer) :: {:ok, Card.t()} | {:error, String.t()}
|
|
def from_integers(suit, _rank)
|
|
when suit < 0
|
|
when suit >= 4 do
|
|
{:error, "Invalid integer suit: #{suit}"}
|
|
end
|
|
|
|
def from_integers(_suit, rank)
|
|
when rank < 1
|
|
when rank > 0xE do
|
|
{:error, "Invalid integer rank: #{rank}"}
|
|
end
|
|
|
|
def from_integers(suit, rank) do
|
|
suit = Enum.at(@valid_suits, suit)
|
|
|
|
{:ok,
|
|
case rank do
|
|
n when n >= 2 and n <= 10 -> %Card{suit: suit, rank: n}
|
|
0x1 -> %Card{suit: suit, rank: :ace}
|
|
0xB -> %Card{suit: suit, rank: :jack}
|
|
0xD -> %Card{suit: suit, rank: :queen}
|
|
0xE -> %Card{suit: suit, rank: :king}
|
|
end}
|
|
end
|
|
|
|
@doc ~S"""
|
|
Converts a unicode playing card glyph into a playing card struct.
|
|
|
|
## Examples
|
|
|
|
iex> PokerEx.Card.from_string "🂸"
|
|
{:ok, %PokerEx.Card{suit: :hearts, rank: 8}}
|
|
|
|
iex> PokerEx.Card.from_string "🃍"
|
|
{:ok, %PokerEx.Card{suit: :diamonds, rank: :queen}}
|
|
|
|
iex> PokerEx.Card.from_string "Hello world"
|
|
{:error, "Not a valid card glyph!"}
|
|
|
|
iex> PokerEx.Card.from_string "💩"
|
|
{:error, "Not a valid card glyph!"}
|
|
"""
|
|
@spec from_string(String.t()) :: {:ok, Card.t()} | {:error, String.t()}
|
|
def from_string(card_rep) do
|
|
case card_rep do
|
|
<<codepoint::utf8>> when codepoint > 0x1F0A0 and codepoint < 0x1F0DF ->
|
|
offset = codepoint - ?🂠
|
|
suit = div(offset, 0x10)
|
|
rank = rem(offset, 0x10)
|
|
|
|
from_integers(suit, rank)
|
|
|
|
_ ->
|
|
{:error, "Not a valid card glyph!"}
|
|
end
|
|
end
|
|
|
|
@doc ~S"""
|
|
Provides a helper function to easily generate a card.
|
|
Takes the short-hand notation for a suit, and then a rank.
|
|
|
|
## Examples
|
|
|
|
iex> import PokerEx.Card
|
|
iex> ~p"CA"
|
|
%PokerEx.Card{suit: :clubs, rank: :ace}
|
|
iex> ~p"D10"
|
|
%PokerEx.Card{suit: :diamonds, rank: 10}
|
|
"""
|
|
@spec sigil_p(atom() | String.t(), [char()]) :: Card.t()
|
|
def sigil_p(<<suit::utf8, rank::binary>>, []) do
|
|
suit =
|
|
case suit do
|
|
?S -> :spades
|
|
?H -> :hearts
|
|
?D -> :diamonds
|
|
?C -> :clubs
|
|
end
|
|
|
|
rank =
|
|
case rank do
|
|
"A" -> :ace
|
|
"K" -> :king
|
|
"Q" -> :queen
|
|
"J" -> :jack
|
|
n -> String.to_integer(n)
|
|
end
|
|
|
|
card = %Card{suit: suit, rank: rank}
|
|
|
|
if not valid?(card) do
|
|
raise "Invalid card!"
|
|
end
|
|
|
|
card
|
|
end
|
|
|
|
defimpl Inspect, for: Card do
|
|
@suit_letters %{spades: "S", hearts: "H", diamonds: "D", clubs: "C"}
|
|
@rank_letters %{ace: "A", jack: "J", queen: "Q", king: "K"}
|
|
|
|
def inspect(%Card{suit: suit, rank: rank}, _opts) when is_integer(rank) do
|
|
"#Card<#{@suit_letters[suit]}#{rank}>"
|
|
end
|
|
|
|
def inspect(%Card{suit: suit, rank: rank}, _opts) do
|
|
"#Card<#{@suit_letters[suit]}#{@rank_letters[rank]}>"
|
|
end
|
|
end
|
|
|
|
defimpl String.Chars, for: Card do
|
|
@suit_codepoints %{spades: 0x00, hearts: 0x10, diamonds: 0x20, clubs: 0x30}
|
|
@rank_codepoints %{ace: 0x1, jack: 0xB, queen: 0xD, king: 0xE}
|
|
@base_codepoint ?🂠
|
|
|
|
def to_string(%Card{suit: suit, rank: rank}) when is_integer(rank) do
|
|
<<@base_codepoint + @suit_codepoints[suit] + rank::utf8>>
|
|
end
|
|
|
|
def to_string(%Card{suit: suit, rank: rank}) do
|
|
<<@base_codepoint + @suit_codepoints[suit] + @rank_codepoints[rank]::utf8>>
|
|
end
|
|
end
|
|
end
|