Ruby Basics: Creating A Simple CLI Game

Mickey Vershbow
8 min readJun 19, 2021

Hello, travelers. Here we will learn the basics of Ruby syntax, as well as many fundamental skills of programming by creating a simple CLI game of blackjack. This will be fairly simple and straightforward, using variables, control flow, and classes/objects to create the deck of cards, the players, and the game logic. This tutorial will walk you through creating the game from start to finish, including project setup, so beginners should be able to follow along.

On the code editor of your choice, open a new empty folder that you’d like to use for this project. Once you’re there, create a new file called main.rb. We’ll be writing all the code for the game in this file.

OVERVIEW

Here’s an overview of what we’re looking to build:

  • A two-player CLI game of blackjack where each player is dealt two cards, and typical rules of blackjack apply to determine the winner.
  • The human player starts off with a bankroll of 100, and the dealer starts out with 10,000. Each round is worth $10, and players can also set their own custom bets.
  • The player can choose whether to “hit” or “stand”.
  • The player can choose whether to end the game or play another round.
  • The console prints informative messages that help the player navigate the game and understand what’s happening.

GAME SETUP

Creating The Deck

We need to make two classes: a single Card, and a Deck. Once we’ve made the card, we can populate the deck with several card objects. In that sense, the deck will really be an object made up of other objects. Let’s begin.

Start by creating the Card class:

# CARD CLASS REPRESENTS A SINGLE CARD OBJECTclass Cardattr_accessor :suit
attr_accessor :face
attr_accessor :value
def initialize (suit, face, value)
@suit = suit
@face = face
@value = value
end
end

We created the class “Card” and initialized three properties: @suit, @face, @value. We made those properties accessible to other methods using the attr_accessor_ method. Lastly, we defined suit, face, and value as parameters so that we can pass specific properties as arguments when we create each Card object. Be aware that, just like in Javascript, we must pass properties in the same order as they were defined in the parameters, as the computer will read each argument’s value and assign it to those parameters in order.

Creating The Deck Class

Next we need to create and populate a deck with 52 card objects. Each suit has 13 cards: 2–10, Jack, Queen, King, Ace. We’ll define Ace as ‘1’ for now to keep it simple. Our job is to create four sets of those thirteen cards, and assign each set to one of the four suits. In order to do this, we will initialize three arrays:

  1. An empty @cards array to push cards into as we create them. This must be accessible.
  2. An array of suits (hearts, clubs, spades, diamonds).
  3. An array of faces / values.
# DECK CLASS REPRESENTING A DECK OF CARDSclass Deckattr_accessor :cardsdef initialize
@cards = []
suits = ["Hearts", "Diamonds", "Spades", "Clubs"]
faces = [
["Ace", 1],
["Two", 2],
["Three", 3],
["Four", 4],
["Five", 5],
["Six", 6],
["Seven", 7],
["Eight", 8],
["Nine", 9],
["Ten", 10],
["Jack", 10],
["Queen", 10],
["King", 10]
]

As we defined each face, we also passed a second argument that will be read as the value of that face. Remember, we’re passing the arguments as suit, face, value. Now we’ve got 13 cards, time to loop through them to create the deck!

Looping Through Cards To Populate The Deck

How do we assign one of each face to each suit, you ask? A loop within a loop, of course! We will loop through the suits array, and then loop through the faces array, and on each iteration of the nested loop we will create a new card, pass it a suit, face, and value, and push it into the @cards array.

Tip: write this inside the initialize method beneath the arrays we just created.

suits.each do |s|
faces.each do |f|
@cards.push(Card.new(s, f[0], f[1]))
end
end

Let’s break this down: the classic Ruby .each, largely considered the most important loop in Ruby, is an incredibly simple — and powerful — way to run a ForEach loop. It allows you to loop through a list of items without having to keep track of the number of iterations or define a counter. Essentially, the loop will “repeat until done”.

In plain English, here’s what this loop is doing:

  • First it loops through the suits array, and then it loops through the faces array.
  • For each individual (s)uit and (f)ace — passed here using a |block| — we’re creating a new Card object and passing it a suit, face, and value. Lastly, once we’ve created the new card object, we push it into the @cards array.
  • Wondering about the f[0] and f[1]? Take a look at how we defined the faces and values in our faces array. f[0] references the element in the first index position (face, i.e. “queen”) and the f[1] references the element in the second index position (value, i.e. “10”).

Shuffle The Deck

Let’s add a few lines to shuffle the deck. Ruby makes this outrageously easy — we can literally use the “shuffle!” method:

suits.each do |s|
faces.each do |f|
@cards.push(Card.new(s, f[0], f[1]))
end
end
@cards.shuffle!
@cards.shuffle!
@cards.shuffle!
end

Note that we’re using the bang version of the shuffle method, as we want to actually modify the original array. If we wrote @cards.shuffle instead of @cards.shuffle!, we would be able to print the the shuffled version of the array, but the original @cards array would remain unmodified.

Also note that end at the bottom of this code block ends the initialize method.

At this point, it may be a good idea to print the @cards.shuffle! array to make sure it’s what we’re expecting. Edit your code so it looks like this:

@cards.shuffle!
@cards.shuffle!
puts @cards.shuffle!

When you run your code in the terminal using the command ruby <fileName.rb>, it should output a shuffled deck of 52 cards.

