//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                             ANTHILL                                  //
//                                by                                    //
//                     Mark Weaver and Alex Lane                        //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include <graphics.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <dos.h>
#include <ctype.h>
#include <string.h>
#include "anthill.h"
					
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))

//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                          Global Variables                            //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
float gfCellSizeX;              // Width (in pixels) of a cell
float gfCellSizeY;              // Height (in pixels) of a cell
unsigned guFoodUnits[100][100]; // How many units of food in each cell
int gnTimeToHatch;              // Cycles required to become a worker ant
int gnDropDistance;             // Distance at which to drop food for queen
unsigned long gulTotalFood;     // Total units of food on board
unsigned guWorkers;             // Total workers on board
unsigned guEggs;                // Total number of eggs on board
unsigned long gulCycle;         // Which life cycle are we in?
unsigned guWorkersPortion;      // What a worker eats from each food packet
unsigned guMaxLifeSpan;         // Longest life span
unsigned guBirthRate;           // Percentage of cycles queen lays an egg
unsigned guMaxSpeed;            // Maximum speed for worker ants
unsigned guQueensEnergy;        // Queens extra starting energy level
unsigned guInitEggs;            // Initial # of eggs
unsigned guInitWorkers;         // Initial # of worker ants
unsigned guInitFood;            // Initial # of cells with food
unsigned guPause;               // Milliseconds to pause each cycle
Queen *gQueen;                  // Queen ant
Worker *headOfAntList;          // Head of linked list of worker ants


//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                        MOVER class functions                         //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

Mover::Mover(unsigned uMaxRng, unsigned uMaxSpd, int nStartX, int nStartY)
{
  uMaxRange = uMaxRng;
  uMaxSpeed = uMaxSpd;
  nX = nInitX = nStartX;
  nY = nInitY = nStartY;
  bVisible = FALSE;
  nVelX = -uMaxSpeed + random(uMaxSpeed + 1);
  nVelY = -uMaxSpeed + random(uMaxSpeed + 1);
}

int Mover::getX( void )
{
return nX;
}

int Mover::getY( void )
{
return nY;
}

BOOLEAN Mover::moveTo(int nNewX, int nNewY )
{
  if ((distance(nNewX, nNewY, nInitX, nInitY) <= uMaxRange || !uMaxRange)){
    erase();
	 if ((nX = nNewX) < 0){
		nX = 0;
	 }
	 else 
		if (nX > 99) {
			nX = 99;
			}

	 if ((nY = nNewY) < 0){
		nY = 0;
	 }
	 else 
		if (nY > 99) {
		nY = 99;
		}

	 draw();
	 return( TRUE );
  }
  else {
	 return( FALSE );
  }
}

BOOLEAN Mover::move( void )
{
  int nNewX, nNewY;

  nNewX = nX + nVelX;
  nNewY = nY + nVelY;

  if ((distance(nNewX, nNewY, nInitX, nInitY) <= uMaxRange || !uMaxRange)){
    erase();
	 if ((nX = nNewX) < 0) {
		nX = 0;
		nVelX = -nVelX;
	 } 
	 else 
		if (nX > 99) {
			nX = 99;
			nVelX = -nVelX;
			}
	 if ((nY = nNewY) < 0) {
		nY = 0;
		nVelY = -nVelY;
	 }
	 else 
		if (nY > 99) {
		nY = 99;
		nVelY = -nVelY;
	 }
	 draw();

    // Turn once in a while
    if (random(100) < 10){
      nVelX += random(3) - 1;
      nVelY += random(3) - 1;
		if (nVelX > uMaxSpeed) {
		  nVelX = uMaxSpeed;
		}
		else 
			if (nVelX < -uMaxSpeed) {
				nVelX = -uMaxSpeed;
			}

		if (nVelY > uMaxSpeed) { 
		  nVelY = uMaxSpeed;
		}
		else 
			if (nVelY < -uMaxSpeed) {
				nVelY = -uMaxSpeed;
			}

      // Make sure I do not stop
		if (nVelX == 0 && nVelY == 0) {
		  nVelX = 1+random(uMaxSpeed-1);
		  nVelY = -1-random(uMaxSpeed-1);
		}
	 }

	 return( TRUE );
  }
  else {
	 draw();
	 return( FALSE );
  }
}

