(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