Extended Life Program

by Dave Aronson

(Yes, I know a lot of this is horrible OO. Bear with me, this was from when I was just learning C++, and had hardly any concept of OO either....)

/*

ecosim.cpp (C) 1997 David J. Aronson

Ecosystem simulation for DigitalThink course CS310
(Designing Reusable Code in C++)


Revisors:
 DJA David J. Aronson


Revision history:
 WHEN       VER WHO WHAT
1997-12-01 0.0a DJA - created according to original exercise instructions
1997-12-02 0.1a DJA - changed name of living to cell (since empty isn't alive)
                    - derived class living from cell (versus empty)
                    - derived class animal from living (versus grass)
                    - made rabbit and fox inherit from animal
                    - made grass inherit from living
                    - added static maxAge to classes fox & rabbit
                    - added command-line parsing of same
                    - added parsing of maximum rows, cols, and generations
1997-12-03 0.2a DJA - added scavenging of rabbits that die of natural causes
                    - added lushness to grass, so it can support multiple
                      rabbits over multiple turns
                    - added hunger and static maxHunger to class fox, and
                      command-line parsing of maxHunger
                    - changed eating mechanism to be responsibility of eater
                      to pick a meal (lushest grass, weakest rabbit)
1997-12-04 0.3a DJA - added concept that lushness level 0 is inedible root/seed
                      and is let sprout on next turn
                    - added user-adjustable chances of birth
                    - added option to abort on species extinction
                    - added adjusting of order of birth-chance checks
                    - added reporting of how many of what at each turn
                    - added requirement that breeding pair be over age 0
1997-12-04 1.0a DJA - submitted at last!
1997-12-05 1.0b DJA - made it clear screen only for first generation
1997-12-08 1.0c DJA - added ability NOT to overwrite generations on screen
                    - added sexes to animals
                    - added ability to use just one world
                    - allowed starving foxes to scavenge dead foxes too


OTHER ISSUES:

- Instructions say to use two worlds.  This runs risk of having a rabbit eaten
by a fox that already died of old age, or having a patch of grass eaten by a
rabbit that has already been eaten or died of old age, or numerous other
sillinesses (as pointed out in the notes for the prior two projects).  This
isn't QUITE as likely as some of the absurdities possible in the
simple-predator model, but....

- Must init stable to false, else if you tell it to do only one gen, stable
never gets changed, so prog would falsely report stability.

- Considered deriving carnivore and herbivore from animal, and deriving fox and
rabbit (respectively) from them, but decided enough subclassing was enough!

- When choosing a random number from 1 to n, I decided to opt for a simple
"rand()%n".  This has a slight bias towards the lower numbers if maxint%n is
not zero.  I consider this negligible, given the size of the typical n used
here, compared to the hassle of "static_cast<int>((static_cast<double>(rand())
/ maxint) * n)".


CURRENT CLASS HIERARCHY:

cell (anything that can occupy the space) (GENERIC; NOT INSTANTIATED)
 empty (unoccupied cell)
 living (anything that truly is alive; adds kill()) (GENERIC; NOT INSTANTIATED)
  grass
  animal
   fox
   rabbit
world


ADDITIONS BEING CONSIDERED:
- humans, who can kill foxes AND rabbits, maintain grass, and die as grass food
- vorpal-bunnies, who will prevent a fox from attacking any common neighbor
  (kill the fox?)

*/



// STANDARD INCLUDES
#include <assert.h>
#include <iostream.h>
#include <macros.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>



// MACROS



// still working on color -- for now, disable
// #define color(n) {}
#define color(n) cout << static_cast<char>(27) << "[;" << n << "m";
#define RED 1
#define GREEN 4
#define WHITE 15


// SIMPLE TYPES



enum state { EMPTY, GRASS, RABBIT, FOX, STATES };
typedef unsigned int uint;



// CLASS DECLARATIONS, IN HIERARCHICAL (THEN ALPHABETICAL) ORDER


// fwd decls needed for cell
class animal;
class fox;
class grass;
class rabbit;
class world;

// fwd decl needed for cellptr
class cell;

typedef cell* cellptr;

// anything that can occupy space in the world (SHOULD NOT BE INSTANTIATED!)
class cell
{
public:
   cell (uint r, uint c) : row (r), column (c) {}

   // find the hungriest fox nearby
   fox*             HungriestFox (world* w);

   // find the lushest grass nearby
   grass*           LushestGrass (world* w);

   // let each subclass decide how to compute next occupant of cell
   virtual cellptr  next (world* w) = 0;

   // let each subclass decide how to print itself
   virtual void     print (bool ANSI) = 0;

   // see how many of what kinds of things are NEARBY
   void             neighbors (world* w, uint sm[STATES]);

