#include <iostream.h>
#include <math.h>
#define ROWS 20
#define COLS 50
#define DOORTOP 7
#define DOORBOTTOM 13
#define WIRES 12

class Dot;
class Slash;
class Hack;

// A Thing is any entity (moving or not) we want to display
class Thing {
public:
	Thing(int, int, char);
	Thing();
	int getX();   // x coordinate of character
	int getY();   // y coordinate of character
	char getSymbol();
protected:
	int x, y;    // Where it is 
	char symbol; // What to print
};

// An Agent is an entity capable of moving. It has a velocity
// and steering in x and y, and a maximum on both. It may also
// be alive or not.
class Agent: public Thing {
public:
	Agent(int, int, char);
	float getVelX();    // How fast is it going in X
	float getVelY();    // How fast is it going in Y
	int getAlive();     // 1 if alive, 0 if not
	void setAlive(int);
	void update();
	void normalizeSteer();
	void normalizeVel();
	void wallCollide();
	int getPrevX();
	int getPrevY();
	float getSteerX();
	float getSteerY();
	int wallCrash();
protected:
	float velX, velY;
	float steerX, steerY;
	int alive;
	float maxVel;
	float maxAccel;
};

// Dot is the player object. She can be directly steered by
// the player.
class Dot: public Agent {
public:
	Dot(int, int, char);
	void playerSteer(int);
};

// Guards are the base class of Hack and Slash. This is where we
// we will define the steering behaviors
class Guard: public Agent {
public: 
	Guard(int, int, char);
	void seek(int, int, float);
	void flee(int, int, float);
	void arrive(int, int, float, float, float);
	void pursue(int, int, float, float, float);
	void evade(int, int, float, float, float);
	void wallAvoid(int, int, float);
	void wander(int, float);
	void normalizeLocalSteer(float);
	float getDistance(int, int, int, int);
private:
	float localSteerX;
	float localSteerY;
};

class Hack: public Guard {
public:
	Hack(int, int, char);
	void move(Dot, Slash, Thing[]);
};

class Slash: public Guard {
public:
	Slash(int, int, char);
	void move(Dot, Hack, Thing[]);
};

// Global function prototypes
void draw(Thing[], Hack, Slash, Dot);
int checkWires(Agent, Thing[]);
int collide(Agent, Agent);
int myround(float);

// Very basic Thing methods
Thing::Thing(int xinit, int yinit, char s) {
	x = xinit;
	y = yinit;
	symbol = s;
}
Thing::Thing() {}

int Thing::getX() {return x;}
int Thing::getY() {return y;}
char Thing::getSymbol() {return symbol;}

// Agent methods
Agent::Agent(int xinit, int yinit, char s):Thing(xinit, yinit, s) {
	velX = 0;
	velY = 0;
	steerX = 0;
	steerY = 0;
	alive = 1;
}
float Agent::getVelX() {return velX;}
float Agent::getVelY() {return velY;}
float Agent::getSteerX() {return steerX;}
float Agent::getSteerY() {return steerY;}
int Agent::getAlive() {return alive;}
void Agent::setAlive(int a) {alive = a;}

// Used to reduce the acceleration and velocities to the
// set meximums
void Agent::normalizeSteer() {	
	float totalSteer = sqrt(steerX*steerX+steerY*steerY);
	if (totalSteer > maxAccel) {
		float ratio = maxAccel / totalSteer;
		steerX *= ratio;
		steerY *= ratio;
	}
}

void Agent::normalizeVel() {	
	float totalVel = sqrt(velX*velX+velY*velY);
	if (totalVel > maxVel) {
		float ratio = maxVel / totalVel;
		velX *= ratio;
		velY *= ratio;
	}
}

// Has this agent hit a wall?
int Agent::wallCrash() {
	return x > COLS || x < 0 || y > ROWS || y < 0;
}

// Detect wall collision and set back to wall, also stopping motion
void Agent::wallCollide() {
	// Check wall collision
	if (x < 0) {
		x = 0;
		velX = 0;
		velX = 0;
	}
	if (x > COLS) {
		x = COLS;
		velX = 0;
		velX = 0;
	}
	if (y < 0) {
		y = 0;
		velX = 0;
		velY = 0;
	}
	if (y > ROWS) {
		y = ROWS;
		velX = 0;
		velY = 0;
	}
}

// These are a bit of a hack to keep characters from running
// through obstacles at maximum velocity. It estimates where
// they would have run through.
int Agent::getPrevX() {
	if (velX > 1) return x-1;
	if (velX < -1) return x+1;
	return x;
}
int Agent::getPrevY() {
	if (velY > 1) return y-1;
	if (velY < -1) return y+1;
	return y;
}

