DEV Community

Jeremy Ward πŸ˜ŽπŸ€“
Jeremy Ward πŸ˜ŽπŸ€“

Posted on

[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

Top comments (0)