   // find weakest rabbit nearby
   rabbit*          WeakestRabbit (world* w);

   // see if any nearby of this type; if we supply one, find NEXT
   cellptr          FindNeighbor (world* w, state s, cellptr p);

   // let each subclass report itself
   virtual state    who() = 0;

protected:

   uint  row, column;
};



// cell with nothing living there
class empty : public cell
{
public:
   empty (uint r, uint c) : cell (r, c) {}
   // is there a breeding pair of some critter type nearby?
   bool     BreedingPair (world* w, state s);
   cellptr  next (world* w);
   void     print (bool ANSI) { cout << "  "; }
   state    who() { return EMPTY; }
   // order in which to check for things getting born there
   static char birthOrder[STATES];
};

char empty::birthOrder[STATES] = "rfg";



// anything ALIVE in a cell (SHOULD NOT BE INSTANTIANTED!)
class living : public cell
{
public:
   living (uint r, uint c) : cell (r, c) {}

   // kill this thing and make its spot empty
   // (mainly used when something ELSE is killing it)
   void kill (world* w);
};



// any ANIMAL alive in a cell (versus plants, which is so far only grass)
// (SHOULD NOT BE INSTANTIATED!)
class animal : public living
{
public:
   enum sexEnum { MALE, FEMALE, SEXES };
   // default sex being female is better for species survival
   animal (uint r, uint c, sexEnum s = FEMALE, uint a = 0) :
      living (r, c), sex (s), age (a) {}
   uint     GetAge() { return age; }
   sexEnum  GetSex() { return sex; }
protected:
   sexEnum  sex;
   uint     age;
};



class fox : public animal
{
public:
   fox (uint r, uint c, sexEnum s = FEMALE, uint a = 0, uint h = 2) :
      animal (r, c, s, a), hunger (h) {}

   // how hungry is this fox?
   uint       GetHunger() { return hunger; }

   // let the fox eat
   void       LetEat (uint portion) { hunger -= min (hunger, portion); }

   cellptr    next (world* w);
   void       print (bool ANSI)
   { if (ANSI) color (RED); cout << hunger << age; if (ANSI) color (WHITE); }
   state      who() { return FOX; }

   // this critter type's chance of being born, in any eligible spot
   static uint  birthChance;

   // max age this critter type lives to
   static uint  maxAge;

   // how hungry this critter type can get w/o dying
   static uint  maxHunger;

protected:
   // how hungry this particular critter is
   uint  hunger;
};

uint fox::birthChance = 9;
uint fox::maxAge = 9;
uint fox::maxHunger = 3;



// fwd decl needed for rabbit
class grass;

class rabbit : public animal
{
public:
   rabbit (uint r, uint c, sexEnum s = FEMALE, uint a = 0) :
      animal (r, c, s, a) {}

   // vulnerability is greatest for very young or old
   uint      GetWeakness() { return abs (age - (maxAge / 2)); }

   cellptr   next (world* w);
   void      print (bool ANSI) { cout << 'r' << age; }
   state     who() { return RABBIT; }

   // this critter type's chance of being born, in any eligible spot
   static uint  birthChance;

   // max age this critter type lives to
   static uint  maxAge;

   // no hunger int: herbivores are ALWAYS hungry -- must have food EVERY turn
};

uint rabbit::birthChance = 9;
uint rabbit::maxAge = 6;



class grass : public living
{
public:
   grass (uint r, uint c, uint l = 0) : living (r, c), lushness (l) {}

   // let a rabbit eat enough to survive one turn -- note that 0 is inedible
   void     GetEaten () { --lushness; }

   // how lush is this patch of grass?
   uint     GetLushness() { return lushness; }

   cellptr  next (world* w);
   void     print (bool ANSI)
   { if (ANSI) color (GREEN); cout <<' '<<lushness; if (ANSI) color (WHITE); }
   state    who() { return GRASS; }

   // this critter type's chance of being born, in any eligible spot
   static uint  birthChance;

   // how lush a patch of grass will this soil support?
   // (limits how many rabbits it can support)
   static uint maxLushness;

protected:
   uint  lushness;
};

uint grass::birthChance = 1;
uint grass::maxLushness = 9;



// slight adaptation of twod class from CS210 -- contains POINTERS
class world
{
public:
   world (uint inRows, uint inCols);
   ~world();

   // produce REFERENCE to content-type (in this case, cellptr)
   // so we can use as both lvalue and rvalue
   // in case implementation changes
   cellptr&  contents (uint row, uint col);

   // count all the things in the world, by type -- SKIP EDGES!!!
   void      countoff (uint sm[STATES]) { sums (sm, 1, rows - 2, 1, cols - 2); }

