TOC PREV NEXT

Program — Class design exercises


This programming assignment focuses on three aspects of class design: operator overloading, use of inheritance, and design of function and class templates.

Readings

A Computer Science Tapestry, section 9.4 (operator overloading), chapters 11 (templates), 13 (inheritance), and appendix E (operator overloading).

C++ Program Design, sections 9.3 through 9.5 (operator overloading), 10.6 (iterators), 12.9, and chapters 13 (inheritance) and 14 (templates and polymorphism) except for material on multiple inheritance.

Computing Fundamentals with C++, section 11.2 (iterators) and chapters 16 (inheritance), 17 (templates), and 18 (operator overloading).

Related quizzes

Class design.

Programming assignment

Do both of the programming exercises described on the following pages. The first programming exercise allows you to choose between two programs to complete.

Class design exercise 1

The directory ~cs9f/lib/inherit contains versions of the cat-and-mouse and Twenty-one programs. You are to complete one of these programs (presumably the one corresponding to the flow-of-control project you wrote) by adding a module that uses inheritance.

Cat and mouse

The following files are provided in ~cs9f/lib/inherit/cat+mouse.

cat+mouse.cpp
This contains the main program, which constructs a "scene" of animals running around the statue and runs the simulation. The simulation stops when one of the animals in the scene captures its target. The code doesn't do much error checking. Don't worry about that; just use it unmodified.

park.h and park.cpp
These comprise the module for the scene and the basic animal object. The Scene constructor prompts the user for the name, animal type, starting position, and target object for each animal in the simulation. The Animal class is the base class for simulation animals. It defines a virtual Chase method that Animal objects use to move.

positions.h, hp.positions.o, dec.positions.o, and solaris.positions.o
These comprise a module for positions similar to what you wrote for the "Flow of Control Project" assignment. Use the appropriate .o file depending on whether you work on a Hewlett-Packard computer (parker, cochise, or mingus), a DEC computer (cory or some of the computers in 199 Cory), or a computer running Solaris (po, torus, quasar, or any of the workstations in the lab rooms in Soda). If you're working on another platform, you will have to implement the changes yourself to the positions module from the flow-of-control project, but they won't be difficult.

You are to provide files animals.h and animals.cpp that define classes Mouse, Cat, and Person. A Mouse object doesn't "chase" anything; it merely moves counterclockwise around the statue, one meter per call to Chase. A Cat object has a Mouse object as its target. If the cat sees its target, it moves one meter toward the statue; otherwise it circles 1.25 meters counterclockwise around the statue. A Person object is trying to photograph the situation. It doesn't try to capture anything. If it sees its target, it doesn't move; otherwise it circles 2 meters clockwise around the statue.

Mouse, Cat, and Person are to be derived from class Animal. Provide enough diagnostic output in each member function so that a tutor can see whether it's performing as specified. Test your program in the same way you did for the flow-of-control project. Your program should also adhere to standards described in the section "Style guidelines" distributed earlier this semester.

Twenty-one

The following files are provided in ~cs9f/lib/inherit/21. (Your program will also need ~cs9f/lib/cards.h and ~cs9f/lib/cards.cpp used in the flow-of-control project.)

21.cpp
This is the main program. It asks for the number of games to play, constructs the set of players, and plays the games.

21game.h and 21game.cpp
These comprise the module for the set of Twenty-one players. The Group21 constructor prompts the user for the name and classification of each player. The PlayGame method deals cards to each player as in the Twenty-one flow-of-control project, then send each player enough information to tally a win or a loss. The Player class is the base class for Twenty-one players. It defines a virtual IsStillDrawing method that represents a player's strategy.

You are to provide files players.h and players.cpp that define classes Dealer, Smarter, and OneMoreTaker. A Dealer object follows the dealer's strategy: draw to 16 or less, stand on 17 or more. A Smarter object follows the alternative strategy described in the flow-of-control project: stand on 17 if the dealer's upcard is 7 or better, stand on 12 otherwise. A OneMoreTaker object takes one more card after the dealer's strategy would say to stand. (The OneMoreTaker class will probably need a replacement Reset member function.)