This is an essential skill for any programmer: don’t make assumptions about what the result of your code will be. Test your code, ask questions, make sure the result is what you were expecting or you will potentially end up having a miserable time trying to find the source of a bug down the line.

The ‘Player’ Class

Create the Player class

Now that we’ve created our individual cards and populated the deck, all that’s left for game setup is creating the actual players. Let’s start by making a player class with accessible @hand, @name, and @bankroll properties:

# PLAYER CLASS REPRESENTING A PLAYER'S HANDclass Playerattr_accessor :hand
attr_accessor :name
attr_accessor :bankroll
def initialize
@hand = []
end
end

Let’s create and print two players objects — remember, objects are instances of a class, so our player objects are instances of the Player class — and pass them a name and bankroll. We’ll also create a deck. This is just to confirm that all our variables are what we’re expecting them to be:

p human = Player.new "Human", 100
p dealer = Player.new "The House (dealer)", 10000
p deck = Deck.new

Your console should output two new players and a shuffled deck of cards. We’re passing them each a name as the first argument, and a starting bankroll as the second argument. Once you’ve confirmed your code is working, you can delete the “p” at the beginning of those lines, as we don’t need to print those details in the console every time we run the file.

Create A Method To Deal Cards to Players

Alright, we’ve got our players and our deck. Let’s create a method to deal two cards to each player. We will use the classic array methods “pop” (to remove a card from the deck) and “push” (to push that card into the player’s @hand array). We’ll need to add an argument that allows us to pass a specific players hand to the method:

def deal_card(h)
h.player.push(@cards.pop)
end

Now we also need a method to sum the total of a player’s hand. In order to do this, we’ll need to use the reduce method, which reduces the code inside an array to a single line:

def sum_hand
@hand.reduce(0) { |t, c| t + c.value}
end

The (0) sets the initial value. t stands for total and c stands for card. Be sure to read up on this method to make sure you understand it!

If you wanted to test this function, you could write a test version outside the Player class:

def sum_hand_test(array)
array.reduce(0) { |t, c| t + c}
end
p sum_hand_test([2, 10])
# => 12

Determine The Winner

As we know, computers are rather “pedantic beasts” that require us to provide line-by-line instruction for everything we want it to execute. We’ve created a method to sum the total value of player and dealer’s cards, so now we need to create a method that determines who’s hand is the winning value.

# Function to determine win or lose. Receives two players as arguments. In our case, human and dealer.def win(h, d)
human_total = h.sum_hand
dealer_total = d.sum_hand
if human_total == dealer_total
p "It's a tie!"
else
return false if human_total > 21
return true if dealer_total > 21
return true if human_total == 21
return true if human_total > dealer_total
return false
end
end

GAME LOGIC

Congrats, little Yoda! You’ve created your classes and objects, as well as several useful functions for dealing and summing up cards. Now we need to start writing the game logic and script, as well as additional functions for determining the winner and adjusting the bankroll accordingly.

Let’s start by simply asking the user to input their name. Then they’ll be able to choose whether to play a round or check their bankroll.

def player_name
puts "What is your name?"
gets.chomp
end
puts "Welcome, #{player_name}! Do you want to [p]lay a round or [c]heck your bankroll?"input = gets.chomp

If you haven’t seen gets.chomp before, I’d highly suggest taking some time to read about it. The short explanation is that gets allows us to receive user input and store it in a variable, and .chomp removes the /n (new line) character that would normally be present at the end of the string that the user inputted. For example, if I run the above code, the terminal will ask me to input a name. Once I enter a name, I will receive the message “Ok <name>, let’s play a round!”. If my code just used name = gets instead of name = gets.chomp, then the output would have been:

"Ok <name>
, let's play a round!

So we use interpolation to invoke player_name and access the users’ input, how cool is that! Remember: Ruby automatically returns the last line of the function, so we don’t need a return keyword. Next we ask the user if they want to play a round or check their bankroll. Notice the way we use array brackets to indicate to the user that they should type “p” if they want to play a round, and so forth. Next we’ll write the logic for those inputs:

# ------- CHECK BANKROLL --------if input == "c"
p "Your bankroll is currently at $#{human.bankroll}."
end
# ------- PLAY A ROUND -- DEFAULT OR CUSTOM BET? --------if input == "p"
p "Do you want to be the [d]efault amount of $10, or [a]nother amount?"
input = gets.chomp

Default or Custom Bet

# ---------- DEFAULT BET --------- #if input == "d"
user_bet = 10
end
# ---------- CUSTOM BET --------- #if input == "a"
p "How much would you like to bet? Type any amount less than or equal to your current bankroll ($#{human.bankroll})"
bet = gets.chomp user_bet = bet.to_i
end

Infinite Loop

Now that we’ve got the player name and bet sorted, let’s start building the infinite game loop. We’ll use a while(true)loop. This essentially translates to “run the loop while true is true”, thus, our “infinite loop”. We’ll give the user the option to quit the game and therefore end the loop.

Part Two Coming Soon!

If you were following this tutorial and need to see the rest of the code, check it out on Github.

--

--

Mickey Vershbow
Mickey Vershbow

Written by Mickey Vershbow

0 Followers

Hi, I'm Mickey! I'm a full-stack software engineer with a background in music education. You can check out my work at www.mickeyvershbow-code.com

No responses yet