   // put things in the cells (formerly called eden())
   void      populate();

   // print current state of the world
   void      pr_state (bool overwrite);

   // see how many of what kinds of things are within a WIDE area
   void      sums (uint sm[STATES],
                   uint loRow, uint hiRow, uint loCol, uint hiCol);

   // update the world to new status -- make friend so access is same
   friend bool  update (world* w_new, world* w_old);

private:

   // pointer to array of pointers to arrays of cellptr
   cellptr**  base;

   // don't want to make world with null pointers available to the public!
   void       clear (bool edges);

   // how big is this world?  Note that it no longer needs to be square!
   uint       rows, cols;
};



// METHOD IMPLEMENTATIONS, IN ALPHABETICAL ORDER BY CLASS THEN METHOD



// is there a cell of this type nearby?

cellptr cell::FindNeighbor (world* w, state s, cellptr last)
{
   // for each spot in area
   for (uint r = row - 1; r <= row + 1; r++)
   {
      for (uint c = column - 1; c <= column + 1; c++)
      {
	 // if it's not the central one
	 if (r != row || c != column)
	 {
	    // stash a pointer for easy access
            cellptr p = w->contents (r, c);

	    // if it's the right species
            if (p->who() == s)
	    {
	       // if "last seen" pointer passed in is NULL, return this one
	       if (! last) return p;

	       // else if it's the same, null it out (return the next one)
	       else if (p == last) last = NULL;

	       // else keep looking
	    }
	 }
      }
   }

   // if we got here, no luck, so return null
   return NULL;
}



// find the hungriest fox nearby

fox* cell::HungriestFox (world* w)
{
   uint  curCol;       // column of fox
   uint  eaters = 0;   // number of potential eaters found
   uint  hunger = 0;   // highest level of hunger so far
   fox*  foxPtr;       // temp pointer to one fox
   fox*  foxPtrs[8];   // pointers to potential eaters
   uint  curRow;       // row of fox


   // for each row from one before to one after this one
   for (curRow = row - 1; curRow <= row + 1; curRow++)
   {
      // for each column from one before to one after this one
      for (curCol = column - 1; curCol <= column+ 1; curCol++)
      {
         // if it's not the one whose neighbors we are examining
         // and it is indeed a fox
         if ((curRow != row || curCol != column) &&
             FOX == w->contents (curRow, curCol)->who())
         {
            // stash pointer to this one for easy access
            foxPtr = static_cast<fox*>(w->contents (curRow, curCol));

            // if its hunger is equal to the current high, add it to list
            if (foxPtr->GetHunger() == hunger) foxPtrs[eaters++] = foxPtr;

            // else if greater than current high, make it ONLY one
            else if (foxPtr->GetHunger() > hunger)
            {
               eaters = 1;
               foxPtrs[0] = foxPtr;
            }
         // end if fox and not central spot
         }
      // end for each nearby column
      }
   // end for each nearby row
   }

   // if we found none, return null
   if (! eaters) return NULL;

   // else if we have only one, return it
   else if (1 == eaters) return foxPtrs[0];

   // else pick one randomly
   return foxPtrs[rand() % eaters];
}



// find the lushest grass nearby
// NOTE: LUSHNESS LEVEL ZERO IS NOT EDIBLE -- ROOTS AND BURIED SEED ONLY!

grass* cell::LushestGrass (world* w)
{
   uint    curCol;        // column of grass
   uint    meals = 0;     // number of potential meals found
   uint    lushness = 1;  // highest level of lushness so far
   grass  *mealPtr;       // temp pointer to one grass
   grass  *mealPtrs[8];   // pointers to potential meals
   uint    curRow;        // row of grass


   // for each row from one before to one after this one
   for (curRow = row - 1; curRow <= row + 1; curRow++)
   {
      // for each column from one before to one after this one
      for (curCol = column - 1; curCol <= column + 1; curCol++)
      {
         // if it's not the one whose neighbors we are examining
         // and it is indeed grass
         if ((curRow != row || curCol != column) &&
             GRASS == w->contents (curRow, curCol)->who())
         {
            // stash pointer to this one for easy access
            mealPtr = static_cast<grass*>(w->contents (curRow, curCol));

            // if its lushness is equal to the current high, add it to list
            if (mealPtr->GetLushness() == lushness) mealPtrs[meals++] = mealPtr;

            // else if greater than current high, make it ONLY one
            else if (mealPtr->GetLushness() > lushness)
            {
               meals = 1;
               mealPtrs[0] = mealPtr;
            }
         // end if grass and not central spot
         }
      // end for each nearby column
      }
   // end for each nearby row
   }

   // if we found none, return null
   if (! meals) return NULL;

   // if we have only one, return it
   if (1 == meals) return mealPtrs[0];

   // else pick one randomly
   return mealPtrs[rand() % meals];
}