// This is the key method in a movable agent, which determines the
// current position and velocity based on the steering
void Agent::update() {

	// Move based on current velocities (round off)
	x += (int)myround(velX);
	y += (int)myround(velY);

	normalizeSteer();

	// Update velocities
	velX += steerX;
	velY += steerY;

	normalizeVel();

	wallCollide();

}

// Guard methods
Guard::Guard(int xinit, int yinit, char s):Agent(xinit, yinit, s) {
}

void Guard::normalizeLocalSteer(float limit) {
	float totalSteer = sqrt(localSteerX*localSteerX+localSteerY*localSteerY);
	if (totalSteer > limit) {
		float ratio = limit / totalSteer;
		localSteerX *= ratio;
		localSteerY *= ratio;
	}
}



Dot::Dot(int xinit, int yinit, char s):Agent(xinit, yinit, s) {
	maxVel = 2;
	maxAccel = 1;
}

// The number pad is used to move Dot. The position of the key
// corresponds to the direction. The steering is set to move with
// a total acceleration of about 1.
void Dot::playerSteer(int key) {
	switch(key) {
		case 1:
			steerX = -0.7;
			steerY = -0.7;
			break;
		case 2:
			steerX = 0;
			steerY = -1;
			break;
		case 3:
			steerX = 0.7;
			steerY = -0.7;
			break;
		case 4:
			steerX = -1;
			steerY = 0;
			break;
		case 5:
			steerX = 0;
			steerY = 0;
			break;
		case 6:
			steerX = 1;
			steerY = 0;
			break;
		case 7:
			steerX = -0.7;
			steerY = 0.7;
			break;
		case 8:
			steerX = 0;
			steerY = 1;
			break;
		case 9:
			steerX = 0.7;
			steerY = 0.7;
			break;
	}
	update();
}

Hack::Hack(int xinit, int yinit, char s):Guard(xinit, yinit, s) {
	maxVel = 2;
	maxAccel = 1;
}



Slash::Slash(int xinit, int yinit, char s):Guard(xinit, yinit, s) {
	maxVel = 2;
	maxAccel = 1;
}



int main() {
    // Create the main players
	Hack hack(5, 7, 'H');
	Slash slash(5, 13, 'S');
	Dot dot(45, 10, 'D');

	// Create an array of Things representing the wires
	Thing wires[WIRES];
	wires[0] = Thing(35, 3, '*');
	wires[1] = Thing(32, 5, '*');
	wires[2] = Thing(38, 5, '*');
	wires[3] = Thing(32, 7, '*');
	wires[4] = Thing(38, 7, '*');
	wires[5] = Thing(35, 9, '*');
	wires[6] = Thing(15, 10, '*');
	wires[7] = Thing(12, 12, '*');
	wires[8] = Thing(18, 12, '*');
	wires[9] = Thing(12, 14, '*');
	wires[10] = Thing(18, 14, '*');
	wires[11] = Thing(15, 16, '*');


	
    int escaped = 0;
	int move = 5;

    draw(wires, hack, slash, dot);

	// Loop until Dot destroyed or escapes
	while (dot.getAlive() && !escaped) {

		// prompt the user for Dot's move
		cout << "Dot's move: ";
		cin >> move;

		// Move Dot
		dot.playerSteer(move);

		// Move Hack and Slash if still alive
		if(hack.getAlive()) hack.move(dot, slash, wires);
		if(slash.getAlive()) slash.move(dot, hack, wires);

		// This is a list of terminating conditions

		// Has Hack or Slash caught Dot?
		if(hack.getAlive() && collide(dot, hack)) {
			cout << "Hack has caught Dot!\n";
			dot.setAlive(0);
		}
		if(slash.getAlive() && collide(dot, slash)) {
			cout << "Slash has caught Dot!\n";
			dot.setAlive(0);
		}

		// Have Hack and Slash collided?
		if(hack.getAlive() && slash.getAlive() && collide(hack, slash)) {
			cout << "Hack and Slash have collided!\n";
			hack.setAlive(0);
			slash.setAlive(0);
		}

		// Have Hack, Slash, or Dot hit a wire?
		if(hack.getAlive() && checkWires(hack, wires)) {
			cout << "Hack has hit a wire!\n";
			hack.setAlive(0);
		}
		if(slash.getAlive() && checkWires(slash, wires))  {
			cout << "Slash has hit a wire!\n";
			slash.setAlive(0);
		}
        if(checkWires(dot, wires)) {
			cout << "Dot has hit a wire!\n";
			dot.setAlive(0);
		}

		// Has Dot reached the door?
		if(dot.getX() < 1 && dot.getY() > DOORTOP && dot.getY() < DOORBOTTOM) {
			escaped = 1; 
			cout << "Dot has escaped!\n";
		}
	    if (dot.getAlive() && !escaped) draw(wires, hack, slash, dot);
	}

}

