To get started in our collection of projects focused around card games, we will begin with Higher or Lower. Higher or Lower is a simple card game in which a deck of cards is shuffled and the player guesses if the value of the next card is going to be higher or lower than the one previously drawn from the deck.
To create a program that a user could use the computer needs to keep track of the following things:
- A deck of randomly ordered cards
- The previously played card
- The user's score
Let's tackle what is probably the biggest problem first; how to store a deck of cards in software. To break down this problem, let's first think about what a playing card represents. At the fundamental level, a card is an _"[object](/reference/object)"_
or entity that stores values, one of these values might be the weight of the card. For our purposes the card stores two values, the suit of the card and the face value of the card (you can think of cards as having a value from 1 - 13 where 11, 12 and 13 are face cards). With this concept, we can begin to think about how we might store the cards in the program. When you play the game the most important attribute to you is the face value of the card, with this in mind we can simplify the data we need to keep track of, allowing us to store all the information we need to know about a card in a single integer. This then allows us to treat the deck of cards as an array/collection of these intergers.
Now that we have all the building blocks required to start building our program let's start to put something together. At the start of the program, we will want to have a deck of cards that we can present to the user one at a time; there are many ways you could generate the deck of cards, the simplest solution would be to have a pre-defined array at the start of your program like this:
deck = [
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9,
10, 10, 10, 10,
11, 11, 11, 11,
12, 12, 12, 12,
13, 13, 13, 13
]
package higherorlower;
public class HigherOrLower {
private int[] deck = new int[]{
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9,
10, 10, 10, 10,
11, 11, 11, 11,
12, 12, 12, 12,
13, 13, 13, 13,
};
}
This solution is elegant as it requires very little in terms of computational work and is easy to see how it works, it does have a few limitations though. Imagine that a player wishes to play multiple games in succession, how would you refresh/repopulate the deck, as the current solution would require the user to restart the program each time they wish to play. A solution you may come up with would be to have two decks of cards, one which is generated like before and never gets changed, and the other which is then copied each time you need a new deck, for example:
baseDeck = [
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9,
10, 10, 10, 10,
11, 11, 11, 11,
12, 12, 12, 12,
13, 13, 13, 13
]
deck = []
function startGame() {
deck = baseDeck
}
package higherorlower;
import java.util.Arrays;
public class HigherOrLower {
private final int[] BASE_DECK = new int[]{
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
4, 4, 4, 4,
5, 5, 5, 5,
6, 6, 6, 6,
7, 7, 7, 7,
8, 8, 8, 8,
9, 9, 9, 9,
10, 10, 10, 10,
11, 11, 11, 11,
12, 12, 12, 12,
13, 13, 13, 13,
};
private int[] deck;
private void startGame() {
deck = Arrays.copyOf(BASE_DECK, BASE_DECK.length);
}
}
This solution is much better as it allows the user to get a new deck without having to restart the program, though it still has some limitations. For example, imagine that the user feels that the game is too easy when there is only one deck of cards at play, so they wish to be able to play with any number of decks. How would you achieve this? You could concatenate the baseDeck
n number of times onto deck
, this would achieve the result, but there is another, arguable more elegant and less labour intensive solution. You could create a function called generateDeck
that takes in the n number of decks of cards you wish your deck to have, such a function might look something like this.
I am also going to introduce the concept of [lists and collections](/reference/lists-and-collections)
. These are incredibly useful tools as they allow us to have arrays that are not of a fixed length, they also provide the ability to insert and remove elements at any index in the list/collection
, a handy feature.
function generateDeck(decks) {
deck = []
for (cardValue between 1 and 13) {
for (number of suits * number of decks) {
deck.add(cardValue)
}
}
}
package higherorlower;
import java.util.ArrayList;
import java.util.List;
public class HigherOrLower {
private final int NUMBER_OF_SUITS = 4;
private List<Integer> generateDeck(int decks) {
// Create new deck
List<Integer> deck = new ArrayList<Integer>();
// For all card values
for (int cardValue = 1; cardValue <= 13; cardValue++) {
// For each suit and deck
for (int i = 0; i < NUMBER_OF_SUITS * decks; i++) {
deck.add(cardValue);
}
}
return deck;
}
}
Now that we have our deck of cards, we need to think about how we are going to shuffle it. There are a few approaches you can take here; the first is to shuffle the deck immediately after you generate it, placing each card in a random location. Doing this without using a list/collection
would prove difficult as you would have to keep track of what elements you've removed, whereas using the list
you can remove the elements freely. Sadly most programming languages don't provide you with the utility to randomly order the elements of a list
so we'll have to do it ourselves. Shuffling at its core is the act of taking a card from one location and putting it into another, with this in mind we can consider shuffling to be moving a randomly selected card from deck a and placing it in deck b. Before we can move the cards between the decks, we need to pick a card, so we need a random number to represent the index of the card we want to remove. This random number needs to be between 0 and the number of cards left in the deck - 1, we can achieve this by taking a random number between 0 and 1, and then multiplying it by the number of cards left in the deck.
cardIndex = random() * numberOfCardsInDeck
int cardIndex = (int) Math.floor(Math.random() * this.deck.size());
Using this approach, we can then apply it to the entire deck producing a shuffle
function that might look something like this:
function shuffle() {
shuffled = []
while (deckSize > 0) {
cardIndex = random() * numberOfCardsInDeck
shuffled.add(deck.remove(cardIndex))
}
deck = shuffled
}
private void shuffle() {
// Create new deck to put shuffled cards into
List<Integer> shuffled = new ArrayList<>();
// While there are cards remaining, keep extracting them to be placed in shuffled
while (this.deck.size() > 0) {
// Pick random card index from the deck
int cardIndex = (int) Math.floor(Math.random() * this.deck.size());
// Remove card from deck and add to shuffled
shuffled.add(deck.remove(cardIndex));
}
// Replace the now empty deck with the shuffled deck
this.deck = shuffled;
}
Now that we have a shuffled deck playing the game is now as simple as taking the first element of the deck each time we want a new card. There is, however, another approach which might be more suited to this game as it reduces the complexity. It is possible to create a function that will produce a random card from the deck without having to pre-shuffle it. We can reuse a lot of the logic we've previously discussed, and instead of taking each random card we selected on line 8 of the previous example, we can just remove that card from the deck and return it to be played, like this:
function pickCard() {
cardIndex = random() * numberOfCardsInDeck
return deck.remove(cardIndex)
}
private int pickCard() {
// Pick random card index from the deck
int cardIndex = (int) Math.floor(Math.random() * this.deck.size());
// Remove card from deck and return it
return this.deck.remove(cardIndex);
}
Now that the hard part is out of the way, we can now focus on the remaining two parts of the program. Firstly we need to start the game with a card and then continue to remember the previously played card for future comparison. Adding this will leave you with a program that lhopefully ooks something like this:
deck = []
previousCard
function generateDeck(decks) {
deck = []
for (cardValue between 1 and 13) {
for (number of suits * number of decks) {
deck.add(cardValue)
}
}
}
function pickCard() {
cardIndex = random() * numberOfCardsInDeck
return deck.remove(cardIndex)
}
function start() {
generateDeck(1)
previousCard = pickCard()
}
package higherorlower;
import java.util.ArrayList;
import java.util.List;
public class HigherOrLower {
private final int NUMBER_OF_SUITS = 4;
private List<Integer> deck;
private int previousCard;
private void generateDeck(int decks) {
// Create new deck
this.deck = new ArrayList<Integer>();
// For all card values
for (int cardValue = 1; cardValue <= 13; cardValue++) {
// For each suit and deck
for (int i = 0; i < NUMBER_OF_SUITS * decks; i++) {
this.deck.add(cardValue);
}
}
}
private int pickCard() {
// Pick random card index from the deck
int cardIndex = (int) Math.floor(Math.random() * this.deck.size());
// Remove card from deck and return it
return deck.remove(cardIndex);
}
private void start() {
this.generateDeck(1);
this.previousCard = this.pickCard();
}
public static void main(String[] args) {
HigherOrLower main = new HigherOrLower();
main.start();
}
}
The final requirement of this program is giving the user the ability to interact with it via the console. We need to be able to tell the user what card was picked from the deck, ask if they think the next card will be higher or lower and tell them if they were successful.
To do this, your start
function might look something like this:
function start() {
generateDeck(1)
previousCard = pickCard()
print("Your starting card is: ", previousCard)
while (cardsInDeck) {
print("Do you think the next card will be (h)igher or (l)ower?")
guess = input()
nextCard = pickCard()
if (guess is 'h' and nextCard is greater than or equal to previousCard) {
print("Correct, the next card was ", nextCard)
}
else if (guess is 'l' and nextCard is less than or equal to previousCard) {
print("Correct, the next card was ", nextCard)
} else {
print("Incorrect, the next card was ", nextCard)
}
previousCard = nextCard
}
}
private void start() {
this.generateDeck(1);
this.previousCard = this.pickCard();
System.out.println("Your starting card is: " + this.previousCard);
Scanner scanner = new Scanner(System.in);
while (this.deck.size() > 0) {
System.out.println("Do you think the next card will be (h)igher or (l)ower?");
String guess = scanner.next();
int nextCard = this.pickCard();
if (guess.equals("h") && nextCard >= this.previousCard) {
System.out.println("Correct, the next card was " + nextCard);
} else if (guess.equals("l") && nextCard <= this.previousCard) {
System.out.println("Correct, the next card was " + nextCard);
} else {
System.out.println("Incorrect, the next card was " + nextCard);
}
this.previousCard = nextCard;
}
}
Combing this with what we have previously built, you should now have a functional Higher or Lower game that you can now play. Though we have met all of the requirements we listed out before, there are many things we can still improve upon, for example, we could keep track of the user's score and present them with a total at the end of the game. We could handle inputs from the user that are not 'h' or 'l' and allow them to try again without penalty. I'm going to leave exploring these possibilities as an exercise in thinking about [defensive programming](/references/defensive-programming)