diff --git a/poker_ex/.formatter.exs b/poker_ex/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/poker_ex/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/poker_ex/.gitignore b/poker_ex/.gitignore new file mode 100644 index 0000000..3906e35 --- /dev/null +++ b/poker_ex/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +poker_ex-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/poker_ex/README.md b/poker_ex/README.md new file mode 100644 index 0000000..37b0016 --- /dev/null +++ b/poker_ex/README.md @@ -0,0 +1,21 @@ +# PokerEx + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `poker_ex` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:poker_ex, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/poker_ex/lib/poker_ex.ex b/poker_ex/lib/poker_ex.ex new file mode 100644 index 0000000..a5e72cd --- /dev/null +++ b/poker_ex/lib/poker_ex.ex @@ -0,0 +1,18 @@ +defmodule PokerEx do + @moduledoc """ + Documentation for `PokerEx`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> PokerEx.hello() + :world + + """ + def hello do + :world + end +end diff --git a/poker_ex/lib/poker_ex/card.ex b/poker_ex/lib/poker_ex/card.ex new file mode 100644 index 0000000..038f809 --- /dev/null +++ b/poker_ex/lib/poker_ex/card.ex @@ -0,0 +1,98 @@ +defmodule PokerEx.Card do + 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] + + @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 + + @spec all_suits() :: [suit()] + def all_suits(), do: @valid_suits + + @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 + + @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 < 2 + when rank > 0xE do + {:error, "Invalid integer rank: #{rank}"} + end + + def from_integers(suit, rank) do + suit = Enum.at(@valid_suits, suit) + + case rank do + n when n >= 2 and n <= 10 -> %Card{suit: suit, rank: n} + 0xB -> %Card{suit: suit, rank: :jack} + 0xD -> %Card{suit: suit, rank: :queen} + 0xE -> %Card{suit: suit, rank: :king} + end + end + + @spec from_string(String.t()) :: {:ok, Card.t()} | {:error, String.t()} + def from_string(card_rep) do + case card_rep do + <> -> + offset = codepoint - ?🂠 + suit = div(offset, 0x10) + rank = rem(offset, 0x10) + + from_integers(suit, rank) + + _ -> + {:error, "Not a valid card glyph!"} + end + 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 diff --git a/poker_ex/lib/poker_ex/deck.ex b/poker_ex/lib/poker_ex/deck.ex new file mode 100644 index 0000000..5577570 --- /dev/null +++ b/poker_ex/lib/poker_ex/deck.ex @@ -0,0 +1,14 @@ +defmodule PokerEx.Deck do + alias PokerEx.Card, as: Card + + @type deck() :: [Card.t()] + + @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() + + @spec valid?(deck()) :: boolean + def valid?(deck), do: deck |> Enum.all?(&Card.valid?/1) +end diff --git a/poker_ex/lib/poker_ex/game.ex b/poker_ex/lib/poker_ex/game.ex new file mode 100644 index 0000000..7efebab --- /dev/null +++ b/poker_ex/lib/poker_ex/game.ex @@ -0,0 +1,3 @@ +defmodule PokerEx.Game do + +end diff --git a/poker_ex/mix.exs b/poker_ex/mix.exs new file mode 100644 index 0000000..556c3df --- /dev/null +++ b/poker_ex/mix.exs @@ -0,0 +1,28 @@ +defmodule PokerEx.MixProject do + use Mix.Project + + def project do + [ + app: :poker_ex, + version: "0.1.0", + elixir: "~> 1.17", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/poker_ex/test/poker_ex_test.exs b/poker_ex/test/poker_ex_test.exs new file mode 100644 index 0000000..78cb072 --- /dev/null +++ b/poker_ex/test/poker_ex_test.exs @@ -0,0 +1,8 @@ +defmodule PokerExTest do + use ExUnit.Case + doctest PokerEx + + test "greets the world" do + assert PokerEx.hello() == :world + end +end diff --git a/poker_ex/test/test_helper.exs b/poker_ex/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/poker_ex/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()