defmodule Poker do @doc """ Returns the best rank & hand out of hole cards and community cards. iex> Poker.best_hand("4c 5d", "3c 6c 7d Ad Ac") {{:straight, :"7"}, {{:"7",:d}, {:"6",:c}, {:"5",:d}, {:"4",:c}, {:"3",:c}}} """ def best_hand(hole_cards, community_cards) when is_binary(hole_cards) do best_hand(parse_hand(hole_cards), community_cards) end def best_hand(hole_cards, community_cards) when is_binary(community_cards) do best_hand(hole_cards, parse_hand(community_cards)) end def best_hand(hole_cards, community_cards) do cards = Tuple.to_list(hole_cards) ++ Tuple.to_list(community_cards) hand = comb(5, cards) |> Enum.sort_by(fn cards -> cards |> List.to_tuple |> hand_value end) |> Enum.reverse |> hd |> List.to_tuple {hand_rank(hand), sort_hand(hand)} end defp comb(0, _), do: [[]] defp comb(_, []), do: [] defp comb(m, [h|t]) do (for l <- comb(m-1, t), do: [h|l]) ++ comb(m, t) end @doc """ Compares two poker hands and returns 1, 0 or -1 when the first hand is respectively more valuable, equally valuable or less valuable than the second hand. iex> Poker.hand_compare("Ac Qd Ah As Kc", "Ac Ad Ah Kc Kc") -1 """ def hand_compare(hand1, hand2) when is_binary(hand1) do hand_compare(parse_hand(hand1), hand2) end def hand_compare(hand1, hand2) when is_binary(hand2) do hand_compare(hand1, parse_hand(hand2)) end def hand_compare(hand1, hand2) do r = hand_value(hand1) - hand_value(hand2) cond do r > 0 -> 1 r == 0 -> 0 r < 0 -> -1 end end @doc """ Returns hand value - a number than uniquely identifies a given hand. The bigger the number the more valuable a given hand is. iex> Poker.hand_value("Ac Kc Qc Jc Tc") 8014 """ def hand_value(str) when is_binary(str) do str |> parse_hand |> hand_value end def hand_value(hand) do case hand_rank(hand) do {:straight_flush, a} -> 8_000 + card_value(a) {:four_of_a_kind, _a, b} -> 7_000 + card_value(b) {:full_house, a, b} -> 6_000 + 15 * card_value(a) + card_value(b) {:flush, _r, a, b, c, d, e} -> 5_000 + card_value(a) + card_value(b) + card_value(c) + card_value(d) + card_value(e) {:straight, a} -> 4_000 + card_value(a) {:three_of_a_kind, a, b, c} -> 3_000 + 15 * card_value(a) + card_value(b) + card_value(c) {:two_pair, a, b, c} -> 2_000 + 15 * card_value(a) + 15 * card_value(b) + card_value(c) {:one_pair, a, b, c, d} -> 1_000 + 15 * card_value(a) + card_value(b) + card_value(c) + card_value(d) {:high_card, a, b, c, d, e} -> card_value(a) + card_value(b) + card_value(c) + card_value(d) + card_value(e) end end @doc """ iex> Poker.hand_rank("Ac Kc Qc Jc Tc") {:straight_flush, :A} iex> Poker.hand_rank("Kc Qc Jc Tc 9c") {:straight_flush, :K} iex> Poker.hand_rank("5c 4c 3c 2c Ac") {:straight_flush, :"5"} iex> Poker.hand_rank("Ac Ad Ah As Kd") {:four_of_a_kind, :A, :K} iex> Poker.hand_rank("Ac Ad Ah Kc Kd") {:full_house, :A, :K} iex> Poker.hand_rank("Kc Kd Kh Ac Ad") {:full_house, :K, :A} iex> Poker.hand_rank("Ac Qc Jc Tc 9c") {:flush, :c, :A, :Q, :J, :T, :"9"} iex> Poker.hand_rank("Ac Kc Qc Jc Td") {:straight, :A} iex> Poker.hand_rank("Kc Qc Jc Tc 9d") {:straight, :K} iex> Poker.hand_rank("5c 4c 3c 2c Ad") {:straight, :"5"} iex> Poker.hand_rank("Ac Ad Ah Kc Qc") {:three_of_a_kind, :A, :K, :Q} iex> Poker.hand_rank("Ac Ad Kc Kd Qc") {:two_pair, :A, :K, :Q} iex> Poker.hand_rank("Ac Ad Kc Qc Jd") {:one_pair, :A, :K, :Q, :J} iex> Poker.hand_rank("Ac Qc Jd Td 9c") {:high_card, :A, :Q, :J, :T, :"9"} """ def hand_rank(str) when is_binary(str) do parse_hand(str) |> hand_rank end def hand_rank(hand) do hand = sort_hand(hand) if is_straight(hand) do {{r1,_}, {r2,_}, _, _, _} = hand if r1 == :A && r2 == :"5" do r = :"5" else r = r1 end if is_flush(hand) do {:straight_flush, r} else {:straight, r} end else case hand do {{a,_}, {a,_}, {a,_}, {a,_}, {b,_}} -> {:four_of_a_kind, a, b} {{a,_}, {a,_}, {a,_}, {b,_}, {b,_}} -> {:full_house, a, b} {{a,_}, {a,_}, {b,_}, {b,_}, {b,_}} -> {:full_house, b, a} {{r1,a}, {r2,a}, {r3,a}, {r4,a}, {r5,a}} -> {:flush, a, r1, r2, r3, r4, r5} {{a,_}, {a,_}, {a,_}, {b,_}, {c,_}} -> {:three_of_a_kind, a, b, c} {{a,_}, {a,_}, {b,_}, {b,_}, {c,_}} -> {:two_pair, a, b, c} {{a,_}, {a,_}, {b,_}, {c,_}, {d,_}} -> {:one_pair, a, b, c, d} {{a,_}, {b,_}, {c,_}, {d,_}, {e,_}} -> {:high_card, a, b, c, d, e} end end end defp is_straight(str) when is_binary(str) do str |> parse_hand |> is_straight end defp is_straight({{a,_}, {b,_}, {c,_}, {d,_}, {e,_}}) do (card_value(a) == card_value(b) + 1 || a == :A && b == :"5") && card_value(b) == card_value(c) + 1 && card_value(c) == card_value(d) + 1 && card_value(d) == card_value(e) + 1 end defp is_flush({{_,a},{_,a},{_,a},{_,a},{_,a}}), do: true defp is_flush({_,_,_,_,_}), do: false defp card_value(:A), do: 14 defp card_value(:K), do: 13 defp card_value(:Q), do: 12 defp card_value(:J), do: 11 defp card_value(:T), do: 10 defp card_value(:"9"), do: 9 defp card_value(:"8"), do: 8 defp card_value(:"7"), do: 7 defp card_value(:"6"), do: 6 defp card_value(:"5"), do: 5 defp card_value(:"4"), do: 4 defp card_value(:"3"), do: 3 defp card_value(:"2"), do: 2 @doc """ Accepts a string and returns a tuple of cards. A card is a tuple of rank and suit. iex> Poker.parse_hand("Ac Kd") {{:A, :c}, {:K, :d}} """ def parse_hand(str) do str |> String.split(" ") |> Enum.map(&parse_card/1) |> List.to_tuple end defp parse_card(str) do [r, s] = String.codepoints(str) {String.to_atom(r), String.to_atom(s)} end defp sort_hand(hand) do hand |> Tuple.to_list |> Enum.sort_by(fn {rank,_} -> card_value(rank) end) |> Enum.reverse |> List.to_tuple end end