Dealer, Smarter, and OneMoreTaker are to be derived from class Player. Provide enough diagnostic output in each member function so that a tutor can see whether it's performing as specified. Test your program in the same way you did for the flow-of-control project. Your program should also adhere to standards described in the section "Style guidelines" distributed earlier this semester.

Checklist for either program

Correctly working code.
No changes to existing code.
Inheritance used as specified.
Testing as specified.
Printed listings of program text, tests, and test results.
Adherence to CS 9F style standards:
appropriate use of indenting and white space;
avoidance of "forbidden C++";
variable and function names that reflect their use;
informative comments at the head of each function;
no routine more than twenty-four lines of code.

cat+mouse.cpp

#include <iostream>
using namespace std;

#include "park.h"

void RunChase (Scene allAnimals) {
	for (int time=1; time<=30; time++) {
		for (int k=0; k<allAnimals.Length (); k++) {
			if (allAnimals[k]->Chase ()) {
				return;
			}
		}
		cout << endl;
	}
	cout << "Chase took too long; all animals drifted away." << endl;
}

int main () {
	Scene allAnimals;
	RunChase (allAnimals);
	return 0;
}

park.h

#ifndef PARK_H
#define PARK_H
#include <vector>
#include <string>

#include "positions.h"

class Animal {
friend class Scene;
public:
	Animal (string s, Position p);
	string Name () const;
	Position Pos () const;
	// Move the animal, and return true if it catches its target.
	virtual bool Chase () = 0;
	
protected:
	string myName;	// animal's name
	Position myPos;	// animal's position
	Animal* myTarget;	// ptr to animal being chased; 0 if none
};

class Scene {
public:
	Scene ();
	int Length ();
	Animal* &operator[] (int k);
private:
	vector<Animal*> allAnimals;
};
#endif

park.cpp

#include <vector>
#include <string>
using namespace std;

#include "positions.h"
#include "park.h"
#include "animals.h"

const int NUMANIMALS = 3;

// Set up the scene of animals.
Scene::Scene (): allAnimals (NUMANIMALS) {
	string response1, response2;
	float r, thetaInRadians;
	int k;
	for (k=0; k<NUMANIMALS; k++) {
		cout << "What kind of animal should be next? ";
		cin >> response1;
		cout << "What is its name? ";
		cin >> response2;
		cout << "What is its starting position?" << endl 
			  << "  radius = ";
		cin >> r;
		cout << "  angle in radians = ";
		cin >> thetaInRadians;
		Position coords (r, thetaInRadians);
		if (response1 == "mouse") {
			allAnimals[k] = new Mouse (response2, coords);
		} else if (response1 == "cat") {
			allAnimals[k] = new Cat (response2, coords);
		} else if (response1 == "person") {
			allAnimals[k] = new Person (response2, coords);
		} else {
			cout << "I don't know what that is." << endl;
			exit (1);
		}
	}
	// Define all the targets for the animals.
	for (k=0; k<NUMANIMALS; k++) {
		cout << "Who is " << allAnimals[k]->myName << " chasing? ";
		cin >> response1;
		for (int k2=0; k2<NUMANIMALS; k2++) {
			if (response1 == allAnimals[k2]->myName) {
				if (k2 == k) {
					cout << allAnimals[k]->myName 
						  << " can't chase itself!" << endl;
					exit (1);
				} else {
					allAnimals[k]->myTarget = allAnimals[k2];
					break;
				}
			}
		}
		if (allAnimals[k]->myTarget == 0) {
			cout << " not chasing anyone." << endl;
		}
	}
}

// Return the number of animals in the scene.
int Scene::Length () {
	return allAnimals.size ();
}

// Index the scene.
Animal* &Scene::operator[] (int k) {
	return allAnimals[k];
}

