DEV Community

Jeremy Ward 😎🤓
Jeremy Ward 😎🤓

Posted on

1

[WIP] Creating a Blackjack CLI game with Elixir

Day 6 of 100 Days of Code

Today I was able to work on my Blackjack CLI written in elixir. It's starting to shape up, but it still feels a little off. Here's a quick demo:

blackjack demo

Update to the user experience

First I took advantage of elixir's pattern matching in functions to create a set of functions to calculate the value for a given card.

defmodule Card do
  ...
  def value(%{value: "J"}), do: 10
  def value(%{value: "Q"}), do: 10
  def value(%{value: "K"}), do: 10
  def value(%{value: "A"}), do: 11
  def value(%{value: value}), do: value
  ...
end
Enter fullscreen mode Exit fullscreen mode

Next, I added a flash to displaying the cards by adding a white background and painting the card according to the suit.

defmodule Card do
  ...
  def display(%{suit: suit, value: value}) do
    IO.ANSI.color_background(15) <> IO.ANSI.color(color_code(suit)) <> "#{suit}#{value}" <> IO.ANSI.reset()
  end

  defp color_code("♥"), do: 9
  defp color_code("♦"), do: 9
  defp color_code("♣"), do: 0
  defp color_code("♠"), do: 0
end
Enter fullscreen mode Exit fullscreen mode

Another bit of flare I added was chaning out the Deck @suits constant with the unicode icons for each suit. It's the little things you know?

Now, let's take a look at the changes to the Game module.

defmodule Game do
  # Added the "finished_players" attribute to the %Game struct. When a user
  # finishes their turn, they will be removed from the players list and placed
  # into the finished_players list.
  defstruct players: [%Player{name: "player_one"}, %Player{name: "dealer"}], cards: Deck.generate(), finished_players: []

  # After all users are removed from the players list, we can pattern match on
  # an empty players attr and finish the game.
  def play(%{players: []} = game) do
    game |> inspect_table()
  end

  # If there are still players, the play function logic will be a little
  # different, but with the pipe operator its pretty easy to follow.
  def play(game) do
    game
    |> deal()
    |> inspect_table()
    |> turn()
    |> play
  end

  # To handle a players turn, we just ask them if they want to Hit or Stay, and
  # build a new game struct accordingly.
  def turn(%{players: [current | players]} = game) do
    IO.puts "\n#{current.name} your move."
    case IO.gets("(H)it or (S)tay\n") |> String.downcase |> String.trim do
      "hit" -> deal_card(game, current)
      "h" -> deal_card(game, current)
      "stay" -> %{game | players: players, finished_players: [current | game.finished_players]}
      "s" -> %{game | players: players, finished_players: [current | game.finished_players]}
      _ -> game
    end
  end

  # Inspecting the table clears out the terminal, then displays info about for
  # the current state of the game
  defp inspect_table(%{players: players, finished_players: f_players} = game) do
    IO.write IO.ANSI.reset() <> IO.ANSI.clear() <> IO.ANSI.home()
    if length(players) == 0 do
      IO.puts "Game Over."
    end
    for player <- f_players ++ players do
      IO.puts "#{player.name}'s\n  hand: #{Player.display_hand(player)} |  #{player.total}"
    end
    game
  end
end
Enter fullscreen mode Exit fullscreen mode

Implement the Dealer logic

In regular Blackjack, the deal has to hit if they have less than 17. I decided to replace %Player{name: "dealer"} with a %Dealer struct. With that in place I can use pattern matching again for the Game.turn/1 function which looks a little like this:

  def turn(%{players: [%Dealer{} = dealer | players]} = game) do
    cond do
      dealer.total >= 17 -> %{game | players: players, finished_players: [dealer | game.finished_players]}
      true -> deal_card(game, dealer)
    end
    |> inspect_table()
    |> (fn (game) ->
      IO.gets("Press enter to continue.")
      game
    end).()
  end
Enter fullscreen mode Exit fullscreen mode

What's next?

If I want to finish Blackjack CLI, I would have to handle Aces. Aces can have a value of 1 or 11. This shouldn't be to hard, but might take some time. Other than that, It would be nice to choose how many decks could be played, and play to a certain percentage of the deck before regenerating the game.

I will prolly move on to the advance section of elixirschool.com, and come back to this repo later on.

Follow Me @

Twitter | Instagram

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay