|
| 1 | +--- |
| 2 | +title: '[WIP] Creating a Blackjack CLI game with Elixir' |
| 3 | +published: true |
| 4 | +description: On day 6 of my 100 days of code, I was able to make some head way with my CLI Blackjack game with elixir. |
| 5 | +tags: 100daysofcode, webdev, elixir |
| 6 | +cover_image: |
| 7 | +datePosted: '2021-02-19' |
| 8 | +--- |
| 9 | + |
| 10 | +## Day 6 of 100 Days of Code |
| 11 | + |
| 12 | +Today I was able to work on my [Blackjack CLI](https://github.com/basicBrogrammer/blackjack_cli) written in elixir. It's starting to shape up, but it still feels a little off. Here's a quick demo: |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | +## Update to the user experience |
| 17 | + |
| 18 | +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. |
| 19 | + |
| 20 | +```elixir |
| 21 | +defmodule Card do |
| 22 | + ... |
| 23 | + def value(%{value: "J"}), do: 10 |
| 24 | + def value(%{value: "Q"}), do: 10 |
| 25 | + def value(%{value: "K"}), do: 10 |
| 26 | + def value(%{value: "A"}), do: 11 |
| 27 | + def value(%{value: value}), do: value |
| 28 | + ... |
| 29 | +end |
| 30 | +``` |
| 31 | + |
| 32 | +Next, I added a flash to displaying the cards by adding a white background and painting the card according to the suit. |
| 33 | + |
| 34 | +```elixir |
| 35 | +defmodule Card do |
| 36 | + ... |
| 37 | + def display(%{suit: suit, value: value}) do |
| 38 | + IO.ANSI.color_background(15) <> IO.ANSI.color(color_code(suit)) <> "#{suit}#{value}" <> IO.ANSI.reset() |
| 39 | + end |
| 40 | + |
| 41 | + defp color_code("♥"), do: 9 |
| 42 | + defp color_code("♦"), do: 9 |
| 43 | + defp color_code("♣"), do: 0 |
| 44 | + defp color_code("♠"), do: 0 |
| 45 | +end |
| 46 | +``` |
| 47 | + |
| 48 | +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? |
| 49 | + |
| 50 | +Now, let's take a look at the changes to the Game module. |
| 51 | + |
| 52 | +```elixir |
| 53 | +defmodule Game do |
| 54 | + # Added the "finished_players" attribute to the %Game struct. When a user |
| 55 | + # finishes their turn, they will be removed from the players list and placed |
| 56 | + # into the finished_players list. |
| 57 | + defstruct players: [%Player{name: "player_one"}, %Player{name: "dealer"}], cards: Deck.generate(), finished_players: [] |
| 58 | + |
| 59 | + # After all users are removed from the players list, we can pattern match on |
| 60 | + # an empty players attr and finish the game. |
| 61 | + def play(%{players: []} = game) do |
| 62 | + game |> inspect_table() |
| 63 | + end |
| 64 | + |
| 65 | + # If there are still players, the play function logic will be a little |
| 66 | + # different, but with the pipe operator its pretty easy to follow. |
| 67 | + def play(game) do |
| 68 | + game |
| 69 | + |> deal() |
| 70 | + |> inspect_table() |
| 71 | + |> turn() |
| 72 | + |> play |
| 73 | + end |
| 74 | + |
| 75 | + # To handle a players turn, we just ask them if they want to Hit or Stay, and |
| 76 | + # build a new game struct accordingly. |
| 77 | + def turn(%{players: [current | players]} = game) do |
| 78 | + IO.puts "\n#{current.name} your move." |
| 79 | + case IO.gets("(H)it or (S)tay\n") |> String.downcase |> String.trim do |
| 80 | + "hit" -> deal_card(game, current) |
| 81 | + "h" -> deal_card(game, current) |
| 82 | + "stay" -> %{game | players: players, finished_players: [current | game.finished_players]} |
| 83 | + "s" -> %{game | players: players, finished_players: [current | game.finished_players]} |
| 84 | + _ -> game |
| 85 | + end |
| 86 | + end |
| 87 | + |
| 88 | + # Inspecting the table clears out the terminal, then displays info about for |
| 89 | + # the current state of the game |
| 90 | + defp inspect_table(%{players: players, finished_players: f_players} = game) do |
| 91 | + IO.write IO.ANSI.reset() <> IO.ANSI.clear() <> IO.ANSI.home() |
| 92 | + if length(players) == 0 do |
| 93 | + IO.puts "Game Over." |
| 94 | + end |
| 95 | + for player <- f_players ++ players do |
| 96 | + IO.puts "#{player.name}'s\n hand: #{Player.display_hand(player)} | #{player.total}" |
| 97 | + end |
| 98 | + game |
| 99 | + end |
| 100 | +end |
| 101 | +``` |
| 102 | + |
| 103 | +## Implement the Dealer logic |
| 104 | + |
| 105 | +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: |
| 106 | + |
| 107 | +```elixir |
| 108 | + def turn(%{players: [%Dealer{} = dealer | players]} = game) do |
| 109 | + cond do |
| 110 | + dealer.total >= 17 -> %{game | players: players, finished_players: [dealer | game.finished_players]} |
| 111 | + true -> deal_card(game, dealer) |
| 112 | + end |
| 113 | + |> inspect_table() |
| 114 | + |> (fn (game) -> |
| 115 | + IO.gets("Press enter to continue.") |
| 116 | + game |
| 117 | + end).() |
| 118 | + end |
| 119 | +``` |
| 120 | + |
| 121 | +## What's next? |
| 122 | + |
| 123 | +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. |
| 124 | + |
| 125 | +I will prolly move on to the advance section of elixirschool.com, and come back to this repo later on. |
| 126 | + |
| 127 | +## Follow Me @ |
| 128 | + |
| 129 | +[Twitter](https://twitter.com/basicbrogrammer) | [Instagram](https://instagram.com/basicbrogrammer) |
0 commit comments