// Constructor (s is the animal's name, 
// coords is its starting position).
Animal::Animal (string s, Position coords) {
	myName = s;
	myPos = coords;
	myTarget = 0;
}

// Access functions.

string Animal::Name () const {
	return myName;
}

Position Animal::Pos () const {
	return myPos;
}

positions.h

#ifndef POSITIONS_H
#define POSITIONS_H

class Position {
public:
	// Initialize a position. It consists of a radius expressed in meters
	// and an angle expressed in radians.
	Position ();                // r = 1, angle = 0.
	Position (float r);         // angle = 0.
	Position (float r, float thetaInRadians);

	// Reinitialize this position.
	void SetAbsolutePosition (float r, float thetaInRadians);

	// Change this position, incrementing the radius by rChange
	// and incrementing the angle by thetaChange (expressed in radians).
	// One of the two arguments must be 0.
	// Negative radius values represent movement toward the statue. 
	// Positive angular distance changes represent 
	// counterclockwise motion; negative values are clockwise.
	void IncrementPosition (float rChange, float thetaChange);

	// Compare two positions.
	bool operator== (Position coords);

	// Print this position.
	void Print ();
	friend ostream& operator<< (ostream &out, Position &pos);

	// Return true if someone at this position can see someone 
	// or something at the argument position (i.e. the statue 
	// does not block one's view), and return false otherwise.
	// The argument position must be at the statue.
	bool Sees (Position pos);

	// Return true if this position is at the base of the statue,
	// i.e. its radius is1, and return false otherwise.
	bool IsAtStatue ();

	// Return true if this position is between the first argument
	// position and the second.  Precondition: the difference 
	// between the first and second argument positions is less 
	// than pi radians, and the radii of all the positions 
	// are the same.
	bool IsBetween (Position old, Position current);

private:
	float myRadius;
	float myAngleInRadians;
	float Normalize (float radians);
};

#endif

21.cpp

#include <iostream>
using namespace std;

#include "cards.h"
#include "21game.h"

int main () {
	Deck d(false);
	Group21 group;
	int numGames;
	cout << "How many games? ";
	cin >> numGames;
	for (int k=1; k<=numGames; k++) {
		group.PlayGame (d);
	}
	group.Report ();
	return 0;
}

21game.h

#ifndef GAME_H
#define GAME_H
#include <vector>
#include <string>

#include "cards.h"

class Player {
public:
	Player (string name);
	
	// Reinitialize the hand to contain no cards.
	virtual void Reset ();
	
	// Add the given card to the hand.
	void AddCard (Card c);
	
	// Return true if the player wants another card.
	virtual bool IsStillDrawing (Card c) = 0;
	
	// Return the player's total hand value.
	int Total ();
	
	// Tally a win or a loss.
	void Tally (int dealerTotal);
	
	// Tally a loss.
	void TallyLoss ();
	
	// Report how many wins.
	void Report ();
	
protected:
	string myName;
	vector<Card> myHand;
	int myCardCount;
	int myAcesAs11Count;
	int myTotal;
	int myWinCount;
	int myGameCount;
};

class Group21 {
public:
	Group21 ();
	void PlayGame (Deck &);
	void Report ();
private:
	vector<Player*> allPlayers;
};
#endif

21game.cpp

#include <iostream>
#include <vector>
#include <string>
using namespace std;

#include "21game.h"
#include "players.h"
#include "cards.h"
const int NUMPLAYERS = 1;			// doesn't include the dealer

// Set up the group of Twenty-one players.
Group21::Group21 (): allPlayers(NUMPLAYERS+1) {
	string response1, response2;
	allPlayers[0] = new Dealer ("dealer");
	for (int k=1; k<=NUMPLAYERS; k++) {
		cout << "Player name? ";
		cin >> response1;
		cout << "Player strategy? ";
		cin >> response2;
		if (response2 == "dealer") {
			allPlayers[k] = new Dealer (response1);
		} else if (response2 == "smart") {
			allPlayers[k] = new Smarter (response1);
		} else if (response2 == "onemore") {
			allPlayers[k] = new OneMoreTaker (response1);
		} else {
			cout << "I don't know that strategy." << endl;
			exit (1);
		}
		cout << endl;
	}
}