void Mover::show( void )
{
  if (!bVisible){
	 draw();
	 bVisible = TRUE;
  }
}

void Mover::erase( void )
{
  moverDrawFunc(nX, nY, FALSE, BACKGROUND );
}

void Mover::draw( void )
{
  moverDrawFunc(nX, nY, TRUE, color);
}

void Mover::moverDrawFunc(int nX, int nY, BOOLEAN bVisible, int nColor)
{
	setfillstyle(SOLID_FILL, bVisible ? nColor : BACKGROUND);
	bar(nX * gfCellSizeX,        nY * gfCellSizeY,
			(nX + 0.5) * gfCellSizeX, (nY + 1) * gfCellSizeY - 1);
} 


//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                          ANT class functions                         //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

Ant::Ant( int Range, int Speed, int nX, int nY, int Energy) :
			 Consumer( Energy ), // constructor 
			 Mover( Range, Speed, nX, nY ) // constructor
{
  bDead= FALSE;
}

// Dying ant updates counters and drops any food he is carying
void Ant::die(void)
{
  bDead = TRUE;
  nEnergy = 0;
  erase(); 
}

void Ant::doTheAntThing(void)
{
// Age the ant
nAge++;

// Every ant uses one energy unit per cycle
nEnergy -= 1;

if (nEnergy<1 || nAge > nLifeSpan) {
	die();
	}
else { 
	if (bEgg) {
		// force redraw in case egg has been walked on
		moveTo(nX, nY); 
		if (nAge > gnTimeToHatch) {
			bEgg = FALSE;
			color = GREEN;	
			guEggs--;
			guWorkers++;
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                       QUEEN class functions                          //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

Queen::Queen( unsigned InitEnergy, int QRange, int QSpeed, 
				  int QIntitX, int QInitY, unsigned _birthRate ) : 
	Ant(QRange, QSpeed, QIntitX, QInitY, random(100)+InitEnergy )
{
  birthRate = _birthRate;
  color = YELLOW;
}


// Queen ant lays eggs once in a while
void Queen::layEgg(void)
{
  new Worker(TRUE, nX, nY);
  guEggs++;
  nEnergy -= MIN(150, nEnergy);
}


void Queen::doTheAntThing(void)
{
// Every ant uses one energy unit per cycle
nEnergy -= 1;

nVelX = random(uMaxSpeed+1)*(random(2) ? 1 : -1 );
nVelY = random(uMaxSpeed+1)*(random(2) ? 1 : -1 );

if (nEnergy<1) {
	die();
	}
else {
// Turn around if at limits of area!
if (distance(nInitX, nInitY, nX + nVelX, nY + nVelY) > uMaxRange) {
	nVelX = -nVelX ;
	nVelY = -nVelY ;
	}
							 
	// Move somewhere
	move();

	if (guFoodUnits[nX][nY]) {
		eatFood(guFoodUnits[nX][nY]);
		}
	// need a minimum of 750 units to be able to lay an egg
	// (the idea here is to conserve energy)
	if ((nEnergy > 750) && (random(100) < birthRate)) {
		layEgg();
		}
	// if the energy level gets too high, improve the chances of
	// laying an egg; and vice versa
	// keep the birthrate between 50 and 5 percent.
	if ((nEnergy > 5000) && (birthRate < 49)) birthRate++;
	if ((nEnergy < 3000) && (birthRate > 6)) birthRate--;
	}
}

//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                       WORKER class functions                         //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

Worker::Worker( BOOLEAN bEggYes, int nX, int nY ) : 
			 Ant( 0, guMaxSpeed, nX, nY, random(100)+50 )
{
  bEgg = bEggYes;
  addToList();
  nFood = 0;    // Start out carrying no food
  // ants live for lifespan plus/minus 20%
  nLifeSpan = (0.8 * guMaxLifeSpan) + random(0.4 * guMaxLifeSpan);

  if (bEggYes){
	 color = CYAN;
	 nAge = 0;
  }else{
	 color = GREEN;
	 nAge = gnTimeToHatch + 1;
  }
}

Worker::~Worker(void)
{
  // Remove myself from the ant list
  if (prevWorker == NULL) {
	 headOfAntList = nextWorker;
  }
  else {
    prevWorker->nextWorker = nextWorker;
  }
  if (nextWorker != NULL) {
    nextWorker->prevWorker = prevWorker;
  }
}

// Lie down and be counted (out)!!
void Worker::die(void)
{
  // Drop any food before going to ant heaven
  if (nFood) {
	 guFoodUnits[nX][nY] += nFood;
  }
  Ant::die();           // Call ant die function

  if (bEgg){
	 guEggs--;
  }else{
    guWorkers--;
  }
}


// Add new ant to head of doubly linked list
void Worker::addToList()
{
  // Place myself at the head of doubly linked list of ants
  nextWorker = headOfAntList;
  headOfAntList = this;
  prevWorker = NULL;
  if (nextWorker != NULL) {
    nextWorker->prevWorker = this;
  }
}

// Drop food for the Queen to eat
void Worker::dropFood(void)
{
  int xDist, yDist;

  guFoodUnits[nX][nY] += nFood;
  nFood = 0;
  color = GREEN;
  showFood(nX, nY, TRUE);

  // Move AWAY from queen
  if (nX < gQueen->getX()) {
	 xDist = -uMaxSpeed;
  }
  else 
	if (nX > gQueen->getX()) {
		xDist = uMaxSpeed;
	}
  if (nY < gQueen->getY()) {
	 yDist = -uMaxSpeed;
  }
  else 
	if (nY > gQueen->getY()) {
		yDist = uMaxSpeed;
	}
  moveTo(nX + xDist, nY + yDist);
}

// Grab food to carry to queen, after eating a portion for self
void Worker::grabFood(void)
{
// Eat some food to replenish energy
eatFood(guWorkersPortion);

// Pick up rest to carry to queen
nFood = guFoodUnits[nX][nY];
guFoodUnits[nX][nY] = 0;
showFood(nX, nY, FALSE);
color = LIGHTRED;
}

// Carry food toward queen ant
void Worker::moveTowardQueen(void)
{
  int xDist, yDist;

  if (nX > gQueen->getX()) {
	 xDist = -1 * MIN(uMaxSpeed / 2, nX - gQueen->getX());
  }
  else 
	if (nX < gQueen->getX()) {
		xDist = MIN(uMaxSpeed / 2, gQueen->getX() - nX);
	}
	else {
		xDist = 0;
	}

  if (nY > gQueen->getY()) {
	  yDist = -1 * MIN(uMaxSpeed / 2, nY - gQueen->getY());
  }
  else 
	  if (nY < gQueen->getY()) {
		  yDist = MIN(uMaxSpeed / 2, gQueen->getY() - nY);
	  }
	  else {
		  yDist = 0;
	  }

  moveTo(nX + xDist, nY + yDist );
}


// Roam around randomly, looking for food
void Worker::lookForFood(void)
{
if (!bEgg) {
  move();
  if (guFoodUnits[nX][nY]) {
	 grabFood();
	 }
  }
}

void Worker::doTheAntThing(void)
{
Ant::doTheAntThing(); // age, reduce energy, die if necessary
if (nFood) {
	if (distance(nX, nY, gQueen->getX(), gQueen->getY()) <= gnDropDistance) {
		dropFood();
		}
	else {
		moveTowardQueen();
		}
	}
else {
	lookForFood();
	}
}


//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                     CONSUMER class functions                         //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

void Consumer::eatFood(int nUnits)
{
  int nX = getX();
  int nY = getY();
  nEnergy += MIN(nUnits, guFoodUnits[nX][nY]);
  gulTotalFood -= MIN(nUnits, guFoodUnits[nX][nY]);
  if (!(guFoodUnits[nX][nY] -= MIN(nUnits, guFoodUnits[nX][nY]))){
	 showFood(nX, nY, FALSE);
  }
}



//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                           Support Functions                          //
//                                                                      //
//////////////////////////////////////////////////////////////////////////


int distance(int nX1, int nY1, int nX2, int nY2)
{
  double sumSq = (double)((nX2-nX1)*(nX2-nX1) + (nY2-nY1)*(nY2-nY1));
  if (sumSq > 0.0){
    return((int) sqrt(sumSq));
  }else{
    return(0);
  }
}

void showFood(int nX, int nY, BOOLEAN bVisible)
{
  setfillstyle(SOLID_FILL, bVisible ? WHITE : BACKGROUND);
  bar((nX + 0.5) * gfCellSizeX + 1,   nY * gfCellSizeY,
      (nX + 1) * gfCellSizeX - 1, (nY + 1) * gfCellSizeY - 1);
}

void showStatus(BOOLEAN bShowAll)
{
  char szMsg[100];

  setfillstyle(SOLID_FILL, BLACK);
  if (bShowAll){
    bar(0,getmaxy()-19,getmaxx(),getmaxy());
    sprintf(szMsg, "Workers: %4u   Eggs: %4u   Food: %7lu    Cycle: %6lu   \
Queen: %5d     <X-eXit>", guWorkers, guEggs, gulTotalFood, gulCycle++,
			gQueen->nEnergy);
    outtextxy(5,getmaxy()-16,szMsg);
  }else{
    sprintf(szMsg, "%4u", guWorkers);
    bar(5+8*8, getmaxy()-19, 5+8*13, getmaxy());
    outtextxy(5+8*8, getmaxy()-16, szMsg);

    sprintf(szMsg, "%4u", guEggs);
    bar(5+8*20, getmaxy()-19, 5+8*24, getmaxy());
    outtextxy(5+8*20, getmaxy()-16, szMsg);

    sprintf(szMsg, "%7lu", gulTotalFood);
    bar(5+8*32, getmaxy()-19, 5+8*39, getmaxy());
    outtextxy(5+8*32, getmaxy()-16, szMsg);

    sprintf(szMsg, "%6lu", gulCycle++);
    bar(5+8*47, getmaxy()-19, 5+8*53, getmaxy());
    outtextxy(5+8*47, getmaxy()-16, szMsg);

    sprintf(szMsg, "%5d", gQueen->nEnergy);
    bar(5+8*61, getmaxy()-19, 5+8*67, getmaxy());
    outtextxy(5+8*61, getmaxy()-16, szMsg);
  }
}

void runSimulation(void)
{
  int grMode, grDriver;      // Used to initialize graphics device
  int nErrCode;              // Results of graphics operation
  char ch;


  // Clear out accumulators
  headOfAntList = NULL;
  gulTotalFood = 0L;
  guWorkers = 0;
  guEggs = 0;

  grDriver = DETECT;
  initgraph(&grDriver, &grMode, "");

  nErrCode = graphresult();

  if (nErrCode != grOk){
    printf("\n\nGraphics error: %s\n", grapherrormsg(nErrCode));
  }else{
	 // Determine size of cells for 100 x 100 grid
    gfCellSizeX = getmaxx() / 100.0;
    gfCellSizeY = (getmaxy() - 20) / 100.0; // Allow room for status line

    // Draw line to seperate status area
    line(0,getmaxy() - 20, getmaxx(), getmaxy() - 20);

    // Reset timer
    gulCycle      = 0;      // Start timer at 0


    // Create and show the queen ant
	 gQueen = new Queen( guQueensEnergy, 20, 2, 10, 10, guBirthRate );
	 gQueen->show();

    // Now the Worker ants and eggs will be created. The pointers to the
    // newly created ants are not kept because they are automatically put
    // in a doubly linked list by the constructor function. The head of the
    // list is the global variable head.

    // Create worker ants
    for (int i=0; i<guInitWorkers; i++){
		Worker *temp = new Worker(FALSE, random(100), random(100));
		temp->show();
		guWorkers++;
	 }

    // Create the eggs
    for (i=0; i<guInitEggs; i++){
		Worker *temp = new Worker(TRUE, random(30), random(30));
		temp->show();
		guEggs++;
	 }

    // Create some food
    for (i=0; i<100; i++){
      for (int j=0; j<100; j++){
        guFoodUnits[i][j] = 0;
      }
    }
    for (i=0; i<guInitFood; i++){
      int nY = random(100);
      int nX = (nY>50) ? random(100) : (50 + random(50));
      guFoodUnits[nX][nY] = random(1000) + 1000;
      gulTotalFood += guFoodUnits[nX][nY];
      showFood(nX, nY, TRUE);
    }

    BOOLEAN bDone = FALSE;
    showStatus(TRUE);

    while(!bDone){

      // Update status line
      showStatus(FALSE);

      // Pause
      delay(guPause);

      // Process the queen ant
      if (!gQueen->bDead){
        gQueen->doTheAntThing();
      }

      // Process all ants that are in list
		Worker *thisAnt = headOfAntList;
		while(thisAnt != NULL){
		  Worker *nextWorker = thisAnt->next();
		  if (!thisAnt->bDead){
			 thisAnt->doTheAntThing();
		  }
		  if (thisAnt->bDead){
			 delete thisAnt;
        }
        thisAnt = nextWorker;
      }

      // Are all ants dead?
		if (gQueen->bDead && headOfAntList == NULL){
        bDone = TRUE;
        bar(5+8*67, getmaxy()-19, getmaxx(), getmaxy());
        outtextxy(5+8*67,getmaxy()-16,"<Hit A Key>");
        showStatus(FALSE);
        sound(6000);
        delay(1000);
        nosound();
        getch();
      }

      // Has user hit X key?
      if (kbhit()){
        ch = toupper(getch());
        bDone = (ch == 'X');
        if (!bDone){
          sound(1000);
          delay(500);
          nosound();
        }
      }
    }

    delete gQueen;

    // Go back to original screen mode
    closegraph();
  }
}

void resetDefaults(void)
{
  /////////////////////////////////////////////////////////////////
  // These are the parameters to adjust to affect the simulation //
  /////////////////////////////////////////////////////////////////
  gnDropDistance   = 4;     // Drop food when distance 8 away from queen
  gnTimeToHatch    = 20;    // Cycles to become a worker ant
  guInitWorkers    = 2;     // Initial # of workers
  guInitEggs       = 2;     // Initial # of eggs
  guInitFood       = 400;   // Initial # of food cells
  guPause          = 0;     // Milliseconds to pause each cycle
  guWorkersPortion = 250;   // What a worker eats from each food cell found
  guBirthRate      = 10;    // How many of 100 rounds does egg get laid
  guMaxLifeSpan    = 200;   // Maximum age ant will live to
  guMaxSpeed       = 2;     // Maximum speed that worker ant will move
  guQueensEnergy   = 2000;  // Queens extra starting energy
}

void displayValues(void)
{
  clrscr();
  gotoxy(26,1);
  printf("ANTHILL  by Mark Weaver & Alex Lane");
  gotoxy(35,24);
  printf("SELECT ONE");

  gotoxy(2,5);
  printf("[W]               initial number of Worker ants");
  gotoxy(2,6);
  printf("[E]               initial number of Eggs");
  gotoxy(2,7);
  printf("[F]               initial number of cells containing Food");
  gotoxy(2,8);
  printf("[B]               Birth rate (chance in 100 of a birth each cycle)");
  gotoxy(2,9);
  printf("[L]               worker ant's maximum Life span");
  gotoxy(2,10);
  printf("[V]               worker ant's maximum Velocity (cells per cycle)");
  gotoxy(2,11);
  printf("[P]               Portion of food worker eats each time food is \
found");
  gotoxy(2,12);
  printf("[H]               number of cycles it takes an egg to Hatch");
  gotoxy(2,13);
  printf("[Q]               Queens initial energy level");
  gotoxy(2,14);
  printf("[D]               Delay between each cycle (in milliseconds)");
  gotoxy(2,16);
  printf("[R]               Run simulation");
  gotoxy(2,17);
  printf("[X]               eXit to DOS");

  gotoxy(7,5);
  printf("%5u", guInitWorkers);
  gotoxy(7,6);
  printf("%5u", guInitEggs);
  gotoxy(7,7);
  printf("%5u", guInitFood);
  gotoxy(7,8);
  printf("%5u", guBirthRate);
  gotoxy(7,9);
  printf("%5u", guMaxLifeSpan);
  gotoxy(7,10);
  printf("%5u", guMaxSpeed);
  gotoxy(7,11);
  printf("%5u", guWorkersPortion);
  gotoxy(7,12);
  printf("%5u", gnTimeToHatch);
  gotoxy(7,13);
  printf("%5u", guQueensEnergy);
  gotoxy(7,14);
  printf("%5u", guPause);
}

unsigned getNumber(char *szPrompt, unsigned nMin, unsigned nMax, 
                   unsigned nDefault)
{
  char szBuff[500];
  int nRetval;
  

  gotoxy(1,20);
  clreol();
  gotoxy(1,21);
  clreol();
  gotoxy(1,20);
  printf("%s", szPrompt);
  gotoxy(1,21);
  printf("Range (%u to %u), <Return> for %u : ", nMin, nMax, nDefault);
  gets(szBuff);
  gotoxy(1,20);
  clreol();
  gotoxy(1,21);
  clreol();

  // strip off leading spaces
  char *ptr = szBuff;
  while(*ptr == ' '){
    ptr++;
  }

  if (*ptr == '\0'){
    return(nDefault);
  }else{
    sscanf(ptr, "%d", &nRetval);
    return(nRetval);
  }

}

char getChoice(void)
{
  char chRetval = ' ';

  while (strchr("WEFBLVPHQDRX", chRetval) == NULL){
    chRetval = toupper(getch());
  }
  return(chRetval);
}


//////////////////////////////////////////////////////////////////////////
//                                                                      //
//                            Main Program                              // 
//                                                                      //
//////////////////////////////////////////////////////////////////////////

main()
{

  // Initialize random number generator
  randomize();

  resetDefaults();

  BOOLEAN bDone = FALSE;
  while(!bDone){
    displayValues();
    char ch = getChoice();
    switch(ch){
      case 'X':
        bDone = TRUE;
        break;

      case 'H':   // hatch time
        gnTimeToHatch = getNumber("Enter the number of cycles it takes an \
egg to hatch",
                                 5, 100, gnTimeToHatch);
        break;

      case 'W':   // Initial workers
        guInitWorkers = getNumber("Enter initial number of worker ants",
                                   0, 100, guInitWorkers);
        break;

      case 'E':   // Initial eggs
        guInitEggs = getNumber("Enter initial number of worker eggs",
                                   0, 100, guInitEggs);
        break;

      case 'F':   // Initial number of food cells
        guInitFood = getNumber("Enter number of cells initially containing \
food",
                           0, 1000, guInitFood);
        break;

      case 'B':   // Birth rate
        guBirthRate = getNumber("Enter percentage chance of a birth each cycle",
											5, 50, guBirthRate);
        break;

      case 'V':   // Max velocity
        guMaxSpeed = getNumber("Enter workers maximum speed", 1, 20,
                                  guMaxSpeed);
        break;

      case 'Q':   // Queens start energy
        guQueensEnergy = getNumber("Enter queens initial energy level",
                                   1000, 100000, guQueensEnergy);
        break;

      case 'L':   // Max worker lifespan
        guMaxLifeSpan = getNumber("Enter maximum lifespan of worker ants",
                                   50, 500, guMaxLifeSpan);
        break;

      case 'P':   // Workers portion of food
        guWorkersPortion = getNumber("Enter amount of food a worker eats \
each time it finds some food",
                                     0, 500, guWorkersPortion);
        break;

      case 'D':   // Delay between each round
        guPause = getNumber("Enter delay for each cycle in milliseconds",
                            0, 10000, guPause);
        break;

      case 'R':   // Run simulation
        runSimulation();
        break;
    }
  }
  clrscr();
}