// see how many of what are in the immediate area

void cell::neighbors (world* w, uint sm[STATES])
{
   // call world routine that does essentially the same thing
   w->sums (sm, row - 1, row + 1, column - 1, column + 1);
}



// find the weakest rabbit nearby

rabbit* cell::WeakestRabbit (world* w)
{
   uint     curCol;        // column of rabbit
   uint     meals = 0;     // number of potential meals found
   rabbit*  mealPtr;       // temp pointer to one rabbit
   rabbit*  mealPtrs[8];   // pointers to potential meals
   uint     curRow;        // row of rabbit
   uint     weakness = 0;  // highest level of weakness so far


   // make sure we init the first one to null, just in case we don't find any
   mealPtrs[0] = NULL;

   // for each row from one before to one after this one
   for (curRow = row - 1; curRow <= row + 1; curRow++)
   {
      // for each column from one before to one after this one
      for (curCol = column - 1; curCol <= column + 1; curCol++)
      {
         // if it's not the one whose neighbors we are examining
         // and if it is indeed a rabbit
         if ((curRow != row || curCol != column) &&
             RABBIT == w->contents (curRow, curCol)->who())
         {
            // stash pointer to this one for easy access
            mealPtr = static_cast<rabbit*>(w->contents (curRow, curCol));

            // if its weakness is equal to the current high, add it to list
            if (mealPtr->GetWeakness() == weakness) mealPtrs[meals++] = mealPtr;

            // else if greater than current high, make it ONLY one
            else if (mealPtr->GetWeakness() > weakness)
            {
               meals = 1;
               mealPtrs[0] = mealPtr;
            }
         // end if rabbit and not central spot
         }
      // end for each nearby column
      }
   // end for each nearby row
   }

   // if we found none, return NULL
   if (! meals) return NULL;

   // else if we have only one, return it
   else if (1 == meals) return mealPtrs[0];

   // else pick one randomly
   return mealPtrs[rand() % meals];
}



// is there a breedable female of this type nearby, with a stud near HER?

bool empty::BreedingPair (world* w, state s)
{
   animal*  f = NULL;   // female -- must be next to THIS CELL
   bool     z = false;  // did we find any of age zero?

   // do
   do
   {
      // find a neighbor of the desired type
      f = static_cast<animal*>(FindNeighbor (w, s, f));

      // if we found one
      if (f)
      {
	 // if it's over zero age
         if (f->GetAge())
	 {
	    // if it's female
            if (animal::FEMALE == f->GetSex())
	    {
	       animal*  m = NULL;  // male -- must be next to THE FEMALE

	       // do
	       do
	       {
		  // find another such critter next to her
		  // (may or may not be next to the empty cell)
                  m = static_cast<animal*>(f->FindNeighbor (w, s, m));

		  // if we found one, and it's male and over age zero, bingo!
		  if (m)
                     if (animal::MALE == m->GetSex() && m->GetAge())
			return true;

	       // until we run out of same-species neighbors of this female
	       } while (m);
	    }
	 }
	 // else turn on the "found a puppy" flag
	 else z = true;
      }

   // until we run out of such critters nearby
   } while (f);

   // if we found any of age zero, give chance to "discover" a littermate
   if (z) return rand() % 3;

   // else we have no excuse to even try to put a puppy there
   return false;
}



// compute what occupies a cell next, if currently empty

cellptr empty::next (world* w)
{
   uint  i;

   // for however many living types we have
   for (i = EMPTY; i < STATES - 1; i++)
   {
      // whatever critter this priority slot goes to,
      //    if we have enough breedable ones nearby, and we pass the
      //    (chance-1)/chance chances to make one, return a new one
      switch (birthOrder[i])
      {
         case 'f':
            if (BreedingPair (w, FOX) &&
                rand() % 9 < static_cast<int>(fox::birthChance))
               return new fox (row, column,
                               static_cast<animal::sexEnum>(rand()%animal::SEXES),
                               0, fox::maxHunger/2);
         break;
         case 'g':
            if (LushestGrass (w) &&
                rand() % 9 < static_cast<int>(grass::birthChance))
               return new grass (row, column);
         break;
         case 'r':
            if (BreedingPair (w, RABBIT) &&
                rand() % 9 < static_cast<int>(rabbit::birthChance))
               return new rabbit (row, column,
                                  static_cast<animal::sexEnum>(rand()%animal::SEXES),
                                  0);
         break;
      }
   }

   // if we got here, nothing got born, so leave it empty
   return new empty (row, column);
}



// compute what occupies a cell next, if currently occupied by a fox

