Add doctests for Card and Deck modules.
This commit is contained in:
parent
21a0673ba2
commit
4210670e9e
4 changed files with 174 additions and 4 deletions
|
|
@ -1,4 +1,8 @@
|
||||||
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]
|
||||||
|
|
@ -15,21 +19,85 @@ 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]
|
||||||
|
|
||||||
|
@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
|
@spec valid?(Card.t()) :: boolean
|
||||||
def valid?(%Card{suit: suit, rank: rank}) when is_valid_rank(rank) and is_valid_suit(suit),
|
def valid?(%Card{suit: suit, rank: rank}) when is_valid_rank(rank) and is_valid_suit(suit),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
def valid?(_card), do: false
|
def valid?(_card), do: false
|
||||||
|
|
||||||
|
@doc ~S"""
|
||||||
|
Returns all valid suits as array.
|
||||||
|
"""
|
||||||
@spec all_suits() :: [suit()]
|
@spec all_suits() :: [suit()]
|
||||||
def all_suits(), do: @valid_suits
|
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()]
|
@spec all_cards_for_suit(suit()) :: [Card.t()]
|
||||||
def all_cards_for_suit(suit) when is_valid_suit(suit) do
|
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]
|
[2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace]
|
||||||
|> Enum.map(fn rank -> %Card{suit: suit, rank: rank} end)
|
|> Enum.map(fn rank -> %Card{suit: suit, rank: rank} end)
|
||||||
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()}
|
@spec from_integers(integer, integer) :: {:ok, Card.t()} | {:error, String.t()}
|
||||||
def from_integers(suit, _rank)
|
def from_integers(suit, _rank)
|
||||||
when suit < 0
|
when suit < 0
|
||||||
|
|
@ -38,7 +106,7 @@ defmodule PokerEx.Card do
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_integers(_suit, rank)
|
def from_integers(_suit, rank)
|
||||||
when rank < 2
|
when rank < 1
|
||||||
when rank > 0xE do
|
when rank > 0xE do
|
||||||
{:error, "Invalid integer rank: #{rank}"}
|
{:error, "Invalid integer rank: #{rank}"}
|
||||||
end
|
end
|
||||||
|
|
@ -46,18 +114,36 @@ defmodule PokerEx.Card do
|
||||||
def from_integers(suit, rank) do
|
def from_integers(suit, rank) do
|
||||||
suit = Enum.at(@valid_suits, suit)
|
suit = Enum.at(@valid_suits, suit)
|
||||||
|
|
||||||
case rank do
|
{:ok, case rank do
|
||||||
n when n >= 2 and n <= 10 -> %Card{suit: suit, rank: n}
|
n when n >= 2 and n <= 10 -> %Card{suit: suit, rank: n}
|
||||||
|
0x1 -> %Card{suit: suit, rank: :ace}
|
||||||
0xB -> %Card{suit: suit, rank: :jack}
|
0xB -> %Card{suit: suit, rank: :jack}
|
||||||
0xD -> %Card{suit: suit, rank: :queen}
|
0xD -> %Card{suit: suit, rank: :queen}
|
||||||
0xE -> %Card{suit: suit, rank: :king}
|
0xE -> %Card{suit: suit, rank: :king}
|
||||||
end
|
end}
|
||||||
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()}
|
@spec from_string(String.t()) :: {:ok, Card.t()} | {:error, String.t()}
|
||||||
def from_string(card_rep) do
|
def from_string(card_rep) do
|
||||||
case card_rep do
|
case card_rep do
|
||||||
<<codepoint::utf8>> ->
|
<<codepoint::utf8>> when codepoint > 0x1F0A0 and codepoint < 0x1F0DF ->
|
||||||
offset = codepoint - ?🂠
|
offset = codepoint - ?🂠
|
||||||
suit = div(offset, 0x10)
|
suit = div(offset, 0x10)
|
||||||
rank = rem(offset, 0x10)
|
rank = rem(offset, 0x10)
|
||||||
|
|
@ -69,6 +155,43 @@ defmodule PokerEx.Card do
|
||||||
end
|
end
|
||||||
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
|
defimpl Inspect, for: Card do
|
||||||
@suit_letters %{spades: "S", hearts: "H", diamonds: "D", clubs: "C"}
|
@suit_letters %{spades: "S", hearts: "H", diamonds: "D", clubs: "C"}
|
||||||
@rank_letters %{ace: "A", jack: "J", queen: "Q", king: "K"}
|
@rank_letters %{ace: "A", jack: "J", queen: "Q", king: "K"}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,51 @@ defmodule PokerEx.Deck do
|
||||||
|
|
||||||
@type deck() :: [Card.t()]
|
@type deck() :: [Card.t()]
|
||||||
|
|
||||||
|
@doc ~S"""
|
||||||
|
Creates a new empty deck. This deck is sorted and contains all cards
|
||||||
|
2-10, jack, queen, king, and ace for ranks spades, hearts, diamonds, and clubs.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> deck = PokerEx.Deck.new()
|
||||||
|
iex> PokerEx.Deck.valid? deck
|
||||||
|
true
|
||||||
|
iex> length deck
|
||||||
|
52
|
||||||
|
iex> Enum.take(deck, 3)
|
||||||
|
[
|
||||||
|
%PokerEx.Card{suit: :spades, rank: 2},
|
||||||
|
%PokerEx.Card{suit: :spades, rank: 3},
|
||||||
|
%PokerEx.Card{suit: :spades, rank: 4}
|
||||||
|
]
|
||||||
|
"""
|
||||||
@spec new() :: deck()
|
@spec new() :: deck()
|
||||||
def new(), do: Card.all_suits() |> Enum.flat_map(&Card.all_cards_for_suit/1)
|
def new(), do: Card.all_suits() |> Enum.flat_map(&Card.all_cards_for_suit/1)
|
||||||
|
|
||||||
@spec new_shuffled() :: deck()
|
@spec new_shuffled() :: deck()
|
||||||
def new_shuffled(), do: new() |> Enum.shuffle()
|
def new_shuffled(), do: new() |> Enum.shuffle()
|
||||||
|
|
||||||
|
@doc ~S"""
|
||||||
|
Returns true if a deck is valid. A deck is valid if, and only if,
|
||||||
|
it contains valid cards. There is no limitation on the size.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> PokerEx.Deck.valid? PokerEx.Deck.new()
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PokerEx.Deck.valid? PokerEx.Deck.new_shuffled()
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PokerEx.Deck.valid? ["Haha!" | PokerEx.Deck.new()]
|
||||||
|
false
|
||||||
|
|
||||||
|
iex> PokerEx.Deck.valid? [%PokerEx.Card{suit: :diamonds, rank: 5}]
|
||||||
|
true
|
||||||
|
|
||||||
|
iex> PokerEx.Deck.valid? []
|
||||||
|
true
|
||||||
|
"""
|
||||||
@spec valid?(deck()) :: boolean
|
@spec valid?(deck()) :: boolean
|
||||||
def valid?(deck), do: deck |> Enum.all?(&Card.valid?/1)
|
def valid?(deck), do: deck |> Enum.all?(&Card.valid?/1)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
4
poker_ex/test/poker_ex/card_test.exs
Normal file
4
poker_ex/test/poker_ex/card_test.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule PokerEx.CardTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest PokerEx.Card
|
||||||
|
end
|
||||||
4
poker_ex/test/poker_ex/deck_test.exs
Normal file
4
poker_ex/test/poker_ex/deck_test.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule PokerEx.DeckTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
doctest PokerEx.Deck
|
||||||
|
end
|
||||||
Loading…
Add table
Reference in a new issue