May 7, 2008

Two increments in each iteration

The game::get function introduced in Homework 4.4a, ¶ (5), modified in Homework 5.8a.

wabbit *game::get(unsigned x, unsigned y) const
{
	for (master_t::const_iterator it = master.begin();
		it != master.end(); ++it) {

		wabbit *const p = *it;
		++it;

		if (p->x == x && p->y == y) {
			return *it;
		}
	}

	return 0;
}

Pacman

I had no idea that my manager would demand an animal with multiple lives.

New member function for class terminal

#include <string>	//for class string
using namespace std;

class terminal {
public:
	void put(unsigned x, unsigned y, const string& s) const {
		put(x, y, s.c_str());
	}
};

New members for class game

class game {
public:
	unsigned score_;
	unsigned lives_;
	unsigned startPos_[2];     //assigned to when game::game constructs pman

	void clearScoreLine() const; //blank out bottom line of terminal
};

New member functions for class wabbit

value is good. But id will be misused by game::put.

class wabbit {
private:
	virtual int value() const {return 0;}	//for keeping score
public:
	bool move(int *value);
	char id() const {return c;}	//so game can id wabbit
};

For example,

class cherry: public brownian, public victim {
private:
	int value() const {return 30;}
};

class ghost: public brownian, public immortal {
private:
	int value() const {return -50;}
};

class pellet: public immobile, public victim {
private:
	int value() const {return 10;}
};

wabbit::move takes an argument.

bool wabbit::move(int *value)
{
	*value = 0;

	decide which way to move;
	if (decided not to move) {
		return true;
	}

	if (new location is off the screen) {
		punish and return true;
	}

	if (new location is already occupied by another wabbit) {
		if (I'm hungry enough to eat him) {
			*value = other wabbit's value;
			beep the other wabbit and delete it;
		}

		if (he's hungry enough to eat me) {
			*value = other wabbit's value;
			beep and return false;	//not allowed to delete myself
		}

		if (neither wabbit gets eaten) {
			punish and return true;
		}
	}

	move to new location and return true;
}

game::play gives special handling to pman’s.

void game::play()
{
	score_ = 0;
	lives_ = 0;

	ostringstream os;
	string empty;
	unsigned counter = 1;
	clearScoreLine();

	for (;; term.wait(10)) {
		for (master_t::const_iterator it = master.begin();
			it != master.end();) {


			os.str(empty);
			os << "Score " << score_ << " Lives: " << lives_;
			term.put(0, term.ymax() - 1, os.str());

			wabbit *const p = *it;
			int value = 0;

			//Return a value if wabbit moving is pman,
			// and he hits something tasty, else 0.

			const bool alive = p->move(&value);
			if (p->id() == 'C') {	//class pman
				score_ += value;
			}
			++it;

			if (!alive) {
				delete p;
			}

			if (count('C') == 0) {	//no more pman's
				--lives_;

				if (lives_ >= 1) {
					clearScoreLine();
					os.str(empty);
					os << "You were eaten.  Lives remaining: "
						<< lives_;
					term.put(0, term.ymax() - 1, os.str());
					term.wait(2000);
					clearScoreLine();
					new pman(this,
						startPos_[0], startPos_[1]);
				}

				else {
					clearScoreLine();
					os.str(empty);
					os << "You were eaten.  Lives remaining: "
					os << "You have died of dysentary.  "
						"Final score: " << score_;
					term.put(0, term.ymax() - 1, os.str());
					term.wait(2000);
					return;
				}
			}

			if (count('o') <= 0) {	//no more pellet's
				clearScoreLine();
				os.str(empty);
				os << "WINNER IS YOU!!!  "
					<< "Final Score: " << score_;
				term.put(0, term.ymax() - 1, os.str());
				term.wait(2000);
				return;
			}

			if (score_ > 500 * counter) {
				++lives_;
				++counter;
				//term.put(0, term.ymax() - 1, "Extra Life!");
			}
		}
	}
}

Move the Pacman-specific code from game::play to class pacman

A major achievement of Homework 5.8a (single inheritance) was that the main loop in game::play was cleansed of all code specific to class wolf. I wish we could cleansed it of all code specific to class pman.

If a dying pman was responsible for constructing a new pman, there would (momentarily) be two pman’s. But we don’t want that. Among other problems, the old and new one might interfere with each other because they are at the same location on the terminal.

If the new pman was constructed after the old one was destructed, then there would (momentarily) be no pman at all. The code that calls the constructor the new pman would therefore have to be in class game. game::play already destructs old wabbit’s; it does not seem unreasonable for game::play to construct new wabbit’s. But we would like to avoid writing pman-specific code in class game.

One way to avoid this would be to have a dying wabbit give game::play a pointer to a function. Most of the time, the pointer would be zero. But a dying pman that deserves a new lease on life would return a pointer to a function that would construct a new pman at the desired location. If the pointer is non-zero, game::play would then call the function to which the pointer points. But let’s not pursue this any further.

I think the cleanest solution is not to destruct the old pman. The old pman would be rejuvinated by a call to pman::deserves_to_die.

New member function for class terminal

#include <string>	//for class string
using namespace std;

class terminal {
public:
	void put(unsigned x, unsigned y, const string& s) const {
		put(x, y, s.c_str());
	}
};

New member functions for class game

#include <string>  //for class string
using namespace std;

class game {
public:
	void scoreLine(const string& s = "") const {
		const string clear(term.xmax(), term.background()); 
		const unsigned y = term.ymax() - 1;
		term.put(0, y, clear);
		term.put(0, y, s);
	}
};

New member functions for class wabbit

wabbit::scoreLine makes it possible for classes derived from wabbit to write to the score line. See wabbit::key and wabbit::beep.

#include <string>	//for class string
using namespace std;

class wabbit {
	virtual int value() const {return 0;}
	virtual bool deserves_to_die() {return true;}
	virtual void accumulate(int value) {}
protected:
	void scoreLine(const string& s = "") const {g->term.scoreLine(s);}
public:
	//no longer have id member function
};

Each place where we delete a wabbit will now be enclosed in an if statement that checks if the wabbit deserves to die.

bool wabbit::move()   //no longer takes argument
{
	//etc.
	if (I'm hungry enough to eat him) {
		accumulate(other->value());
		if (other->deserves_to_die()) {
			other.beep();
			delete other;
		}
	}

	if (he's hungry enough to eat me) {
		other->accumulate(value());
		beep and return false;
	}
	//etc.
}
void game::play()
{
	//etc.
	if (!alive && p->deserves_to_die()) {
		delete p;
	}
	//etc.
}

New code in class pman

#include <sstream> //for ostringstream
using namespace std;

class pman: public manual, public predator {
	int score_;
	int lives_;
	unsigned counter_;
	unsigned startPos_[2]; //should be structure with 2 unsigned fields

	virtual void accumulate(int value) {
		if ((score_ += value) > 500 * counter_) {
			++lives_;
			++counter_;
			//scoreLine("Extra Life!");
		}
	}

	bool deserves_to_die() {
		if (--lives_ <= 0) {
			return true;
		}

		ostringstream os;
		os << "You were eaten.  Lives remaining: " << lives;
		scoreLine(os.str());
		(somehow) move to location startPos_[0], startPos_[1];
		return false;
	}

public:
	pman(game *initial_g, unsigned initial_x, unsigned initial_y)
		: wabbit(initial_g, initial_x, initial_y, 'C'),
		score_(0),
		lives_(0),
		counter(1)
	{
		startPos_[0] = x;
		startPos_[1] = y;
	}
};