cellptr fox::next (world* w)
{
   uint  sum[STATES];
   fox*  f;

   // see how many of what are nearby
   neighbors (w, sum);

   // if this one is too old, or this one starved to death
   // (no more dying of crowding; lack of food enforces that if needed)
   if (age >= maxAge || hunger >= maxHunger)
   {
      // find hungriest fox nearby
      f = HungriestFox (w);

      // if any, and he's at or near starvation level, let him scavenge
      // (fox carcasses, being bigger than rabbits, are still worth 2 points)
      if (f) if (f->GetHunger() >= fox::maxHunger - 1) f->LetEat (2);

      // set this cell empty
      return new empty (row, column);
   }

   // if he's hungry (0 = full, 1 = indifferent), and there are rabbits around, 
   // BUT NOT MORE THAN TWICE AS MANY AS FOXES (they'll gang up on him),
   if (hunger > 1 && sum[RABBIT] && sum[RABBIT] <= 2 * sum[FOX])
   {
      // find the weakest rabbit in the immediate vicinity
      rabbit *r = WeakestRabbit (w);

      // assuage the fox's hunger, for TWO meal-points
      // (scavenged carcasses, dead of age/starvation, are ONE point)
      LetEat (2);

      // note that this empties that cell, but two-board model
      // makes it a no-op if cell already handled!
      r->kill (w);
   }

   // age him and make him a little hungrier
   return new fox (row, column, sex, age + 1, hunger + 1);
}



// compute what occupies a cell next, if currently occupied by grass

cellptr grass::next (world* w)
{
   uint  sum[STATES];
   int   t;


   // see how many of what are nearby
   neighbors (w, sum);

   // use a signed int for lushness calculations since it may go minus
   t = lushness;

   // if it's just a root/seed for now, let it sprout
   if (! t) ++t;

   // adjust randomly up or down or let stay the same
   // (effects of wear & tear, light, rain, etc.)
   t += rand() % 3 - 1;

   // increase lushness according to how many of what animals are nearby
   // (fertilizing)
   t += (sum[RABBIT] + sum[FOX]) / 2;

   // if it's now more than maximum, decrease
   t = min (t, static_cast<int>(maxLushness));

   // if, after all that, it's now zero or negative, kill it
   if (t <= 0) return new empty (row, column);

   // return new lushness
   return new grass (row, column, t);
}



// kill off a living thing

void living::kill (world* w)
{
   // mark the spot where it was, empty
   w->contents (row, column) = new empty (row, column);
   assert (w->contents (row, column));

   // nuke old contents
   delete this;
}



// compute what occupies a cell next, if currently occupied by a rabbit

cellptr rabbit::next (world* w)
{
   bool    dies = false;
   grass*  g;
   uint    sum[STATES];


   // see how many of what are nearby
   neighbors (w, sum);

   // don't worry here about getting attacked, fox::next does that.

   // if too old, or no grass around, it dies
   if (age >= maxAge || ! sum[GRASS]) dies = true;

   else
   {
      // find the lushest EDIBLE grass nearby
      g = LushestGrass (w);

      // if none edible, it STILL dies of starvation */
      if (! g) dies = true;
   }

   // if it dies (of natural causes)
   if (dies)
   {
      // look for hungriest fox nearby
      fox* f = HungriestFox (w);

      // if found, let it scavenge rabbit's carcass, for ONE meal-point
      // (fresh kills, that were at least alive and untouched, are TWO points)
      // (note that two-board model makes this a no-op if fox already handled!)
      if (NULL != f) f->LetEat (1);

      // set cell empty
      return new empty (row, column);
   }

   // else there must have been some edible grass around, so
   // eat some (no need to record anything in the rabbit; ALWAYS hungry!)
   g->GetEaten ();

   // make it a year older
   return new rabbit (row, column, sex, age + 1);
}



// create a world -- slight adaptation of twod_alloc()

world::world (uint inRows, uint inCols)
{
   uint col, row;

   // should be done in init list, but I couldn't figure out
   // how to do that AND make the method body external
   rows = inRows;
   cols = inCols;

   // assign (and assert) base address for array of row-pointers
   base = new cellptr* [rows];
   assert (base);

   // for each row, assign (and assert) its pointer
   for (row = 0; row < rows; ++row)
   {
      base[row] = new cellptr [cols];
      assert (base[row]);
   }

   // set each cell to point to an empty (and assert that it does so)
   for (row = 0; row < rows; ++row)
   {
      for (col = 0; col < cols; ++col)
      {
         contents (row, col) = new empty (row, col);
         assert (contents (row, col));
      }
   }
}



// destroy a world -- slight adaptation of twod_dealloc()

