//-------------------------------------------------------------------------
//
// rabbit4.cpp
//
// Written by:    Simon Parsons
// Last modified: 4th November 2007

// A revised version of the ecosystem with virtual function definitions and
// abstract classes.

// Include the necessary header files

#include <stdlib.h>
#include <time.h>
#include <iostream>
using namespace std;

//-------------------------------------------------------------------------
//

enum direction {north, east, south, west};
const int SIZE = 5;
const int CARROTS = 3;

//-------------------------------------------------------------------------
//
// point
//
// Our old favorite
//

class point{

private:
  
  // The only attributes of the point are x and y coordinates.

  int x, y;

public:

  // The interface methods get the x and y coordinates of the point,
  // set the coordinates, and print their values.

  int  getX() const;
  int  getY() const;
  void set(int x, int y);
  void print() const;

};

int point::getX() const{
  return x;
}

int point::getY() const{
  return y;
}

void point::set(int x, int y){
  this->x = x;
  this->y = y;
}

void point::print() const{
  cout << "(" << x << ", " << y << ")" << endl;
}

//
// End of point

//-------------------------------------------------------------------------
//
// living
//
// A living thing has a location.

class living {

protected:

  // Location and eaten are protected to allow them to be modified by
  // derived classes.

  point location;
  bool  eaten;

public:
  
  // Most of the interface methods for living are just the same as for a
  // point, and just call the ones for point.

  int  getX() const;
  int  getY() const;
  void set(int x, int y);
  void print() const;

  // We also have a constructor that ensures thingss are not created
  // already eaten and a virtual method for being eaten.

  living(){eaten = false;};
  virtual void beEaten() = 0;
};

int living::getX() const{
  return location.getX();
}

int living::getY() const{
  return location.getY();
}

void living::set(int x, int y){
  location.set(x, y);
}

void living::print() const{
  location.print();
}

//
// End of living

//-------------------------------------------------------------------------
//
// plant
//
// A plant is a kind of thing that doesn't like to beEaten.

class plant : public living {

public:

  void beEaten();
};

void plant::beEaten(){
  cout << "Pah!" << endl;
  eaten = true;
}

//
// End of plant

//-------------------------------------------------------------------------
//
// carrot
//
// A carrot is a kind of plant which responds differently to being eaten.

class carrot : public plant {

public:

  void beEaten();
};

void carrot::beEaten(){
  cout << "Oh no, not again!" << endl;
  eaten = true;
}

//
// End of plant

//-------------------------------------------------------------------------
//
// animal
//
// An animal is a thing that can move, eat, be eaten, and be
// hungry. It contains a data member consumed that counts how many
// things it has eaten.

class animal : public living {

protected:

  int consumed;

public:

  animal(){consumed = 0;};
  void move();
  void move(direction d);
  void eat();
  bool hungry();
};

// Pick a random direction to move in, and then move one unit in that direction

void animal::move(){
  direction d;
  d = static_cast<direction>(rand() % 4);
  move(d);
}

// When the animal moves, the world "wraps around", so, for example,
// if the animal is at the east end of the world and moves east, it
// appears at the far west end.
//
// The overloaded function move provides this abilty.

void animal::move(direction d){

  int x = location.getX();
  int y = location.getY();

  // Find a new x and y coordinate

  if (d == north){ 
      y = (y + 1) % SIZE;
    }

  if (d == south){ 
      y = (y - 1);
      if (y < 0){
	  y = SIZE;
	}
    }

  if (d == east){ 
      x = (x + 1) % SIZE;
    }

  if (d == west)
    { 
      x = (x - 1) % SIZE;
      if (x < 0){
	  x = SIZE;
	}
    }

  // Set the location of the animal to those coordinates

  location.set(x, y);
};

void animal::eat(){
  cout << "Yum!" << endl;
  consumed++;
}

// A animal is hungry unless it has eaten something

bool animal::hungry(){
  if (consumed == 0){
    return true;
  }
  else {
    return false;
  }
}

//
// End of animal

//-------------------------------------------------------------------------
//
// rabbit
//
// A rabbit is a kind of animal with its own way to beEaten.

class rabbit : public animal {

public:

  void beEaten();
};

void rabbit::beEaten(){
  cout << "Drat that fox!" << endl;
  eaten = true;
}

//
// End of rabbit

//-------------------------------------------------------------------------
//
// fox
//
// A fox is a kind of animal with its own way of moving. 
//
// Note that even though foxes don't get eaten in this model, we need
// to have a function definition for beEaten, or else fox is an
// abstract class.

class fox : public animal {

public:
  void move();
  void move(direction d);
  void beEaten();
};