// Draw the board
void draw(Thing things[WIRES], Hack hack, Slash slash, Dot dot) {
	int row, col, k;
    int x, y;
    char c;

	cout<<"\n\n";
	cout<<"+";
	for (col = 0; col < COLS+2; col++) cout << "-";
	cout<<"+\n";

	for (row = ROWS; row >= 0; row--) {
		cout << "|";
		if (row > DOORTOP && row < DOORBOTTOM) cout << "#";
		else cout << " ";
		for (col = 0; col < COLS+1; col++) {
            c = ' ';
			for (k = 0; k < WIRES; k++) {
				x = things[k].getX();
				y = things[k].getY();
				if (row == y && col == x) c = things[k].getSymbol();
			}
			x = hack.getX();
			y = hack.getY();
			if (row == y && col == x && hack.getAlive()) c =  hack.getSymbol();
			x = slash.getX();
			y = slash.getY();
			if (row == y && col == x && slash.getAlive()) c =  slash.getSymbol();
			x = dot.getX();
			y = dot.getY();
			if (row == y && col == x && dot.getAlive()) c =  dot.getSymbol();
			
			cout << c;
		}
		cout << "|\n";
	}

	cout<<"+";
	for (col = 0; col < COLS+2; col++) cout << "-";
	cout<<"+\n";
}

// Has a character collided with one of the wires?
// Hacked to look one step back as well at high speeds
int checkWires(Agent a, Thing wires[WIRES]) {
	int x = a.getX();
	int y = a.getY();
	for (int i = 0; i < WIRES; i++) {
		if (wires[i].getX() == x && wires[i].getY() == y) {
			return 1;
		}
	}
	x = a.getPrevX();
	y = a.getPrevY();
	for (int i = 0; i < WIRES; i++) {
		if (wires[i].getX() == x && wires[i].getY() == y) {
			return 1;
		}
	}
	return 0;
}

// Have two agents collided?
// Hacked to look one step back as well at high speeds
int collide(Agent a1, Agent a2) {
	if (a1.getX() == a2.getX() && a1.getY() == a2.getY()) return 1;
	if (a1.getPrevX() == a2.getPrevX() && a1.getPrevY() == a2.getPrevY()) return 1;
	if (a1.getPrevX() == a2.getPrevX() && a1.getY() == a2.getY()) return 1;
	if (a1.getX() == a2.getX() && a1.getPrevY() == a2.getPrevY()) return 1;
	return 0;
}

// Annoying hack to round numbers
int myround(float x) {
	if (x > 0) return (int)(0.5+x);
	else return (int)(-0.5+x);
}

/****************** Steering methods ******************************/

// Note that all take a weight as a parameter, which indicates the
// "strength" of the effect on the blended steering. This should be
// a number between 0 and 1.

// Seek moves towards (targetX, targetY) at maximum acceleration
void Guard::seek(int targetX, int targetY, float weight) {
	localSteerX = targetX - x;
	localSteerY = targetY - y;
	// Normalize acceleration 
	normalizeLocalSteer(maxAccel);	
	steerX += (localSteerX * weight);
	steerY += (localSteerY * weight);
}

// Flee moves away from (targetX, targetY) at maximum acceleration
void Guard::flee(int targetX, int targetY, float weight) {
	localSteerX = x - targetX;
	localSteerY = y - targetY;
	// Normalize acceleration 
	normalizeLocalSteer(maxAccel);	
	steerX += (localSteerX * weight);
	steerY += (localSteerY * weight);
}

// Arrive move towards the goal of (targetX, targetY). Once it is within a distance of
// slowRadius, the steering decreases proportianally to the remaining distance. Once
// it is within a distance of stopRadius, it stops.
void Guard::arrive(int targetX, int targetY, float slowRadius, float stopRadius, float weight) {
	float distance = getDistance(targetX, x, targetY, y);
	cout << " x: " << x << " y: " << y;
	cout << " tx: " << targetX << " ty: " << targetY;
	cout << " dist: " << distance << "\n";
	if (distance > slowRadius) {
		seek(targetX, targetY, weight);
		return;
	}
	if (distance <= stopRadius) {
		localSteerX = 0;
		localSteerY = 0;
		velX = 0;
		velY = 0;
		return;
	}
	// How fast should we be going?
	float targetVel = maxAccel * distance/slowRadius;
	// Get indicated steering (like seek)
	localSteerX = targetX - x;
	localSteerY = targetY - y;
	normalizeLocalSteer(targetVel);	
	normalizeLocalSteer(maxAccel);
	// Normalize acceleration (use discrete methods)	
	steerX += (localSteerX * weight);
	steerY += (localSteerY * weight);
}