world::~world ()
{
   // clear away ALL the stuff pointed to (even edges)
   clear (true);

   // clear away the rows of pointers
   for (uint row = 0; row < rows; ++row) delete [] base[row];

   // clear away the pointer to the rows
   delete [] base;

   // mark it empty
   base = NULL;
   rows = cols = 0;
}



// clear all the stuff off a world, INCLUDING "EMPTY" CELLS

void world::clear (bool edges)
{
   uint maxCol, minCol, maxRow, minRow;

   // set DEFAULT mins/maxes -- LEAVE EDGES UNTOUCHED
   maxCol = cols - 1;
   maxRow = rows - 1;
   minCol = minRow = 1;

   // if clearing edges too, fix mins/maxes
   if (edges)
   {
      maxCol++;
      maxRow++;
      minCol = minRow = 0;
   }

   // for each spot, if not null, delete its contents and null the pointer
   for (uint row = minRow; row < maxRow; ++row)
   {
      for (uint col = minCol; col < maxCol; ++col)
      {
         cellptr& c = contents (row, col);
         if (NULL != c)
         {
            delete (c);
            c = NULL;
         }
      }
   }
}



// safe reference to a spot in the array

cellptr& world::contents (uint row, uint col)
{
   // check that args are in the proper range
   // (since accepting as uint, MUST be >= 0);
   // if ok, return REFERENCE to the intended spot
   assert (row < rows);
   assert (col < cols);
   return base[row][col];
}



// put stuff on a world, including empty spaces

void world::populate (void)
{
   // for each row and column, EXCEPT EDGES, randomize its contents.
   // equal chances for each thing, and for each setting of ranged parms
   for (uint row = 1; row < rows - 1; ++row)
   {
      for (uint col = 1; col < cols - 1; ++col)
      {
         switch (rand() % STATES)
         {
            case EMPTY: contents (row, col) = new empty (row, col); break;
            case GRASS:
               contents (row, col) =
                  new grass (row, col, rand() % grass::maxLushness);
            break;
            case RABBIT:
               contents (row, col) =
                  new rabbit (row, col,
                              static_cast<animal::sexEnum>(rand()%animal::SEXES),
                              rand() % rabbit::maxAge);
            break;
            case FOX:
               contents (row, col) =
                  new fox (row, col,
                           static_cast<animal::sexEnum>(rand()%animal::SEXES),
                           rand() % fox::maxAge,
                           rand() % fox::maxHunger);
            break;
         }
         assert (contents (row, col));
      }
   }
}



// print the current state of the world

void world::pr_state (bool overwrite)
{
   // if overwriting, get to ul corner of screen
   if (overwrite) cout << static_cast<char>(27) << "[H";

   // else separate from prior gen w/ line of dashes
   else
   {
      for (uint col = 1; col < cols - 1; col++) cout << "---";
      cout << endl;
   }

   // for each row EXCEPT TOP AND BOTTOM
   for (uint row = 1; row < rows - 1; ++row)
   {
      // for each column EXCEPT EDGES,
      // separate it, and call the contents' printing method
      for (uint col = 1; col < cols - 1; ++col)
         { cout << ' '; contents (row, col)->print (overwrite); }

      // end the line
      cout << endl;
   }
}



// see how many of what are in a given area

void world::sums (uint sm[STATES],
                  uint loRow, uint hiRow, uint loCol, uint hiCol)
{
   uint col, row;

   // reset in LOOP, in case we get more states!
   for (col = 0; col < STATES; col++) sm[col] = 0;

   // for each one in range, count in proper slot
   for (row = loRow; row <= hiRow; ++row)
      for (col = loCol; col <= hiCol; ++col)
         sm[contents (row, col)->who()]++;
}



// DECLARED FRIENDS



//new world w_new computed from old world w_old

bool update (world* w_new, world* w_old)
{
   cellptr  cp;
   state    s;
   bool     same;
   bool     stable = true;

   // clear out new world to make room for new occupants, EXCEPT EDGES
   same = (w_new == w_old);
   if (! same) w_new->clear (false);

   // for each row EXCEPT TOP AND BOTTOM
   for (uint row = 1; row < w_old->rows - 1; ++row)
   {
      // for each column EXCEPT EDGES
      for (uint col = 1; col < w_old->cols - 1; ++col)
      {
	 // stash pointer to old contents
         cp = w_old->contents (row, col);

         // set new contents according to current content's "next" method
         w_new->contents (row, col) = cp->next (w_old);
         assert (w_new->contents (row, col));

	 // if one world, old contents hasn't been deleted, so do it now
	 if (same) delete cp;

         // if it's an animal, turn off stability flag
         s = w_new->contents (row, col)->who();
         if (FOX == s || RABBIT == s) stable = false;

         // else if it's grass, and it's ROOT OR SEED, turn off stability flag
         else if (GRASS == s)
            if (! static_cast<grass*>(w_new->contents(row,col))->GetLushness())
               stable = false;
      }
   }

   // return stability flag
   return stable;
}



