├── test ├── test_helper.exs └── dojo │ └── hand_test.exs ├── .gitignore ├── lib ├── dojo.ex └── dojo │ ├── card.ex │ └── hand.ex ├── README.md ├── mix.exs ├── config └── config.exs └── card-hands.md /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | *.beam 7 | -------------------------------------------------------------------------------- /lib/dojo.ex: -------------------------------------------------------------------------------- 1 | defmodule Dojo do 2 | @moduledoc """ 3 | Documentation for Dojo. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> Dojo.hello 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/dojo/card.ex: -------------------------------------------------------------------------------- 1 | defmodule Dojo.Card do 2 | # Ranks of numbered cards are represented by the number (2..10) 3 | # Jack, Queen, King, Ace represented by 11, 12, 13, 14 respectively 4 | # Suits can be: :hearts, :diamonds, :clubs, :spades 5 | 6 | defstruct [:rank, :suit] 7 | end 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elixir cards dojo 2 | 3 | ## What 4 | 5 | We're going to be solving a card problem in Elixir. 6 | You will need to have [Elixir installed](https://github.com/dwyl/learn-elixir#how), 7 | or pair with someone who does. 8 | 9 | **Given five playing cards, evaluate the hand.** 10 | 11 | Have a quick look at the [possible hands](cards-hands.md) we'll be identifying. 12 | 13 | ## How 14 | 15 | * Find a pairing partner 16 | * `git clone` this repo 17 | * `cd elixir-dojo` 18 | * Run the tests (`mix test`) 19 | * Fix the failing tests by adding to [`hand.ex`](/lib/dojo/hand.ex) 20 | * Add [tests](/test/dojo) where relevant (the provided tests are not sufficient) 21 | * Open a Pull Request so we can discuss different approaches 22 | 23 | ## Why 24 | 25 | Level up your elixir skills! 26 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Dojo.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :dojo, 6 | version: "0.1.0", 7 | elixir: "~> 1.4", 8 | build_embedded: Mix.env == :prod, 9 | start_permanent: Mix.env == :prod, 10 | deps: deps()] 11 | end 12 | 13 | # Configuration for the OTP application 14 | # 15 | # Type "mix help compile.app" for more information 16 | def application do 17 | # Specify extra applications you'll use from Erlang/Elixir 18 | [extra_applications: [:logger]] 19 | end 20 | 21 | # Dependencies can be Hex packages: 22 | # 23 | # {:my_dep, "~> 0.3.0"} 24 | # 25 | # Or git/path repositories: 26 | # 27 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 28 | # 29 | # Type "mix help deps" for more examples and options 30 | defp deps do 31 | [] 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :dojo, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:dojo, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/dojo/hand.ex: -------------------------------------------------------------------------------- 1 | defmodule Dojo.Hand do 2 | 3 | def score(cards) do 4 | sorted_cards = Enum.sort(cards, fn c, n -> c.rank <= n.rank end) 5 | evaluate(sorted_cards) 6 | end 7 | 8 | defp evaluate(cards) do 9 | suits = Enum.map(cards, fn card -> card.suit end) 10 | ranks = Enum.map(cards, fn card -> card.rank end) 11 | 12 | cond do 13 | # royal flush: 14 | same_suit?(suits) && consecutive_ranks?(ranks) && Enum.at(ranks, 0) == 10 -> 15 | :royal_flush 16 | same_suit?(suits) && consecutive_ranks?(ranks) -> 17 | :straight_flush 18 | of_a_kind?(cards, 4) -> 19 | :four_of_a_kind 20 | of_a_kind?(cards, 3) && of_a_kind?(cards, 2) -> 21 | :full_house 22 | same_suit?(suits) -> 23 | :flush 24 | consecutive_ranks?(ranks) -> 25 | :straight 26 | of_a_kind?(cards, 3) -> 27 | :three_of_a_kind 28 | true -> 29 | :high_card 30 | end 31 | end 32 | 33 | def of_a_kind?(cards, x) do 34 | cards 35 | |> Enum.group_by(fn c -> c.rank end) 36 | |> Stream.map(fn {rank, cards} -> {rank, Enum.count(cards)} end) 37 | |> Enum.any?(fn {_rank, n} -> n == x end) 38 | end 39 | 40 | def same_suit?(suits) do 41 | first_suit = Enum.fetch!(suits, 0) 42 | Enum.all?(suits, fn suit -> suit == first_suit end) 43 | end 44 | 45 | def consecutive_ranks?([cur_rank, next_rank | rem_ranks]) do 46 | next_rank - cur_rank == 1 && consecutive_ranks?([next_rank | rem_ranks]) 47 | end 48 | def consecutive_ranks?(_), do: true 49 | end 50 | -------------------------------------------------------------------------------- /card-hands.md: -------------------------------------------------------------------------------- 1 | # Card hands 2 | 3 | 1. **Royal flush** 4 | A, K, Q, J, 10, all the same suit. 5 | 6 | ![royal flush](https://user-images.githubusercontent.com/1287388/27003994-72942f26-4df9-11e7-8390-dab0dedf56f5.png) 7 | 8 | 2. **Straight flush** 9 | Five cards in a sequence, all in the same suit. 10 | 11 | ![straight flush](https://user-images.githubusercontent.com/1287388/27003933-903a623a-4df8-11e7-957a-9b2c95dccc3e.png) 12 | 13 | 3. **Four of a kind** 14 | All four cards of the same rank. 15 | 16 | ![four of a kind](https://user-images.githubusercontent.com/1287388/27003937-a31f05cc-4df8-11e7-938b-1d7ab12c7fa0.png) 17 | 18 | 4. **Full house** 19 | Three of a kind with a pair. 20 | 21 | ![full house](https://user-images.githubusercontent.com/1287388/27003947-bae98fce-4df8-11e7-84d3-0d87bd244664.png) 22 | 23 | 5. **Flush** 24 | Any five cards of the same suit, but not in a sequence. 25 | 26 | ![flush](https://user-images.githubusercontent.com/1287388/27003949-d0806128-4df8-11e7-82df-0bf195b10738.png) 27 | 28 | 6. **Straight** 29 | Five cards in a sequence, but not of the same suit. 30 | 31 | ![straight](https://user-images.githubusercontent.com/1287388/27003961-e7c8ab92-4df8-11e7-82ed-c16d199682ab.png) 32 | 33 | 7. **Three of a kind** 34 | Three cards of the same rank. 35 | 36 | ![three of a kind](https://user-images.githubusercontent.com/1287388/27003967-0157f6ee-4df9-11e7-8694-cdd679a3eaa9.png) 37 | 38 | 8. **Two pair** 39 | Two different pairs. 40 | 41 | ![two pair](https://user-images.githubusercontent.com/1287388/27003973-16190758-4df9-11e7-9ed8-9c5f10f2a50a.png) 42 | 43 | 9. **Pair** 44 | Two cards of the same rank. 45 | 46 | ![pair](https://user-images.githubusercontent.com/1287388/27003977-2ef4b3da-4df9-11e7-8c40-a14872233660.png) 47 | 48 | 10. **High Card** 49 | When you haven't made any of the hands above, the highest card plays. 50 | 51 | ![high card](https://user-images.githubusercontent.com/1287388/27003985-48015518-4df9-11e7-9451-d923f3688b1c.png) 52 | -------------------------------------------------------------------------------- /test/dojo/hand_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Dojo.HandTest do 2 | use ExUnit.Case, async: true 3 | alias Dojo.Hand 4 | alias Dojo.Card 5 | doctest Dojo.Hand 6 | 7 | test "evaluates a straight flush" do 8 | cards = [ 9 | %Card{rank: 9, suit: :hearts}, 10 | %Card{rank: 10, suit: :hearts}, 11 | %Card{rank: 11, suit: :hearts}, 12 | %Card{rank: 12, suit: :hearts}, 13 | %Card{rank: 13, suit: :hearts} 14 | ] 15 | 16 | assert Hand.score(cards) == :straight_flush 17 | end 18 | 19 | test "evaluates flush of hearts" do 20 | cards = [ 21 | %Card{rank: 3, suit: :hearts}, 22 | %Card{rank: 9, suit: :hearts}, 23 | %Card{rank: 4, suit: :hearts}, 24 | %Card{rank: 8, suit: :hearts}, 25 | %Card{rank: 14, suit: :hearts} 26 | ] 27 | 28 | assert Hand.score(cards) == :flush 29 | end 30 | 31 | test "evaluates flush of spades" do 32 | cards = [ 33 | %Card{rank: 2, suit: :spades}, 34 | %Card{rank: 6, suit: :spades}, 35 | %Card{rank: 5, suit: :spades}, 36 | %Card{rank: 13, suit: :spades}, 37 | %Card{rank: 7, suit: :spades} 38 | ] 39 | 40 | assert Hand.score(cards) == :flush 41 | end 42 | 43 | test "does not evaluate flush for mismatching suits" do 44 | cards = [ 45 | %Card{rank: 10, suit: :diamonds}, 46 | %Card{rank: 11, suit: :diamonds}, 47 | %Card{rank: 12, suit: :diamonds}, 48 | %Card{rank: 13, suit: :clubs}, 49 | %Card{rank: 14, suit: :clubs} 50 | ] 51 | 52 | assert Hand.score(cards) != :flush 53 | end 54 | 55 | test "evaluates high card" do 56 | cards = [ 57 | %Card{rank: 2, suit: :spades}, 58 | %Card{rank: 3, suit: :hearts}, 59 | %Card{rank: 5, suit: :diamonds}, 60 | %Card{rank: 7, suit: :spades}, 61 | %Card{rank: 11, suit: :spades} 62 | ] 63 | 64 | assert Hand.score(cards) == :high_card 65 | end 66 | 67 | test "evaluates royal flush of diamonds" do 68 | cards = [ 69 | %Card{rank: 10, suit: :diamonds}, 70 | %Card{rank: 11, suit: :diamonds}, 71 | %Card{rank: 12, suit: :diamonds}, 72 | %Card{rank: 13, suit: :diamonds}, 73 | %Card{rank: 14, suit: :diamonds} 74 | ] 75 | 76 | assert Hand.score(cards) == :royal_flush 77 | end 78 | 79 | test "does not evaluate royal flush for mismatching suits" do 80 | cards = [ 81 | %Card{rank: 10, suit: :diamonds}, 82 | %Card{rank: 11, suit: :diamonds}, 83 | %Card{rank: 12, suit: :diamonds}, 84 | %Card{rank: 13, suit: :clubs}, 85 | %Card{rank: 14, suit: :clubs} 86 | ] 87 | 88 | assert Hand.score(cards) != :royal_flush 89 | end 90 | 91 | end 92 | --------------------------------------------------------------------------------