// Pursue estimates an intersection point based on the current location
// of the target (targetX, targetY), its current speed (targetVelX, targetVelY),
// and the guards max speed, and seeks it. Note that this might give strange results
// (i.e., outside the room) if the guard and the target are far apart, so you
// might want to use it only when you are near the target.
void Guard::pursue(int targetX, int targetY, float targetVelX, float targetVelY, float weight) {
	float distance = getDistance(targetX, x, targetY, y);
	float timeToIntercept = distance * maxVel;
	int newTargetX = (int)myround(targetX + timeToIntercept * targetVelX);
	int newTargetY = (int)myround(targetY + timeToIntercept * targetVelY);
      cout << "Dist: " << distance << " Time: " << timeToIntercept << " Intercept: " << newTargetX << ", " << newTargetY << "\n";
	seek(newTargetX, newTargetY, weight);
}

// Evade estimates an intersection point based on the current location
// of the target (targetX, targetY), its max speed (targetVelX, targetVelY),
// and the guards current speed, and flees it. Note that this might give strange results
// (i.e., outside the room) if the guard and the target are far apart, so you
// might want to use it only when you are near the target.
void Guard::evade(int targetX, int targetY, float targetVelX, float targetVelY, float weight) {
	float distance = getDistance(targetX, x, targetY, y);
	float timeToIntercept = distance * maxVel;
	int newTargetX = (int)myround(targetX + timeToIntercept * targetVelX);
	int newTargetY = (int)myround(targetY + timeToIntercept * targetVelY);
	flee(newTargetX, newTargetY, weight);
}

// wallavoid steers the guard away from all four walls. It does so
// if a collision is detected within lookahead squares. It steers
// to a point sefeDistance from the wall.
void Guard:: wallAvoid(int safeDistance, int lookahead, float weight) {
	int inCollision = 0;
	int futureX = x + (int)(myround(velX * lookahead));
	int futureY = y + (int)(myround(velY * lookahead));
	if (futureX < 0) {
		futureX = safeDistance;
		inCollision = 1;
	}
	if (futureX > COLS) {
		futureX = COLS - safeDistance;
		inCollision = 1;
	}
	if (futureY < 0) {
		futureY = safeDistance;
		inCollision = 1;
	}
	if (futureY > ROWS) {
		futureY = COLS - safeDistance;
		inCollision = 1;
	}
	if (inCollision) {
		seek(futureX, futureY, weight);
	}
}

// wander steers the guard in a new direction, which is based on
// the current direction +- a random value between 0 and wanderAngle.
void Guard::wander(int wanderAngle, float weight) {
	float currentAngle = 0.0;
	if (velX != 0) currentAngle = atan(velY/velX)/3.14159 * 180;
	int randomAngle = random() % (2 * wanderAngle) - wanderAngle;
	float newAngleDegs = currentAngle + randomAngle;
	float newAngleRads = (float)newAngleDegs/180 * 3.14159;
	int targetX = x + (int)myround(cos(newAngleRads) * 10);
	int targetY = y + (int)myround(sin(newAngleRads) * 10);
	seek(targetX, targetY, weight);
}

// Useful utility
float Guard::getDistance(int x1, int x2, int y1, int y2) {
	return sqrt((float)((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)));
}

//*********** These are the methods you will need to modify **********************/

void Hack::move(Dot dot, Slash slash, Thing wires[WIRES]) {
	//seek(dot.getX(), dot.getY(), 1.0);
	seek(40, 4, 1.0);

      //wallAvoid(8, 4, 0.5);


	//pursue(dot.getX(), dot.getY(), dot.getVelX(), dot.getVelY(), 1.0);
	//arrive(31, 8, 10.0, 2.0, 1.0);
	/*
	for (int i = 0; i < WIRES; i++) {
		flee(wires[i].getX(), wires[i].getY(), 0.1);
	}
	*/
	//wander(45, 0.5);
	update();
}

void Slash::move(Dot dot, Hack hack, Thing wires[WIRES]) {
      
	pursue(dot.getX(), dot.getY(), dot.getVelX(), dot.getVelY(), 1.0);
      //wallAvoid(8, 4, 0.5);

	//seek(40, 10, 0.5);

      //wallAvoid(4, 4, 0.5);



/*
	if (hack.getAlive()) {
	    seek(dot.getX(), dot.getY(), 0.67);
		//flee(hack.getX(), hack.getY(), 0.33);
		//wallAvoid(5, 7, 1.0);
	    //evade(hack.getX(), hack.getY(), hack.getVelX(), hack.getVelY(), 0.33);
	}
	else {
	    seek(dot.getX(), dot.getY(), 1.0);
	}
*/
	update();

}


