The goal of this lab is to introduce you to basic
concepts in the C++ program language. Concepts you will be familiar with
after this lab include:
- object-oriented programming in C++
- inheritance and polymorphism
- defensive programming (e.g., detecting improper input)
- designing and compiling multi-file programs
A skeleton version the program will appear in your
cs35/labs/03 directory when you run update35. The
program handin35 will only submit files in this directory.
Partners will again be assigned for this lab. You will be working with a new partner for this lab. You can find your
new partner in this file. Both partners should be present
and working on the code together. You will both be responsible
for understanding all concepts, so dividing and conquering is not an
option. The academic integrity policy applies to the entire pair; you
cannot work or share code with anyone outside of your partner. Also,
be sure to indicate who your partner is by including both names in the
header and by also selecting the 'p' option when
using handin35. Only one student in the pair should submit
the lab.
See this page for tips on sharing files with your lab partner.
Introduction
In this lab, you will create classes representing different games,
including Tic Tac Toe and a game called Pig (see below for rules of
Pig). Both of these classes will be subclasses of an
abstract Game class. This Game class will
encapsulate all information and functionality that is common to all
games. Your subclasses will add additional data and methods
particular to each game, as well as implement inherited method from
the Game class.
In addition, you will create classes to represent different players
for the Pig game. One player class HumanPigPlayer will
provide functionality for a human player to play Pig. You will
implement another class for a computer AI player. These player
classes will both be subclasses of a base
class PigPlayer. Note: as part of the grading
process, your computer pig player classes will compete against each
other. The team implementing the best AI player will receive one
extra credit point on this lab.
Below is an overview of the files required for submitting this week's
lab. Those highlighted in blue will require implementation on your
part. Those in black are complete and should not be modified except
for comments.
- Makefile - contains scripts for compiling your program.
You will only need to modify lines 4 and 6 as you finish portions of the lab.
- game.h/.cpp - declares and defines the Game
class, an abstract parent class of all game classes.
- ticktactoe.h, tictactoe.cpp - declares and
defines the TicTacToe class. Most of this class implementation has been provided for you.
- pig.h/cpp - declares and
defines the Pig class, a subclass of Game.
- pigplayer.h, pigplayer.cpp - declares and
defines the PigPlayer class. This class has been provided for you.
- humanPigPlayer.h, humanpigplayer.cpp - declares and
defines the HumanPigPlayer class.
- USERpigplayer.h/.cpp - declares
and defines the USERPigPlayer class. Note: you
should replace USER with your user name. For example, if you are
jdoe3, create a class Jdoe3PigPlayer.
- testGame.cpp - use this file to design
simple but thorough tests for your classes.
- testPig.cpp - use this file to design
simple tests for your Pig class.
- gameEngine.cpp - the main program which enables users to create
instances of your games and play them.
- README - complete this when your lab
is finished and you are ready to hand in the program.
Program Requirements
I recommend implementing your program in 5 phases:
- Game and TicTacToe classes -- be able to play a game of TicTacToe.
- Pig class -- be able to play a game of Pig using given solution objects for PigPlayer and humanPigPlayer (see section 2 below).
- PigPlayer, and HumanPigPlayer classes -- be able to play a game of Pig using your own implementation of PigPlayer and HumanPigPlayer.
- Computer PigPlayer. Design your own PigPlayersubclass that uses artificial intelligence to make decisions.
- Final program. In main.cpp, you should create a program that lets user(s) play the games you created.
Your program should use proper design, and you should use defensive
programming as you develop your code. Your program will be tested on
your use of modular functions and understanding of how to define C++
classes and use inheritance. In addition, you should implement small
test programs in between each phase.
I. Game and TicTacToe classes
Your program will need to handle a couple of different types of
games. To get started, take a look at the Game and TicTacToe classes.
The Game class has been implemented for you. But you must start but
understanding this class.
The Game class encapsulates all information
and methods common to all game types. In particular, it contains:
- basic information for a game, including the name and number of players.
- accessor functions for all of the above data.
- A play method. This is the main method you call to play
a game. play is a pure virtual function since you
cannot play a general game object itself.
- A isGameOver method, which returns true iff the game has completed.
- A printGameState method, which should print out
information about the current state of the game. For instance,
the TicTacToe game should print out the game board along with
X's and O's given where players filled in squares.
- A default printInfo method. This will print out
information about the game. Subclasses should also imlpement this
method and be sure to call the parent version (since the data members
are private).
I have provided skeleton code for both
game.h
and
game.cpp.
---------------------------
Key Concepts: virtual functions, pure virtual functions, abstract classes.
The
play(), isGameOver(), and
printGameState() are
all what are called
pure virtual functions -- there are no
implementations for these functions in the Game class. Instead, each
subclass must implement the function(s) themselves. In a class
declaration, pure virtual functions are declared as folows:
virtual bool isGameOver() = 0;
The
virtual keyword makes
isGameOver a virtual
function. This allows subclasses to provide their own method
definition. By adding
= 0 the method becomes a
pure
virtual funciton. This means the subclasses
must provide
their own imlpementation of this method.
A class that declares a pure virtual funciton is called
an
abstract class --- it forms the template for subclasses you
want to define later. In this way, it is a
contract:
anything that inherits the
Game class
must complete
unique
play, isGameOver, and
printGameState methods.
And any user who wants to create
Game objects must chose a
specific subclass (e.g.,
TicTacToe, Pig, etc.). Use pointers to create these objects as follows.
Game *mygame = new TicTacToe();
This creates a pointer to a Game object called
mygame and
makes it a new TicTacToe game. From the user's perspective, mygame is
a
Game pointer, even though "under the hood" it is a TicTacToe game.
---------------------------
The
TicTacToe class should enable two people to play
TicTacToe. It should specialize (i.e., inherit)
Game and implement any information and methods particular to
Tic Tac Toe. When you are finished imlementing this class, you and
your partner should be able to play Tic Tac Toe using an object of
this class.
Much of the Tic Tac Toe implementation has been provided for
you. In particular, I have provided the following data members:
- an int current_player indicating which player is to
select next.
- a two-dimensional, 3x3 integer array board, which
represents which squares each player has taken. To index a
two-dimensional array, use the following syntax.
board[i][j]
This gives you the square indexed by the ith row and jth column. Each
entry contiains an int, which equals 0 if the square has not been
selected, 1 if the square has been selected by the first player, and 2
if the square has been selected by the second player.
In addition to the virtual functions from the
Game class that
must be implemented, there are three private functions:
makeMove,
getWinner, and
checkLine:
- void makeMove();
- int getWinner();
- int checkLine(int a, int b, int c);
These are helper functions that get called from within other TicTacToe
methods. They're private because they shouldn't be called from
outside the class definition.
To complete the
TicTacToe implementation, you must do the
following:
- Complete the implementation of makeMove. You should prompt
the current player to enter a row and column, validate these choices,
and once valid, claim the square for the current player.
- Implement the checkLine helper function. This takes in
three integer values and returns (i) zero if the integers are not all equal, or (ii) k if the inputs are all equal to k.
This function is used to check
to see if a horizontal, vertical, or diagonal line has been claimed by
a player. See the checkWinner function to see how it is used.
- Implement the isGameOver function. This should return true
if the game has ended and false otherwise. Hint: look at
the checkWinner function for guidance.
Before moving onto the second phase of this lab, modify
testGame.cpp to test your class. This program should create
a
TicTacToe object and play a game of TicTacToe. You should
test, at a minimum, all methods whether inherited or not, before
moving forward. You should also test to ensure that the game ends
correctly, in a win for the X player, for the Y player, or when there is a tie.
To compile incrementally, first compile the TicTacToe class by
itself to make sure you have no errors:
$ g++ -c TicTacToe.cpp
We have provided a
Makefile that automates the process. To compile
one of the classes, simple follow this example:
$ make tictactoe.o
This executes the command given above. When you are ready to use
testGame, run:
$ make testGame
II. The Pig class
Once you have successfully created a
TicTacToe object and
played a game or two of TicTacToe, you can begin creating the Pig
game. Pig is a die-rolling game played between two or more players.
In one players turn, he or she rolls a six-sided die. If a six is
rolled, the player immediately loses their turn. If the player does
not roll a six, they accumulate a number of points equal to the
die roll. The player then has two choices: they can either lock in
these points but give up their turn, or they can roll again. When
they roll again, they are presented with the same
option. Note:If a player rolls a six, all accumulated
points are lost. Players only gain points when they choose to end
their turn and lock in the points accumulated during that turn. Play
continues until one player gains a certain number of points. The
normal game lasts until 101 points, but you should allow a user of
your class to specify the points needed. Also, it will be a good idea
to test this with a lower ending score, like 11 or 21, until you know
you have a correct Pig game implementation.
In this section, you should complete and test an implementation of
the Pig class. Note: the Pig and PigPlayer
classes are coupled. (I will explain more below). To help you get
started, I have provided you with object
files pigPlayersoln.o and humanPigPlayersoln.o.
These provide compiled implementations of the PigPlayer
and HumanPigPlayer classes, which you can use to develop
your Pig class. Once you have compiled and tested your Pig
class, you can implement the PigPlayer and HumanPigPlayer classes.
Pig vs. PigPlayer. The Pig and PigPlayer classes
work closely together. To minimize confusion, it might be helpful to
describe how they should interact. Pig is the Game class that manages
a single Pig game. This class has pointers to
two PigPlayer objects. Each of these objects encapsulates a
single Pig game player. When you play the Pig game (by calling the
play() method), you will have each player take turns, by calling
a takeTurn method on the player. This passes control to
your PigPlayer object. Inside this method, the player should
roll the dice and make choices associated with taking their turn. All
game state should be stored in the Pig object. While a player
is taking a turn, they will change the game state (by, for example,
rolling the die and accumulating points). To enable that, you should pass in a pointer to
your Pig game object, as follows:
player1->takeTurn(this);
this calls the takeTurn method of the PigPlayer class, passing in the
address of the Pig object. The PigPlayer should update game state by
calling one of the public methods of Pig that you'll implement.
Details of the data and methods required for the Pig class are
below.
To recap, the only place the Pig class calls methods on a
PigPlayer object is during the play() method, when it calls
e.g. player1->takeTurn(...). It also might use PigPlayer::getName()
when printing out whose turn it is, or who won the game. A PigPlayer
calls public methods in Pig when they want to access or change game
state.
The Pig class should specialize Game and implement
additional data and members to implement the Pig game. In particular,
you should provide the following data:
- player1 and player2, which are pointers to PigPlayer objects.
- score1 and score2, which are integers representing each players' current score.
- current_player: an integer representing which player is the current player.
- score_to_win: an integer representing how many points are needed to win the game.
- accum: an integer representing how many points have been accumulated by the current player on this turn.
You should also provide the following methods:
- a Pig constructor:
Pig(PigPlayer *_plr1, PigPlayer *_plr2, int score_to_win = 101);
This constructor takes pointers to two PigPlayer objects
and an int representing the winning score. I have provided the
declaration for this method, but not the definition.
In the constructor, you should (i) initialize player1
and player2 to the PigPlayer pointers passed into the
constructor. You should also initialize score_to_win to the
value passed into the constructor. In addition to these variables,
initialize the scores of each player and the accumulated points to
zero and have the current_player be the first of the two payers. The
constructor should also seed the random number generator. To do this,
include the cstdlib library and add
srand(time(NULL));
- a destructor that cleans up all data that was dynamically
allocated to create an object of this class. Note: it's up to the
user to create the PigPlayer objects that are used in a Pig game, but
it's up to you to free these objects. Do this in the destructor.
- accessor methods for all of your data members, including the following:
- int getScore(int _which_player); takes as input which player, returns that players score
- int getCurrentPlayer(); returns current_player
- int getAccum(); returns accum
- Implement a method increaseAccum:
void increaseAccum(int a);
The increaseAccum method takes an integer and adds it to the number of
points accumulated by the current player.
- Implement a method accumulate:
void accumulate();
accumulate takes no argument and (i) adds the currently points
accumulated to the current players score and (ii) resets the
accumulated points to zero.
- implement the isGameOver method. This should
output true if either player's score exceeds the score needed to win.
- implement the play method. This function should manage one
complete game of Pig.cpp. In particular, you should welcome the
user(s), then have players alternate turns. In each turn, you should
(1) print out the game state, (2) have the current player take a turn
using the takeTurn method in PigPlayer, (3) switch the
current player, and (4) reset the number of accumulated points to
zero. When the game is over, print out the final score and who won
the game. Note: this is the only Pig method which passes
control to one of the PigPlayer objects. The only time you should
call a PigPlayer method from within Pig is during play(), either to
(i) have one of the players take their turn, or (ii) print out the
players' names. To have e.g. the first player take a turn, call the
method as follows:
player1->takeTurn(this);
The this keyword returns an address to the current object.
This is how the PigPlayer objects access state in the Pig game.
- implement the printInfo and printGameState
methods. printInfo should print information about the players
in addition to the standard game information. printGameState
should print the current game state, including the players' scores and
whose turn it is.
Testing your Pig implementation. There is a lot of data and
several methods in the Pig class. Before implementing your PigPlayer
classes, test your pig implementation to make sure it works. To help
you, I have provided pigPlayersoln.o
and humanPigPlayersoln.o These are compiled implementations of
the PigPlayer and HumanPigPlayer classes. In the next section, you'll
provide your own implementations of these classes, but for now, use these objects.
Test your Pig implementation in testPig.cpp. Here,
you can call public methods on the Pig class one-by-one and verify
they do what they're supposed to do. To compile this file using the given PigPlayer objects, run
make testJustPig
To compile your
testPig.cpp program using your own PigPlayer objects, use
make testPig
III. PigPlayer, HumanPigPlayer classes
In this section, you should complete implementations of the Pig,
PigPlayer, and HumanPigPlayer classes.
The PigPlayer class. This is an abstract class
representing Pig game players. It contains the following.
Your only task for the
PigPlayer class is to finish implementing the
takeTurn method.
Note:This class uses random number generation, which is
provided in the
cstdlib library. The
rand() function
gives you a random number. I have provided the code for this.
Implement a HumanPigPlayer class. This should
specialize PigPlayer, and has no additional data members. To
implement the HumanPigPlayer class, implement the following:
- a constructor which just calls the PigPlayer constructor.
- makeChoice In this function, you should (i) print the
player's score and the current accumulated points and (ii) ask the
(human) user to either keep rolling or lock in points. Of course, you
should also validate the user's input to ensure his choice
makes sense.
IV. Computer PigPlayer class
Finally, you should implement
a computer
PigPlayer class. Like the
HumanPigPlayer
class, this class should specialize
PigPlayer, implement a
basic constructor, and implement the
makeChoice method.
However, rather than asking the user to make the choice, your class
will make decisions automatically. For example, you could always
choose to lock in points on the first roll, or you could always choose
to stop rolling only when you've accumulated enough points to win.
Note: As part of the grading process, we will pit your
computer PigPlayer classes against eachother. The group with the most
victories will receive one
extra credit point on this lab.
Put your comptuer PigPlayer class in a file
fooPigPlayer.h/cpp, where foo is the user name of one of your
partners. Your class should be called FooPigPlayer.
To compile this class, make sure to
- add fooPigPlayer.o to the list of objects to be compiled in line 4 of the Makefile
- add fooPigPlayer.h to the list of header files in line 6 of Makefile.
V. The Main program
Once you've completed all the components
of this lab, create a
gameEngine.cpp file which brings everything
together. In your main program, you should create a list of games
(including different players/settings for the
Pig game. Then
you should ask the user which one they want to play, and let the user
play them. Much of this file has been provided for you.
When you are ready to compile this program, uncomment out
main in line 9 of the Makefile.
Check List and Sample Output
To recap, you are required to to the following:
- Complete implementation of the TicTacToe class.
- Implement the Pig, PigPlayer, HumanPigPlayer and your
computer class using inheritance and good design.
- Other classes implement the details required and no more. The
exception is that additional private methods are allowed. But no
additional data members or public methods.
- All user-input is validated to ensure they select correct options.
- Your code maintains good style: spacing, in-line commenting for
complicated blocks of code, function header comments, and header comments.
- One partner submits the lab, and the partnership is made clear in the
header comments for each file (plus the README and handin35 script).
- Answer questions listed in the README file.
I have provided executables for simple test versions of Tic Tac Toe
and for the Pig game. To play Tic Tac Toe, run the following command:
$ ~brody/public/cs35/gameEngine/testTTT
To play the Pig game, run the following command:
$ ~brody/public/cs35/gameEngine/testPig
testPig asks the user whether each player is a human or a computer
player. It also asks how many points are needed to win the game.
Then, it plays the game. Note: this test class hard-codes names into
the players. In your final program, you should prompt the user for
names before you start the game.
Question: See if you can determine the strategy used by the
AI player.
Submit
Once you are satisfied with your code, hand it in by typing
handin35. This will copy the code from your
cs35/labs/03 to my grading directory. You may run
handin35 as many times as you like, and only the most recent
submission will be recorded. Please record your partner one time using the
'p' option.