// Play a single game for all players.
void Group21::PlayGame (Deck &d) {
	// Initialize all the hands.
	for (int k=0; k<=NUMPLAYERS; k++) {
		allPlayers[k]->Reset();
	}
	// Deal initial cards.
	for (int j=0; j<2; j++) {
		for (int k=1; k<=NUMPLAYERS; k++) {
			allPlayers[k]->AddCard(d.Deal());
		}
		allPlayers[0]->AddCard(d.Deal());
	}
	// Check for dealer blackjack.
	if (allPlayers[0]->Total() == 21) {
		for (int k=1; k<=NUMPLAYERS; k++) {
			allPlayers[k]->TallyLoss();
		}
	} else {
		// Why is the type cast needed?
		Card dealerUpCard = ((Dealer*) allPlayers[0])->Upcard();
		// Have all the players play.
		for (int k=1; k<=NUMPLAYERS; k++) {
			while (allPlayers[k]->IsStillDrawing(dealerUpCard)) {
				allPlayers[k]->AddCard(d.Deal());
			}
			cout << endl;
		}
		// Have the dealer play.
		while (allPlayers[0]->IsStillDrawing(dealerUpCard)) {
			allPlayers[0]->AddCard(d.Deal());
		}
		// Determine outcomes.
		for (int k=1; k<=NUMPLAYERS; k++) {
			allPlayers[k]->Tally(allPlayers[0]->Total());
		}
		cout << endl;
	}
}

// Have all the players report how many games they've won.
void Group21::Report () {
	for (int k=1; k<=NUMPLAYERS; k++) {
		allPlayers[k]->Report ();
	}
}

// Constructor (the argument is the player's name)
Player::Player (string s): myHand(10) {
	myName = s;
	myCardCount = myAcesAs11Count = 0;
	myTotal = 0;
	myWinCount = myGameCount = 0;
}

// Reset the player's hand to empty.
void Player::Reset () {
	myCardCount = myAcesAs11Count = 0;
	myTotal = 0;
}

// Add the given card to the player's hand.
void Player::AddCard (Card c) {
	myHand[myCardCount] = c;
	myCardCount++;
	if (c.IsAce ()) {
		if (myAcesAs11Count > 0) {
			myTotal += 1;
		} else {
			myAcesAs11Count++;
			myTotal += 11;
		}
	} else {
		myTotal += c.Value();
	}
	if (myTotal>21 && myAcesAs11Count>0) {
		myTotal -= 10;
		myAcesAs11Count--;
	}
}

// Return the player's hand total.
int Player::Total () {
	return myTotal;
}

// Compare the player's total to the dealer's total,
// and tally a win or loss appropriately.
// Precondition: the dealer doesn't have a blackjack.
void Player::Tally (int dealerTotal) {
	if (myTotal==21 && myCardCount==2) {
		myWinCount += 2;
		cout << myName << " wins with a blackjack!" << endl;
	} else if (myTotal <= 21) {
		if (myTotal>dealerTotal || dealerTotal>21) {
			myWinCount++;
			cout << myName << " wins." << endl;
		} else {
			cout << myName << " loses." << endl;
		}
	} else {
		cout << myName << " loses." << endl;
	}
	myGameCount++;
}

// Tally a loss resulting from the dealer's blackjack.
void Player::TallyLoss () {
	cout << myName << " loses to dealer blackjack." << endl;
	myGameCount++;
}

// Report the player's success ratio.
void Player::Report () {
	cout << myName << " won " << myWinCount 
		<< " out of " << myGameCount << endl;
}

Class design exercise 2

