Add doctests for Card and Deck modules.

This commit is contained in:
Hans Goor 2024-08-12 19:52:58 +02:00
parent 21a0673ba2
commit 4210670e9e
Signed by: eyedevelop
SSH key fingerprint: SHA256:Td89veptDEwCV8J3fjqnknNk7SbwzedYhauyC2nFBYg
4 changed files with 174 additions and 4 deletions

View file

@ -1,4 +1,8 @@
defmodule PokerEx.Card do
@doc ~S"""
Holds functions that operate and symbolise a playing card.
"""
alias __MODULE__
@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
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
@ -38,7 +106,7 @@ defmodule PokerEx.Card do
end
def from_integers(_suit, rank)
when rank < 2
when rank < 1
when rank > 0xE do
{:error, "Invalid integer rank: #{rank}"}
end
@ -46,18 +114,36 @@ defmodule PokerEx.Card do
def from_integers(suit, rank) do
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}
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}
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>> ->
<<codepoint::utf8>> when codepoint > 0x1F0A0 and codepoint < 0x1F0DF ->
offset = codepoint - ?🂠
suit = div(offset, 0x10)
rank = rem(offset, 0x10)
@ -69,6 +155,43 @@ defmodule PokerEx.Card do
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"}

View file

@ -3,12 +3,51 @@ defmodule PokerEx.Deck do
@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()
def new(), do: Card.all_suits() |> Enum.flat_map(&Card.all_cards_for_suit/1)
@spec new_shuffled() :: deck()
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
def valid?(deck), do: deck |> Enum.all?(&Card.valid?/1)
end

View file

@ -0,0 +1,4 @@
defmodule PokerEx.CardTest do
use ExUnit.Case
doctest PokerEx.Card
end

View file

@ -0,0 +1,4 @@
defmodule PokerEx.DeckTest do
use ExUnit.Case
doctest PokerEx.Deck
end