// DECLARATIONS OF OTHER SUPPORT ROUTINES



void BadParm (const char *desc, const char *val);



// MAIN ROUTINE (originally swiped from course)

int main (uint argc, char *argv[])
{
   const uint MAJORVER = 1;    // major version number
   const uint MINORVER = 0;    // major version number
   const char REVISION = 'a';  // sub-version revision letter

   bool    checkExt = false;   // does the user want to check extinction?
   uint    cols = 26;          // how many columns in world
   world*  curWorld;           // world updated FROM
   bool    done = false;       // is the world stable, or any species extinct?
   uint    gensShown = 0;      // how many generations we have shown
   uint    gensToShow = 100;   // how many to show
   bool    one = false;        // use only one board?
   bool    overwrite = true;   // overwrite gens on screen, or sepr8 w/dashes?
   uint    rows = 20;          // how many rows in world
   uint    sum[STATES];        // how many of what kind of space there are
   world*  tmpWorld;           // world updated INTO


   // set color properly
   color (WHITE);

   // introduce "self" to user
   cout << "ecosim " << MAJORVER << "." << MINORVER << REVISION
        << ", (C) 1997 David J. Aronson\n"
        << "Project for DigitalThink CS310 (Designing Reusable Code in C++)\n"
        << "Use 'ecosim h' for help.\n"
        << endl;

   // parse command line args, if any
   for (uint i = 1; i < argc; i++)
   {
      switch (argv[i][0])
      {
         case '1': one = true; break;

         case 'a':
            rabbit::maxAge = atoi (argv[i] + 1);
            if (! rabbit::maxAge || rabbit::maxAge > 9)
               BadParm ("rabbit maxAge", argv[i]+1);
         break;

         case 'A':
            fox::maxAge = atoi (argv[i] + 1);
            if (! fox::maxAge || fox::maxAge > 9)
               BadParm ("fox maxAge", argv[i]+1);
         break;

         case 'b':
            char c;
            uint *p;

            // for each type
            for (uint t = EMPTY + 1; t < STATES; t++)
            {
               // get the char
               c = argv[i][t * 2 - 1];

               // make sure string is terminated so search will work
               empty::birthOrder[t-1] = '\0';

               // see if we've already gotten this char
               if (strchr (empty::birthOrder, c))
                  BadParm ("birthOrder (repeat)", argv[i]+1);

               // copy into order string
               empty::birthOrder[t-1] = c;

               // point to correct chance number
               switch (c)
               {
                  case 'f':  p = &fox::birthChance;     break;
                  case 'g':  p = &grass::birthChance;   break;
                  case 'r':  p = &rabbit::birthChance;  break;
                  default:
                     BadParm ("birthOrder (invalid)", argv[i]+1);
                  break;
               }

               // assign chance
               *p = argv[i][t * 2] - '0';

               // if invalid, barf
               if (*p > 9) BadParm ("birthChance", argv[i]+1);
            }

         break;

         case 'c':
            cols = atoi (argv[i] + 1);
            if (cols < 1) BadParm ("cols", argv[i]+1);
         break;

         case 'd': overwrite = false; break;

         case 'e': checkExt = true; break;

         case 'g':
            gensToShow = atoi (argv[i] + 1);
            if (! gensToShow) BadParm ("gensToShow", argv[i]+1);
         break;

         case 'l':
            grass::maxLushness = atoi (argv[i] + 1);
            if (! grass::maxLushness || grass::maxLushness > 9)
               BadParm ("maxLushness", argv[i]+1);
         break;

         case 'r':
            rows = atoi (argv[i] + 1);
            if (rows < 1) BadParm ("rows", argv[i]+1);
         break;

         case 'h':
            cout << "Usage: ecosim [options]\n"
                 << "Options:\n"
                 << "1    = use one board, instead of 2 as instructed\n"
                 << "a{#} = make {#} maximum RABBIT age (1-9; default 6)\n"
                 << "A{#} = make {#} maximum FOX age (1-9; default 9)\n"
                 << "b{t#t#t#}: order and chance of birth (see below).\n"
                 << "c{#} = use {#} columns (1+; default 26 (each takes 3))\n"
                 << "d    = separate generations w/dashes (don't overwrite)\n"
                 << "e    = abort when any living species becomes extinct\n"
                 << "g{#} = show {#} generations (1+; default 100)\n"
                 << "l{#} = make {#} maximum grass lushness (1-9; default 9)\n"
                 << "r{#} = use {#} rows (1+; default 20)\n"
                 << "s{#} = starvation time for a fox (3-9; default 3)\n"
                 << "h    = give this help and exit\n"
                 << "Each option must be separated by a space.\n"
                 << "Example: life 1 a7 e g1000 l4 s2\n\n"
                 << "Each cell shows the type of life occupying it, and, if\n"
                 << "applicable, its age.  Types are grass, rabbit, and fox,\n"
                 << "represented as follows:\n"
                 << "FOX:     its hunger and Age\n"
                 << "GRASS:   a space and its lushness (0 is root/seed)\n"
                 << "RABBIT:  letter r and its age\n\n"
                 << "BIRTH CHANCES:\n"
                 << "Each t is an r, f, or g (rabbit/fox/grass).\n"
                 << "Each # is a digit from 0 to 9.\n"
                 << "At each empty spot, the first type will be checked to\n"
                 << "see if it can be born there.  (For animals, the spot\n"
                 << "must be next to at least two; for grass, one will do.)\n"
                 << "If so, the type has a #/9 chance of being born.  If this\n"
                 << "does not happen, the next type is checked, and so on.\n"
                 << "All types MUST be specified.  Default is r9f9g1.\n"
                 << "Note: 1 == will NEVER be born; 0 == almost always!";
         return 0;
         //break not needed due to return

         case 's':
            fox::maxHunger = atoi (argv[i] + 1);
            if (fox::maxHunger < 2 || fox::maxHunger > 9)
               BadParm ("maxHunger", argv[i]+1);
         break;

         default:
            cout << "Unrecognized option: '" << argv[i] << "'!\n"
                 << "Use '" << argv[0] << " h' for help." << endl;
         return 1;
         //break not needed due to return
      }
   }

   // add 2 to the dimensions to allow for borders
   rows += 2;
   cols += 2;

   // init random number generator, feeding it the current time
   // (in seconds since 0:00:00 1/1/1970, as per Unix standard);
   // must feed time() a NULL so it doesn't try to store value too!
   srand (static_cast<uint>(time (NULL)));

   // allocate first world
   // (can't do that until here 'cuz we don't know size!)
   curWorld = new world (rows, cols);
   assert (curWorld);

   // if we are using only one board, point second pointer as first one
   if (one) tmpWorld = curWorld;

   // else allocate it
   else
   {
      tmpWorld = new world (rows, cols);
      assert (tmpWorld);
   }

   //initialize first world to non-empty types
   curWorld->populate();         //generate initial world

   // if overwriting, CLEAR screen (further updates only overwrite)
   if (overwrite)
      cout << static_cast<char>(27) << "[H" << static_cast<char>(27) << "[J";

   // display initial state
   curWorld->pr_state (overwrite);

   do
   {
      // if this isn't the last generation
      if (gensShown < gensToShow)
      {
         // update world
         done = update (tmpWorld, curWorld);

         // swap 'em
         world* w = curWorld;
         curWorld = tmpWorld;
         tmpWorld = w;

         // display new state
         curWorld->pr_state (overwrite);
         cout << "Generation " << ++gensShown << ": ";

         // see how many of what exist
         curWorld->countoff (sum);

         // report on it all
         cout << sum[EMPTY] << " empty, "
              << sum[GRASS] << " grass, "
              << sum[RABBIT] << " rabbits, "
              << sum[FOX] << " foxes.";

         // if stable, show new status and say so
         if (done)
         {
            cout << "Stable (no animals, nor grass still at root/seed stage)"
                 << endl;
         }

         // else if user wants to check, see if any living types extinct
         else if (checkExt)
         {
            // for each living thing
            for (uint i = EMPTY + 1; i < STATES; i++)
            {
               // if there are none
               if (! sum[i])
               {
                  // say we're stopping
                  cout << "\nHALTING: ";

                  // say which one
                  switch (i)
                  {
                     case GRASS: cout << "grass is"; break;
                     case RABBIT: cout << "rabbits are"; break;
                     case FOX: cout << "foxes are"; break;
                     default: cout << "some unknown lifeform is"; break;
                  }

                  // say it's extinct
                  cout << " extinct!" << endl;

                  // turn on done-flag
                  done = true;
               }
            }
         }
      }

   // as long as we have to show more gens, and any life left
   } while (gensShown < gensToShow && ! done);

   // deallocate the worlds
   delete curWorld;
   if (! one) delete tmpWorld;

   // tell system all is okay
   return 0;
}



// OTHER SUPPORT ROUTINES



// complain about a bad parameter, and exit

void BadParm (const char *desc, const char *val)
{
   cout << "Bad value for " << desc << ": " << val << endl;
   cout << "Use 'ecosim h' for help." << endl;
   exit (1);
}



// END OF FILE ecosim.cpp