AbstractPlayer Class

Overview

If we think about how to play the game of Go Fish!, there are certain pieces of information that all players will keep track of, regardless of the strategy they use to try to win the game. Each player will have a hand of cards, as well as a number of books that they have already collected. They also share several actions that they will all have to take, such as adding and removing cards from their hand, and reporting their score at the end of the game to determine the winner. When thinking about object oriented programming, these shared pieces of information and behaviors are excellent candidates for an abstract parent class.

In this second part of the lab, we will create an AbtractPlayer abstract class that implements the instance variables and methods that are shared by every type of player. In parts 3 and 4, we will later implement child classes called RandomPlayer and UserPlayer that implement different strategies for winning the game.

AbstractPlayer Description

Inside your GitHub repository, you will find a file called AbstractPlayer.java. This is where we will implement the AbstractPlayer abstract class that implements the provided Player interface. Open this file in Visual Studio Code to get started.

Given how much is shared between different types of Player’s, the AbstractPlayer abstract class will be able to implement most of the instance variables and methods needed for a Player object. First, the instance variables should include:

/** The {@link Player}'s hand of {@link Card}s. */
protected Card[] hand;

/** The number of books (i.e,, four-of-a-kind) the {@link Player} has acquired. */
protected int bookScore;

/** The {@link Player}'s number in the game. */
protected int playerNumber;

Here, we make all of the instance variables protected instead of private because the AbstractPlayer will be inherited, and protected allows all child classes to access the instance variables of its parent class.

If you open up the Player.java file in Visual Studio Code, you can see a list of all the methods (and Javadocs describing their purpose) needed to implement the Player interface. We will want to implement all of the methods of the Player interface inside the AbstractPlayer class, except for the decideRank method which will differ based on the game strategies used in different children classes. As such, the decideRank method can be declared as abstract in AbstractPlayer the class so that it must be implemented instead by the children classes:

public abstract int decideRank();

Below, we describe what should be implemented for each of the methods in AbstractPlayer.java.

AbstractPlayer Constructor

The responsibility of the AbstractPlayer constructor is to take in the player’s unique number (either 1 or 2) assigned by the game and save it in the playerNumber instance variable, as well as create default values for the hand (an empty array of length 0, signifying the player has no cards, yet) and bookScore (0) instance variables.

gainCards Method

The gainCards method takes as an argument an array of Cards which should be added to the player’s hand instance variable. This will require creating a new Card[] array, copying all the Cards from the hand instance variable into the new array, then copying all the Cards from the method argument cards into the new array, and finally assigning the new array to the hand instance variable. In future labs, we will see how this can be done differently with other data structures!

Hint

Be careful that your hand instance variable always has a length equal to the exact number of Cards in the AbstractPlayer’s hand. So if you add n cards, then the new length should be hand.length + n. And later if you remove m cards, then the new length should be hand.length - m.

requestCards Method

The requestCards method takes as an argument a rank of card requested by the opponent and should return an array of Cards from the player’s hand that match that rank. For example, if the opponent asks for cards with rank 2 and the player’s hand instance variable consists of three 2’s and one 5, then the method should return an array of length three with those three 2’s.

Before returning, this method should also remove any returned cards from the player’s hand instance variable. In the example of the previous paragraph, the player’s hand instance variable should only include the one 5 when the function returns since the three 2’s are being returned (and eventually given to the opponent by the game logic).

Hint

Removing cards from the player’s hand is also needed for the checkBooks method described below. Rather than implementing the same idea twice, it might be useful (although not required) to create a helper method private Card[] removeCards(int rank) that is responsible for this small bit of code, which can then be called by the other two methods.

Another shared functionality of both the requestCards and checkBooks methods is counting how many cards of a given rank the player holds in their hand. This could be another opportunity for a helper function protected int countCards(int rank) that returns a count of many cards in the hand instance variable have a given rank.

Note: we recommend using protected for this helper function instead of private since it might also be useful (but not required) for your UserPlayer in Part 4.

Since we are using Card[] arrays to store the collection of cards in the player’s hand, we will need to create a new array to store the remaining cards, then we can assign that new array to the hand instance variable to make sure the player’s hand is updated (similar to how we made a new array for the hand instance variable in the gainCards method.)

If the player does not have any of the requested rank currently in their hand, then the method should just return an empty Card[] array of length 0.

checkBooks Method

The checkBooks method is responsible for determining whether the player has collected a book of cards (i.e., four-of-a-kind). If the player does have a book, it should remove those four cards from the player’s hand instance variable and add 1 to their bookScore instance variable.

getNumCards and getBookScore Methods

These methods are simply accessors for the length of the hand instance variable and the value of the bookScore instance variable, respectively. They are needed since we made the instance variables protected to protect them from being edited by the GoFish class or other non-child classes.

Testing and Committing Your Progress

Unfortunately, because AbstractPlayer is an abstract class, it is difficult to test before we implement a child class. We will discuss in the next part how we can test this code, which you might want to read before completing this part. However, this shouldn’t stop you from saving your progress periodically to GitHub using the add, commit, and push commands with git.