void fox::move(){
  direction d;
  d = static_cast<direction>(rand() % 4);
  move(d);
}

void fox::move(direction d){

  int x = location.getX();
  int y = location.getY();

  // Find a new x and y coordinate

  if (d == north){ 
      y = (y + 2) % SIZE;
    }

  if (d == south){ 
      y = (y - 2);
      if (y < 0){
	  y = SIZE;
	}
    }

  if (d == east){ 
      x = (x + 2) % SIZE;
    }

  if (d == west)
    { 
      x = (x - 2) % SIZE;
      if (x < 0){
	  x = SIZE;
	}
    }

  location.set(x, y);
}

void fox::beEaten(){
};

//
// End of fox

//-------------------------------------------------------------------------
//
// world
//
// Class world represents the little ecosystem we are building
//

class world {

private:

  // The world contains a rabbit and some carrots.

  rabbit peter;
  fox    mrTodd;
  carrot carrots[CARROTS];

public:

  // There are methods to get the positions of the rabbit and the
  // carrots. There are also methods to determine if a carrot is at a
  // particular location, to make the rabbit move around, and to print
  // the position of all objects in the world.

  void  setRabbit(int x, int y);
  void  setFox(int x, int y);
  void  setCarrot(int x, int y, int index);
  bool  isCarrotAt(int x, int y) const;
  int   whichCarrotAt(int x, int y) const;
  void  rabbitRoam();
  void  print() const;  
};

// Setting the locations of rabbit, fox, and carrots is easy since
// they have methods to do this.

void world::setRabbit(int x, int y){
  peter.set(x, y);
}

void world::setFox(int x, int y){
  mrTodd.set(x, y);
}

void world::setCarrot(int x, int y, int index){
  carrots[index].set(x, y);
}

// Identify if there is a carrot at a specific location. Step through
// the carrots array checking if x and y coordinates line up.
 
bool world::isCarrotAt(int x, int y) const{
  bool match = false;
  int  index = 0;

  while(!match && index < CARROTS){
    if((x == carrots[index].getX()) && 
       (y == carrots[index].getY())){
      match = true;
    }
    index++;
  }

  return match;

}

// If we there is a carrot at a specific location, which one is it?
// Assumes that there is a match, so only call it under those circumstances.
// Not very elegant, but it works.

int world::whichCarrotAt(int x, int y) const{
  for(int i = 0; i< CARROTS; i++){
    if((x == carrots[i].getX()) && 
       (y == carrots[i].getY())){
      return i;
    }
  }
}

// Make the rabbit roam around, make the rabbit eat if it is able to
// (when it is at the same location as a carrot) and if it does, make
// the carrot respond.
//
// At the same time, make the fox roam, and if it is in the same location
// as the rabbit, eat the rabbit.
//
// Roaming stops as soon as either the rabbit or the fox eats.

void world::rabbitRoam(){
  int index;
  while(peter.hungry() && mrTodd.hungry()){
    peter.move();
    peter.print();
    mrTodd.move();
    cout << "\t";
    mrTodd.print();
    if (isCarrotAt(peter.getX(), peter.getY()))
      {
	peter.eat();
	index = whichCarrotAt(peter.getX(), peter.getY());
	carrots[index].beEaten();
      }
    if ((peter.getX() == mrTodd.getX()) && (peter.getY() == mrTodd.getY()))
      {
	mrTodd.eat();
	peter.beEaten();
      }
  }
}

// Printing the world is printing the location of the objects in the
// world. We use their print methods to do this.

void world::print() const {
  cout << "The rabbit is at:" << endl;
  peter.print();
  cout << "The fox is at:" << endl;
  mrTodd.print();
  cout << "The carrots are at: " << endl;
  for(int i = 0; i < CARROTS; i++) {
    carrots[i].print();
  }
}

//-------------------------------------------------------------------------


// In main() we create a rabbit and some carrots at random locations,
// and the rabbit then moves randomly until it finds a carrot.

int main( void ) {

  world earth;
  int x, y;

  // First set up the objects in the world.

  srand(time(NULL));

  x = rand() % SIZE;
  y = rand() % SIZE;

  earth.setRabbit(x, y);

  x = rand() % SIZE;
  y = rand() % SIZE;

  earth.setFox(x, y);
    
  for(int i = 0; i < CARROTS; i++){
      x = rand() % SIZE;
      y = rand() % SIZE;
      earth.setCarrot(x, y, i);
  }

  earth.print();

  // Now the rabbit roams randomly, until it finds a carrot and eats it.

  cout << "Rabbit and Fox roaming..." << endl;
  earth.rabbitRoam();

  return 0;
}

// end of main()
