Using Ruby Classes to Implement a Game of BlackJack
Ruby is an object-oriented programming language, meaning that it has classes and objects. A class is a model that we use to define the attributes and behaviors of an object. There are many examples of this, such as a car being an object with attributes like make, model, and color and behaviors like drive() and break(). The class of car determines the model used and an individual object would be, for example, my red Honda Civic. drive(false)
To better illustrate this I’m going to take an example that I used for a recent class project: designing a data-structure for a deck of 52 cards, using classes to implement BlackJack. I also saw this as an example of a coding challenge used for job interviews in Gayle Laakmann McDowell’s 6th Edition of Cracking the Coding Interview, so to say this is a popular example would be an understatement.
There were two parts to this game. The first was setting up classes for Player, Deck, and Card. The second was the actual game logic, which included terminal prompts and if statements that would run based on user input or win/lose scenarios. For the purposes of this article, I will be focusing on setting up the classes.
The first class I created was “Player”. I originally started by creating “Player” and “Dealer” classes, but quickly realized this was redundant.
I used “attr_accessor”, which is a Ruby method that allows me to update certain attributes of these objects (name, hand, bankroll and total) elsewhere in my program.
“def initialize” is a function that allows me to create a new object based on this model with the attributes provided.
In this case, “name” will be the name the user provides (or the default I create), “hand” will be an empty array, which we can later push card objects into, “bankroll” will be how much money the player currently has (they can win and lose money for each hand) and “total” will be the total value of the player’s hand. We will need this value later on to determine the win scenario: having the hand with the highest total value that doesn’t exceded 21.
As you can see, I started by creating a human player with an empty hand (empty array), $100 in the bank, and a total hand value of 0.
I also created a computer player with an empty hand (empty array), $10000 in the bank, and a total hand value of 0.
How can they play with empty hands? Don’t worry, remember attr_accessor? That means we can update these attributes later.
Next, we want to create our card class. This will be what we use to generate individual cards with attributes of face, suit, and value (remember, we need to know the value of the card to calculate the total value of the player’s hand).
So now that we have our “Card” class, we can start generating new cards as we did with players right?
Well, it’s not that simple. We need 52 cards to be generated, with 13 ranks and four of each rank, one of each suit. Imagine how tedious it would be if I had to manually create each card? Thankfully, I don’t have to. I can create another class called Deck that will generate these cards for us and then use that class to initialize my card objects. Each card will actually be created when a Deck is created and inherit its properties from the deck.
Now for the challenging part, initializing the deck. I started by simply creating three arrays: one for the faces, one for the suits, and one empty array to hold all the cards once they have been initialized.
You may notice that for the faces array I have a range of numbers with an asterisk in front of it. If you’re new to Ruby like I was, you may be unfamiliar with this method, but it’s called “splat” and it’s awesome! There are numerous uses for it, one of which is decomposing a range of numbers into separate list items.
Next, we need to create our deck of cards, but we need to ensure that we have four cards for each face value, one of every suit in our deck. We can accomplish this by iterating through each face and then for each face, iterating through each suit (a loop within a loop).
As we loop through each face value, I also included an if statement to determine the value of the card based on the face. If the face is a number or integer we want the value to be equal to that number. An Ace should have a value of 11 (we can change this value to 1 later for special cases). Every other face card (Kings, Queens, and Jacks) will have a value of 10.
Next, for each face, we will also loop through the four suits and create a new card.
Recognize the syntax? We are creating a new card the same way we created a new player, except this time we are doing it on a loop, so this one line of code will run 52 times and generate 52 card objects. We are using these << carrots to push each new card object into the “cards” property of the Deck object.
Now that we have our deck, we want the cards in it to be randomized. In a real card game, we would shuffle the deck. In Ruby, we can shuffle the array with @cards.shuffle! (the exclamation point at the end means that the original object will be permanently transformed.) Pretty cool, huh?
Now how do we deal these cards to our players and effectively add these cards to their hand arrays?
For this, I used two functions that work together, one inside of the Deck class called “deal” and another inside of the Card class called “generate_card”.
Let’s walk through the generate card function first.
For this function to work properly, I need to first know who is receiving the card, so I’ll require a parameter called “player”.
Using the same syntax as before, I’m going to create a new card with a face, suit, and value (which I will receive from the deck).
I’m next going to push the new card into the specified player’s hand
Finally, I need to update the total value for the player’s hand when I add the new card. I simply made the total equal the original total plus the new card value. I could have also written this (player.total += new_card.value).
Now, I’m going to call this function inside the deal function in my Deck class.
The deal function needs two parameters, the number of cards to deal and the player to deal them to. Instead of taking the number of cards dealt I could have just called this function the number of times in the game logic that I needed to, but writing it this way was a little drier.
I used Ruby’s shift method, which removes the first item from an array and returns the item, to effectively take a card out of the deck and put it in the player’s hand. I called the generate_card function here to create the new card for the player with the attributes of the card I removed from the deck.
num.times will determine the number of times this will happen.
Here is an example of how I called this function in the game logic.
I used deck.deal(2, human) to deal two cards to the human player. Then I could reference those cards below, by index 0 and 1. I used deck.deal(2, computer) to deal the computer cards and then reveal one of them to the player.
I used some logic, later on, to allow the player to “hit” or add more cards to their hand, but it was very simple to do because I could use the same deck.deal() function.
I hope you enjoyed this tutorial on how to set up a BlackJack game with Ruby classes. I may write a part two in the future to discuss the actual gameplay and console prompts.