This exercise has three parts, involving modifications to code in ~cs9f/lib/sorted.lists.h.

  1. The headers for the SortedList member functions have been omitted. Supply them.
  2. Add a member function that overloads the assignment operator ("=") for sorted lists. Your function should first delete all the ListNode objects in the variable being assigned to (the left hand side of the =). It should then construct a copy of the list on the right hand side of the = to assign to the variable on the left hand side. (Be prepared to explain to a tutor how this behavior differs from the default assignment behavior.)
  3. Provide a SortedListIterator class that allows elements of a sorted list to be returned, one by one. The SortedListIterator class will have three member functions:
    • a constructor;
    • a boolean MoreRemain function, which says whether more elements of the list remain to be generated;
    • a Next function, which returns the next element in the list.

    You'll also need to make it a friend to the SortedList and ListNode classes. (Tutors may ask you why.)

    Here's an example of how the SortedListIterator functions should work. Suppose that the contents of the variable list are as shown in the diagram below.

    Then the code

	iter = new SortedListIterator<int> (list);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
should produce the output
	1 5 7
The SortedListIterator class is separate from the SortedList class to simplify having multiple iterators.

The file ~cs9f/lib/sltest.cpp contains a test program that exercises all this code. A listing of the program appears on the following pages. It should produce the output given below; your output must match it.

	List 1: 3 5 7 10
	List 2: 4 6 8 11
	Should get operator= call here deleting elements of list 1.
	*** in operator=, destroying:  3 5 7 10
	List 1 after replacement: 4 6 8 11
	Error msg should result here from assigning list 2 to itself.
	*** Assigning a list to itself.
	Should get copy constructor call here.
	*** in copy constructor
	Should get operator= call here deleting old elements of list 1.
	*** in operator=, destroying:  4 6 8 11
	List 1 after assignment from list2: 4 6 8 11
	List 2 after assignment: 4 6 8 11
	Pairs of list 2 elements: (6 4)  (8 4)  (8 6)  (11 4)  (11 6)  (11 8)
	Should now get three destructor calls, one each for list1, list2, and 
	list3.
	*** in destructor, destroying:  4 6 8 11
	*** in destructor, destroying:  4 6 8 11
	*** in destructor, destroying:  4 6 8 11

In case you're wondering why functions appear in a .h file, it's because of the class template. The designer of C++, Bjarne Stroustrup notes in The Design and Evolution of C++ (Addison-Wesley, 1994) that "templates don't fit neatly into this picture [the division of programs into .h and .cpp files]. ... A template isn't just source code (what is instantiated from a template is more like traditional source code), so template definitions don't quite belong in .cpp files. On the other hand, templates are not just types and interface information, so they don't quite belong in .h files either." For CS 9F, we decided that putting template code in a .h file was less confusing than #include'ing a .cpp file.

Checklist

Correct function headers.
Correct overload of =, and explanation of why overloading assignment is necessary to produce the desired behavior.
Correctly implemented SortedListIterator class.
Output that matches the output given above.
Printed listings of program text, tests, and test results.
Adherence to CS 9F style standards:
appropriate use of indenting and white space;
avoidance of "forbidden C++";
variable and function names that reflect their use;
informative comments at the head of each function;
no routine more than twenty-four lines of code.

sorted.lists.h

#ifndef SORTEDLIST_H
#define SORTEDLIST_H

#include <iostream>
#include <cassert>

template <class NODETYPE> class SortedList;
template <class NODETYPE> class SortedListIterator;
template <class NODETYPE>

class ListNode {
	friend class SortedList<NODETYPE>;
public:
	ListNode (const NODETYPE &);
	NODETYPE Info ();
private:
	NODETYPE myInfo;
	ListNode* myNext;
};

template <class NODETYPE>
ListNode<NODETYPE>::ListNode (const NODETYPE &value) {
	myInfo = value;
	myNext = 0;
}

template <class NODETYPE>
NODETYPE ListNode<NODETYPE>::Info () {
	return myInfo;
}

