I am trying to get this Zombie/Human agent based simulation running, but I am having problems with these derived classes (Human and Zombie) who have parent class "Creature". I have 3 virtual functions declared in "Creature" and all three of these are re-declared AND DEFINED in both "Human" and "Zombie". But for some reason when I have my program call "new" to allocate memory for objects of type Human or Zombie, it complains about the virtual functions being abstract. Here's the code:
definitions.h
#ifndef definitions_h
#define definitions_h
class Creature;
class Item;
class Coords;
class Grid
{
public:
Creature*** cboard;
Item*** iboard;
int WIDTH;
int HEIGHT;
Grid(int WIDTHVALUE, int HEIGHTVALUE);
void FillGrid(); //initializes grid object with humans and zombies
void Refresh(); //calls Creature::Die(),Move(),Attack(),Breed() on every square
void UpdateBuffer(char** buffer);
bool isEmpty(int startx, int starty, int dir);
char CreatureType(int xcoord, int ycoord);
char CreatureType(int startx, int starty, int dir);
};
class Random
{
public:
int* rptr;
void Print();
Random(int MIN, int MAX, int LEN);
~Random();
private:
bool alreadyused(int checkthis, int len, int* rptr);
bool isClean();
int len;
};
class Coords
{
public:
int x;
int y;
int MaxX;
int MaxY;
Coords() {x=0; y=0; MaxX=0; MaxY=0;}
Coords(int X, int Y, int WIDTH, int HEIGHT) {x=X; y=Y; MaxX=WIDTH; MaxY=HEIGHT; }
void MoveRight();
void MoveLeft();
void MoveUp();
void MoveDown();
void MoveUpRight();
void MoveUpLeft();
void MoveDownRight();
void MoveDownLeft();
void MoveDir(int dir);
void setx(int X) {x=X;}
void sety(int Y) {y=Y;}
};
class Creature
{
public:
bool alive;
Coords Location;
char displayletter;
Creature() {Location.x=0; Location.y=0;}
Creature(int i, int j) {Location.setx(i); Location.sety(j);}
virtual void Attack() =0;
virtual void AttackCreature(Grid G, int attackdirection) =0;
virtual void Breed() =0;
void Die();
void Move(Grid G);
int DecideSquare(Grid G);
void MoveTo(Grid G, int dir);
};
class Human : public Creature
{
public:
bool armed; //if armed, chances of winning fight increased for next fight
bool vaccinated; //if vaccinated, no chance of getting infected
int bitecount; //if a human is bitten, bite count is set to a random number
int breedcount; //if a human goes x steps without combat, will breed if next to a human
int starvecount; //if a human does not eat in x steps, will die
Human() {displayletter='H';}
Human(int i, int j) {displayletter='H';}
void Attack(Grid G);
void AttackCreature(Grid G, int attackdirection);
void Breed(Grid G); //will breed after x steps and next to human
int DecideAttack(Grid G);
};
class Zombie : public Creature
{
public:
Zombie() {displayletter='Z';}
Zombie(int i, int j) {displayletter='Z';}
void Attack(Grid G);
void AttackCreature(Grid G, int attackdirection);
void Breed() {} //does nothing
int DecideAttack(Grid G);
void AttackCreature(Grid G, int attackdirection);
};
class Item
{
};
#endif
definitions.cpp
#include <cstdlib>
#include "definitions.h"
Random::Random(int MIN, int MAX, int LEN) //constructor
{
len=LEN;
rptr=new int[LEN]; //allocate array of given length
for (int i=0; i<LEN; i++)
{
int random;
do {
random = rand() % (MAX-MIN+1) + MIN;
} while (alreadyused(random,LEN,rptr));
rptr[i]=random;
}
}
bool Random::alreadyused(int checkthis, int len, int* rptr)
{
for (int i=0; i<len; i++)
{
if (rptr[i]==checkthis)
return 1;
}
return 0;
}
Random::~Random()
{
delete rptr;
}
Grid::Grid(int WIDTHVALUE, int HEIGHTVALUE)
{
WIDTH = WIDTHVALUE;
HEIGHT = HEIGHTVALUE;
//builds 2d array of creature pointers
cboard = new Creature**[WIDTH];
for(int i=0; i<WIDTH; i++)
{
cboard[i] = new Creature*[HEIGHT];
}
//builds 2d array of item pointers
iboard = new Item**[WIDTH];
for (int i=0; i<WIDTH; i++)
{
iboard[i] = new Item*[HEIGHT];
}
}
void Grid::FillGrid()
{
/* For each creature pointer in grid, randomly selects whether to initalize
as zombie, human, or empty square. This methodology can be changed to initialize
different creature types with different probabilities */
int random;
for (int i=0; i<WIDTH; i++)
{
for (int j=0; j<HEIGHT; j++)
{
Random X(1,100,1); //create a single random integer from [1,100] at X.rptr
random=*(X.rptr);
if (random < 20)
cboard[i][j] = new Human(i,j);
else if (random < 40)
cboard[i][j] = new Zombie(i,j);
else
cboard[i][j] = NULL;
}
} //at this point every creature pointer should be pointing to either
//a zombie, human, or NULL with varying probabilities
}
void Grid::UpdateBuffer(char** buffer)
{
for (int i=0; i<WIDTH; i++)
{
for (int j=0; j<HEIGHT; j++)
{
if (cboard[i][j])
buffer[i][j]=cboard[i][j]->displayletter;
else
buffer[i][j]=' ';
}
}
}
bool Grid::isEmpty(int startx, int starty, int dir)
{
Coords StartLocation(startx,starty,WIDTH,HEIGHT);
switch(dir)
{
case 1:
StartLocation.MoveUp();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 2:
StartLocation.MoveUpRight();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 3:
StartLocation.MoveRight();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 4:
StartLocation.MoveDownRight();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 5:
StartLocation.MoveDown();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 6:
StartLocation.MoveDownLeft();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 7:
StartLocation.MoveLeft();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
case 8:
StartLocation.MoveUpLeft();
if (cboard[StartLocation.x][StartLocation.y])
return 0;
}
return 1;
}
char Grid::CreatureType(int xcoord, int ycoord)
{
if (cboard[xcoord][ycoord]) //if there is a creature at location xcoord,ycoord
return (cboard[xcoord][ycoord]->displayletter);
else //if pointer at location xcoord,ycoord is null, return null char
return '\0';
}
char Grid::CreatureType(int startx, int starty, int dir)
{
Coords StartLocation(startx,starty,WIDTH,HEIGHT);
switch(dir)
{
case 1:
StartLocation.MoveUp();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 2:
StartLocation.MoveUpRight();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 3:
StartLocation.MoveRight();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 4:
StartLocation.MoveDownRight();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 5:
StartLocation.MoveDown();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 6:
StartLocation.MoveDownLeft();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 7:
StartLocation.MoveLeft();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
case 8:
StartLocation.MoveUpLeft();
if (cboard[StartLocation.x][StartLocation.y])
return (cboard[StartLocation.x][StartLocation.y]->displayletter);
} //if function hasn't returned by now, square being looked at is pointer to null
return '\0'; //return null char
}
void Coords::MoveRight() {(x==MaxX)? (x=0):(x++);}
void Coords::MoveLeft() {(x==0)? (x=MaxX):(x--);}
void Coords::MoveUp() {(y==0)? (y=MaxY):(y--);}
void Coords::MoveDown() {(y==MaxY)? (y=0):(y++);}
void Coords::MoveUpRight() {MoveUp(); MoveRight();}
void Coords::MoveUpLeft() {MoveUp(); MoveLeft();}
void Coords::MoveDownRight() {MoveDown(); MoveRight();}
void Coords::MoveDownLeft() {MoveDown(); MoveLeft();}
void Coords::MoveDir(int dir)
{
switch(dir)
{
case 1:
MoveUp();
break;
case 2:
MoveUpRight();
break;
case 3:
MoveRight();
break;
case 4:
MoveDownRight();
break;
case 5:
MoveDown();
break;
case 6:
MoveDownLeft();
break;
case 7:
MoveLeft();
break;
case 8:
MoveUpLeft();
break;
case 0:
break;
}
}
void Creature::Move(Grid G)
{
int movedir=DecideSquare(G);
MoveTo(G,movedir);
}
int Creature::DecideSquare(Grid G)
{
Random X(1,8,8); //X.rptr now points to 8 unique random integers from [1,8]
for (int i=0; i<8; i++)
{
int dir=X.rptr[i];
if (G.isEmpty(Location.x,Location.y,dir))
return dir;
}
return 0;
}
void Creature::MoveTo(Grid G, int dir)
{
Coords OldLocation=Location;
Location.MoveDir(dir);
G.cboard[Location.x][Location.y]=this; //point new location to this creature
G.cboard[OldLocation.x][OldLocation.y]=NULL; //point old location to NULL
}
void Creature::Die()
{
if (!alive)
{
delete this;
this=NULL;
}
}
void Human::Breed(Grid G)
{
if (!breedcount)
{
Coords BreedLocation=Location;
Random X(1,8,8);
for (int i=0; i<8; i++)
{
BreedLocation.MoveDir(X.rptr[i]);
if (!G.cboard[BreedLocation.x][BreedLocation.y])
{
G.cboard[BreedLocation.x][BreedLocation.y])=new Human(BreedLocation.x,BreedLocation.y);
return;
}
}
}
}
int Human::DecideAttack(Grid G)
{
Coords AttackLocation=Location;
Random X(1,8,8);
int attackdir;
for (int i=0; i<8; i++)
{
attackdir=X.rptr[i];
switch(G.CreatureType(Location.x,Location.y,attackdir))
{
case 'H':
break;
case 'Z':
return attackdir;
case '\0':
break;
default:
break;
}
}
return 0; //no zombies!
}
int AttackRoll(int para1, int para2)
{
//outcome 1: Zombie wins, human dies
//outcome 2: Human wins, zombie dies
//outcome 3: Human wins, zombie dies, but human is bitten
Random X(1,100,1);
int roll= *(X.rptr);
if (roll < para1)
return 1;
else if (roll < para2)
return 2;
else
return 3;
}
void Human::AttackCreature(Grid G, int attackdirection)
{
Coords AttackLocation=Location;
AttackLocation.MoveDir(attackdirection);
int para1=33;
int para2=33;
if (vaccinated)
para2=101; //makes attackroll > para 2 impossible, never gets infected
if (armed)
para1-=16; //reduces chance of zombie winning fight
int roll=AttackRoll(para1,para2);
//outcome 1: Zombie wins, human dies
//outcome 2: Human wins, zombie dies
//outcome 3: Human wins, zombie dies, but human is bitten
switch(roll)
{
case 1:
alive=0; //human (this) dies
return;
case 2:
G.cboard[AttackLocation.x][AttackLocation.y]->alive=0;
return; //zombie dies
case 3:
G.cboard[AttackLocation.x][AttackLocation.y]->alive=0; //zombie dies
Random X(3,7,1); //human is bitten
bitecount=*(X.rptr);
return;
}
}
int Zombie::DecideAttack(Grid G)
{
Coords AttackLocation=Location;
Random X(1,8,8);
int attackdir;
for (int i=0; i<8; i++)
{
attackdir=X.rptr[i];
switch(G.CreatureType(Location.x,Location.y,attackdir))
{
case 'H':
return attackdir;
case 'Z':
break;
case '\0':
break;
default:
break;
}
}
return 0; //no zombies!
}
void Zombie::AttackCreature(Grid G, int attackdirection)
{
int reversedirection;
if (attackdirection < 9 && attackdirection>0)
{
(attackdirection<5)? (reversedirection=attackdirection+4):(reversedirection=attackdirection-4);
}
else
reversedirection=0; //this should never happen
//when a zombie attacks a human, the Human::AttackZombie() function is called
//in the "reverse" direction, utilizing that function that has already been written
Coords ZombieLocation=Location;
Coords HumanLocation=Location;
HumanLocation.MoveDir(attackdirection);
if (G.cboard[HumanLocation.x][HumanLocation.y]) //if there is a human there, which there should be
G.cboard[HumanLocation.x][HumanLocation.y]->AttackCreature(G,reversedirection);
}
void Zombie::Attack(Grid G)
{
int attackdirection=DecideAttack(G);
AttackCreature(G,attackdirection);
}
main.cpp
#include <cstdlib>
#include <iostream>
#include "definitions.h"
using namespace std;
int main(int argc, char *argv[])
{
Grid G(500,500);
system("PAUSE");
return EXIT_SUCCESS;
}