idursun

idursun

Deciding if a hand forms a gin or not?

I am trying to write a little function to determine whether a given hand of cards forms a gin.

Here are the rules:

  • A hand contains 10 cards from a standard 52-card deck.
  • A run is formed if it contains at least 3 cards or more of the same suit and their value increases without gaps.
  • A set is formed if it contains at least 3 or more cards of the same value.
  • A hand is a gin if every card in the hand either belongs to a set or a run.

I am curious to see alternative ways of coding this in idiomatic Elixir.

Most Liked

al2o3cr

al2o3cr

The tricky part is dealing with cards that could be in either a set or a run - for instance if you have 5/6/7/8 of diamonds, 5 of clubs and 5 of hearts.

I’d implement this check by first rewriting it into a recursive definition:

* A hand is a gin if:
  * there is a set of 4 cards, and one of the following:
    * the remaining cards are a gin
    * the remaining cards plus one of the 4 from the set are a gin
  * there is a set of 3 cards, and one of the following:
    * the remaining cards are a gin
    * the whole hand is a gin ignoring this set of 3 cards
  * there is a run of at least 3 cards, and:
    * the remaining cards are a gin
  * the hand is empty

Some of these clauses are trickier than others:

  • the case where a candidate set of four cards isn’t used as a set is ignored, since having four runs would require 12 cards!
  • the “not a set” branch for 3 cards needs to track which values have been tried as a set, or else the definition will loop forever

There’s a corresponding clause in GinCalc.gin? for all the bullet points above:

defmodule GinCalc do
  def gin?(hand, ignored \\ [])
  def gin?([], _), do: true
  def gin?(hand, ignored) do
    case split_set(hand, ignored) do
      {run, rest} when length(run) == 4 ->
        if gin?(rest, ignored) do
          true
        else
          Enum.any?(run, &gin?([&1 | rest], ignored))
        end
      {[{_, v} | _] = run, rest} when length(run) == 3 ->
        if gin?(rest, ignored) do
          true
        else
          gin?(hand, [v | ignored])
        end
      {nil, rest} ->
        case split_run(rest) do
          {nil, _} -> false
          {_, without_run} -> gin?(without_run, ignored)
        end
    end
  end

  def split_set(hand, ignored) do
    grouped = Enum.group_by(hand, &elem(&1, 1))

    set_key =
      grouped
      |> Map.keys()
      |> Enum.reject(& &1 in ignored)
      |> Enum.find(fn k -> length(grouped[k]) > 2 end)

    if set_key do
      set = grouped[set_key]

      {set, hand -- set}
    else
      {nil, hand}
    end
  end

  def split_run(hand) do
    sorted = Enum.sort(hand)

    candidate_run =
      Enum.reduce_while(sorted, [], fn
        {suit, value}, [{suit, prev_value} | _] = acc when value == prev_value + 1 ->
          {:cont, [{suit, value} | acc]}
        _, acc when length(acc) > 2 ->
          {:halt, acc}
        {suit, value}, _ ->
          {:cont, [{suit, value}]}
      end)

    if length(candidate_run) > 2 do
      {candidate_run, hand -- candidate_run}
    else
      {nil, hand}
    end
  end
end

GinCalc.split_set([{:h, 5}, {:h, 6}, {:h, 7}, {:s, 5}, {:d, 5}], []) |> IO.inspect()
GinCalc.gin?([{:h, 5}, {:s, 5}, {:d, 5}, {:c, 5}, {:h, 6}, {:h, 7}]) |> IO.inspect()

I used tuples to represent cards because they Enum.sort in a reasonable way (grouped into suits, then by number) and are easy to pattern-match, but using structs would only require light modifications.

joey_the_snake

joey_the_snake

What have you tried?

dpreston

dpreston

You might find this approach to ranking poker hands helpful. Playing Poker with Elixir (pt. 1)

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New

We're in Beta

About us Mission Statement