template <class NODETYPE>
class SortedList {
public:
	SortedList ();
	~SortedList ();
	SortedList (const SortedList <NODETYPE> &);
	void Insert (const NODETYPE &);
	bool IsEmpty ();
private:
	ListNode <NODETYPE>* myFirst;
};

___ {	// constructor
	myFirst = 0;
}

___ {	// destructor
	if (!IsEmpty ()) {
		cerr << "*** in destructor, destroying: ";
		ListNode <NODETYPE>* current = myFirst;
		ListNode <NODETYPE>* temp;
		while (current != 0) {
			cerr << " " << current->myInfo;
			temp = current;
			current = current->myNext;
			delete temp;
		}
		cerr << endl;
	}
}
___ {	// copy constructor
	cerr << "*** in copy constructor" << endl;
	ListNode <NODETYPE>* listCurrent = list.myFirst;
	ListNode <NODETYPE>* newCurrent = 0;
	while (listCurrent != 0) {
		ListNode <NODETYPE> *temp 
		  = new ListNode <NODETYPE> (listCurrent->Info ());
		if (newCurrent == 0) {
			myFirst = temp;
			newCurrent = myFirst;
		} else {
			newCurrent->myNext = temp;
			newCurrent = temp;
		}
		listCurrent = listCurrent->myNext;
	}
}
___ {	// Insert
	ListNode <NODETYPE> *toInsert 
	  = new ListNode <NODETYPE> (value);
	if (IsEmpty ()) {
		myFirst = toInsert;
	} else if (value < myFirst->Info ()) {
		toInsert->myNext = myFirst;
		myFirst = toInsert;
	} else {
		ListNode <NODETYPE> *temp = myFirst;
		for (temp = myFirst; 
			  temp->myNext != 0 && temp->myNext->Info () < value; 
			  temp = temp->next) {
		}
		toInsert->myNext = temp->myNext;
		temp->myNext = toInsert;
	}
}
___ {	// IsEmpty
	return myFirst == 0;
}
#endif

sltest.cpp

#include <iostream>
using namespace std;

#include "sorted.lists.h"

int main () {
	SortedList<int> list1, list2;
	SortedListIterator<int> *iter;
	list1.Insert (7);
	list1.Insert (10);
	list1.Insert (3);
	list1.Insert (5);
	cout << "List 1:";
	iter = new SortedListIterator<int> (list1);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
	cout << endl;
	
	list2.Insert (8);
	list2.Insert (11);
	list2.Insert (4);
	list2.Insert (6);
	cout << "List 2:";
	iter = new SortedListIterator<int> (list2);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
	cout << endl;
	
	cout << "Should get operator= call here"
		  << " deleting elements of list 1." << endl;
	list1 = list2;
	cout << "List 1 after replacement:";
	iter = new SortedListIterator<int> (list1);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
	cout << endl;
	
	cout << "Error msg should result here "
		  << "from assigning list 2 to itself." << endl;
	list2 = list2;
	cout << "Should get copy constructor call here." << endl;
	SortedList<int> list3 = list2;
	cout << "Should get operator= call here "
		  << "deleting old elements of list 1." << endl;
	list1 = list2;
	cout << "List 1 after assignment from list2:";
	iter = new SortedListIterator<int> (list1);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
	cout << endl;
	
	cout << "List 2 after assignment:";
	iter = new SortedListIterator<int> (list2);
	while (iter->MoreRemain ()) {
		cout << " " << iter->Next ();
	}
	cout << endl;
	
	cout << "Pairs of list 2 elements:";
	SortedListIterator<int> mIter (list2);
	int m, n;
	while (mIter.MoreRemain ()) {
		m = mIter.Next ();
		SortedListIterator<int> nIter (list2);
		for (n = nIter.Next (); n != m; n = nIter.Next ()) {
			cout << " (" << m << " " << n << ") ";
		}
	}
	cout << endl;
	
	cout << "Should now get three destructor calls, "
		 << "one each for list1, list2, and list3." << endl;
	return 0;
}

TOC PREV NEXT