/* 
 * File:   main.cpp
 * Author: jrsullins
 *
 * Created on October 3, 2011, 11:20 AM
 */

#include <cstdlib>
#include <iostream.h>
#include <math.h>
        
using namespace std;


#define ROWS 9
#define COLS 10
#define INTERVAL 1
#define VEHICLES 10
#define TOWERS 5

#define GUN_RANGE 2
#define GUN_DAMAGE 1

#define LASER_RANGE 3
#define LASER_DAMAGE 1

#define MISSILE_RANGE 3
#define MISSILE_DAMAGE 2
#define MISSILE_RATE 2

#define CRUISER_HEALTH 6
#define BATTLESHIP_HEALTH 8
#define HYDROFOIL_HEALTH 3

class Tower;
class Vehicle;
class Goal;
class Laser;
class Missile;
class Gun;
class Cruiser;
class Battleship;
class Hydrofoil;

// 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
        void setX(int); // for moving
        void setY(int);
	char getSymbol();
protected:
	int x, y;    // Where it is 
	char symbol; // What to print
};

// Goal location (also creates influence)
class Goal: public Thing {
public:
    Goal(int, int);
};

// Base class of all towers in game
class Tower: public Thing {
public:
    Tower();
    Tower(int, int, char);
    void fire(Vehicle[]); // The main tower method called each turn
    float getDistance(Vehicle);
    void toString();
protected:
    int range; // The distance in tiles the tower can reach
    int frequency; // How often the tower can fire
    int damage; // The damage taken by the vehicle it fires at
    int count; // Used for fire frequency
};

class Laser: public Tower {
public:
    Laser(int, int);
};

class Missile: public Tower {
public:
    Missile(int, int);
};

class Gun: public Tower {
public:
    Gun(int, int);
};

// Base class of all vehicles in game
class Vehicle: public Thing {
public:
    Vehicle();
    Vehicle(char);
    int isAlive();      // Is the vehicle still alive?
    int isMoving();     // Is it on the board?
    int getHealth();    // What is the health level?
    void addDamage(int);   // Called by tower to damage the vehicle
    void start(); // Start the vehicle at a random position 
    void move(float[COLS][ROWS], Goal); // Move the vehicle along the board, following the influence map
    void toString(int); // Display status of vehicle
    static float distance(int, int, int, int); // Support function
    int prevx, prevy; // Hack to escape local minima
    
protected:
    int health;
    int speed;
    int onBoard;
};

class Cruiser: public Vehicle {
public:
    Cruiser();
    void toString(int);
    static void computeMap(float[COLS][ROWS], Goal, Tower[TOWERS], Vehicle[VEHICLES]); 
};

class Battleship: public Vehicle {
public:
    Battleship();
    void toString(int);
    static void computeMap(float[COLS][ROWS], Goal, Tower[TOWERS], Vehicle[VEHICLES]); 
};

class Hydrofoil: public Vehicle {
public:
    Hydrofoil();
    void toString(int);
    static void computeMap(float[COLS][ROWS], Goal, Tower[TOWERS], Vehicle[VEHICLES]); 
};

// Very basic Thing methods
Thing::Thing(int xinit, int yinit, char s) {
	x = xinit;
	y = yinit;
	symbol = s;
}
Thing::Thing() {}

Goal::Goal(int xinit, int yinit):Thing(xinit, yinit, 'G') {}

int Thing::getX() {return x;}
int Thing::getY() {return y;}
void Thing::setX(int newx) {x = newx;}
void Thing::setY(int newy) {y = newy;}
char Thing::getSymbol() {return symbol;}

// Tower methods
Tower::Tower() {}
Tower::Tower(int xinit, int yinit, char s):Thing(xinit, yinit, s) {
    count = 0;
}

// Useful auxiliary method
float Tower::getDistance(Vehicle v) {
    int xv = v.getX();
    int yv = v.getY();
    return sqrt((float)((x-xv)*(x-xv)+(y-yv)*(y-yv)));
}

// Tower info for messages
void Tower::toString() {
    if (symbol == 'G') cout << "Gun";
    if (symbol == 'L') cout << "Laser";
    if (symbol == 'M') cout << "Missile";
    cout << " tower at (" << x << ", " << y << ")";
}

// Main thing towers do
void Tower::fire(Vehicle vehicles[VEHICLES]) {
    
    // Am I firing yet?
    count++;
    if (count == frequency) count = 0;    
    if (count != 0) return;
    
    // Find the closest moving target in range
    int bestTarget = -1;
    float bestDist = 1000;
    for (int i = 0; i < VEHICLES; i++) {
        float dist = getDistance(vehicles[i]);
        if (dist <= range && dist < bestDist && vehicles[i].isMoving()) {
            
            // Lasers do no damage to battleships, so check this
            if (symbol == 'L' && vehicles[i].getSymbol() == 'B') continue;
            bestDist = dist;
            bestTarget = i;
        }        
    }
    if (bestTarget != -1) {
        // Damage that target
        vehicles[bestTarget].addDamage(damage);
        
        // Show diagnostics
        toString();
        cout << " firing at " ;
        vehicles[bestTarget].toString(bestTarget);
        cout << "\n";
        
        // If this is a missile tower, also damage things nearby
        if (symbol == 'M') {
                for (int j = 0; j < VEHICLES; j++) {
                if (bestTarget != j && vehicles[j].isAlive() &&
                        sqrt((vehicles[bestTarget].getX()-vehicles[j].getX())*(vehicles[bestTarget].getX()-vehicles[j].getX()) 
                            +(vehicles[bestTarget].getY()-vehicles[j].getY())*(vehicles[bestTarget].getY()-vehicles[j].getY()) < 2)) {
                        vehicles[j].addDamage(1);
                        cout << "Also damage to ";
                        vehicles[j].toString(j);
                        cout << "\n";
                }
            }
        }
    }
}

// Laser tower methods
Laser::Laser(int xinit, int yinit):Tower(xinit, yinit, 'L') {
        range = LASER_RANGE;
        frequency = 1;
        damage = LASER_DAMAGE;
}

// Missile tower methods
Missile::Missile(int xinit, int yinit):Tower(xinit, yinit, 'M') {
        range = MISSILE_RANGE;
        frequency = MISSILE_RATE;
        damage = MISSILE_DAMAGE;
}

// Gun tower methods
Gun::Gun(int xinit, int yinit):Tower(xinit, yinit, 'G') {
        range = GUN_RANGE;
        frequency = 1;
        damage = GUN_DAMAGE;
}

// Vehicle methods
Vehicle::Vehicle() {}
Vehicle::Vehicle(char s):Thing(-100, -100, s) {
    onBoard = 0;
}
int Vehicle::isAlive() {return health > 0;}
int Vehicle::isMoving() {return onBoard;}
int Vehicle::getHealth() {return health;}

void Vehicle::addDamage(int d) {
    health -= d;
    
    // If dead, remove from board
    if (health <= 0) {
        health = 0;
        onBoard = 0;
    }
}

// Vehicle info for messages
void Vehicle::toString(int num) {
    if (symbol == 'C') cout << "Cruiser ";
    if (symbol == 'B') cout << "Battleship ";
    if (symbol == 'H') cout << "Hydrofoil ";
    cout << num << " at (" << x << ", " << y << ") health: " << health;
}

// Start vehicle at random position on left.
void Vehicle::start() {
    x = 0;
    y = random() % ROWS-4 + 2;
    onBoard = 1;
    prevx = -100;
    prevy = -100;
}

// The main method to move a vehicle along the appropriate influence map.
void Vehicle::move(float map[COLS][ROWS], Goal goal) {
    // Check influences at the four surrounding tiles (if no tile, no influence)
    // and move in the direction of greatest influence. Not allowed to move back
    // to the previous location (hack for local minima).
    
    // For vehicles that can move > 1 tile, loop
    
    for (int m = 0; m < speed; m++) {
        int xdir = 0;
        int ydir = 0;
        float maxinfluence = -1000;
    
        // Left move
        if (x > 0 && (x-1 != prevx || y != prevy) && map[x-1][y] >= maxinfluence) {
                maxinfluence = map[x-1][y];
                xdir = -1;
                ydir = 0;
        }
        // Up move
        if (y > 0 && (x != prevx || y-1 != prevy) && map[x][y-1] >= maxinfluence) {
            maxinfluence = map[x][y-1];
                xdir = 0;
                ydir = -1;
        }
        // Down move
        if (y < ROWS-1 && (x != prevx || y+1 != prevy) && map[x][y+1] >= maxinfluence) {
                maxinfluence = map[x][y+1];
                xdir = 0;
                ydir = 1;
        }    
        // Right move
        if (x < COLS-1 && (x+1 != prevx || y != prevy) && map[x+1][y] >= maxinfluence) {
                maxinfluence = map[x+1][y];
                xdir = 1;
                ydir = 0;
        }
    
        // Move the vehicle
        prevx = x;
        prevy = y;
        x += xdir;
        y += ydir;
        
        // Adjust in case leaves board due to speed
        if (y < 0) y = 0;
        if (y >= ROWS) y = ROWS-1;
        if (x >= COLS) x = COLS-1;
    
        // If at goal, the vehicle has escaped!
        if (x == goal.getX() && y == goal.getY()) {
                onBoard = 0;
                x = -100;
                y = -100;
                if (symbol == 'C') cout << "A Cruiser has escaped!!";
                if (symbol == 'B') cout << "A Battleship has escaped!!";
                if (symbol == 'H') cout << "A Hydrofoil has escaped!!";
        }
    }
    
}

float Vehicle::distance(int x1, int y1, int x2, int y2) {
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

// Cruiser methods
Cruiser::Cruiser():Vehicle('C') {
    health = CRUISER_HEALTH;
    speed = 1;
}

// Battleship methods
Battleship::Battleship():Vehicle('B') {
    health = BATTLESHIP_HEALTH;
    speed = 1;
}

// Battleship methods
Hydrofoil::Hydrofoil():Vehicle('H') {
    health = HYDROFOIL_HEALTH;
    speed = 2;
}



// Draw the board
void draw(Goal goal, Tower towers[TOWERS], Vehicle vehicles[VEHICLES], char vehicleType, float map[COLS][ROWS]) {
        int row, col, k;
        int x, y;
        char c;

	cout<<"\n\n";
	cout<<"+";
	for (col = 0; col < COLS; col++) cout << "-----";
	cout<<"+\n";

	for (row = ROWS-1; row >= 0; row--) {
		cout << "|";
		for (col = 0; col < COLS; col++) {
                        c = ' ';
                        
			x = goal.getX();
			y = goal.getY();
			if (row == y && col == x) c =  goal.getSymbol();
                        
			for (k = 0; k < TOWERS; k++) {
				x = towers[k].getX();
				y = towers[k].getY();
				if (row == y && col == x) 
                                    c = towers[k].getSymbol();
			}
                        
			for (k = 0; k < VEHICLES; k++) {
                            if (vehicles[k].isAlive() && vehicles[k].isMoving()) {
				x = vehicles[k].getX();
				y = vehicles[k].getY();
				if (row == y && col == x) 
                                    c = vehicles[k].getSymbol();
                            }
			}
			
			cout << c;
                        if (vehicleType == 'n') cout << "   ";
                        if (vehicleType == 'm') printf("%3d", (int)map[col][row]);
                        
                        cout << " ";
		}
		cout << "|\n";
	}

	cout<<"+";
	for (col = 0; col < COLS; col++) cout << "-----";
	cout<<"+\n";
}


// Randomly generate vehicle types (we do this in advance)
void setVehicles(Vehicle vehicles[VEHICLES]) {
    for (int i = 0; i < VEHICLES; i++) {
        int r = random() % 4;
        if (r < 2) {
            vehicles[i] = Cruiser();
        }
        else if (r < 3) {
            vehicles[i] = Hydrofoil();
        }
        else {
            vehicles[i] = Battleship();
        }
    }
}

// Termination condition for main program. After all vehicles are on the board
// this is used to check whether any are still running (that is, not escaped
// or destroyed).
int stillRunning(Vehicle vehicles[VEHICLES]) {
    for (int i = 0; i < VEHICLES; i++) {
        if (vehicles[i].isMoving()) return 1;
    }
    return 0;
}

// Allows user to choose tower types and positions. This can be hardwired in, 
// or made interactive by uncommenting the code.
void setTowers(Tower towers[TOWERS]) {    
    
    towers[0] = Gun(3, 1);
    towers[1] = Gun(4, 3);
    towers[2] = Missile(6, 3);
    towers[3] = Missile(3, 5);
    towers[4] = Laser(4, 7);
    
    /*
    int x, y;
    char type;
    for (int i = 0; i < TOWERS; i++) {
        cout << "Type of tower " << i << " L)aser M)issile F)lame: ";
        cin >> type;
        cout << "X coordinate of tower " << i << ": ";
        cin >> x;
        cout << "y coordinate of tower " << i << ": ";
        cin >> y;
     if (type == 'L') towers[i] = Laser(x, y);
     if (type == 'M') towers[i] = Missile(x, y);
     if (type == 'F') towers[i] = Flame(x, y);
    }
     */
}


// Main gameplay of the system
int main(int argc, char** argv) {
    
    // Randomize
    srand(time(NULL));
    
    int t = 0; // Keep track of time for new vehicle entry

    // Create the array of towers
    Tower towers[TOWERS];
    setTowers(towers);
    
    // create the array of vehicles
    Vehicle vehicles[VEHICLES];
    setVehicles(vehicles);
    
    // Goal at far right edge
    Goal goal(9, 4);
    
    // Define the influence maps (one per vehicle type)
    float cruiserMap[COLS][ROWS];
    float battleshipMap[COLS][ROWS];
    float hydrofoilMap[COLS][ROWS];
    char choice = 'n';
       
    // Go until no vehicles left (still some left to be created or on board)
    while (t < VEHICLES*INTERVAL || stillRunning(vehicles) == 1) {
        
        // Add a new vehicle each INTERVAL turns
        if (t < VEHICLES*INTERVAL && t % INTERVAL == 0) {
            vehicles[t/INTERVAL].start();
        }
        t++;
        
        // Compute the influence maps
        Cruiser::computeMap(cruiserMap, goal, towers, vehicles);
        Battleship::computeMap(battleshipMap, goal, towers, vehicles);
        Hydrofoil::computeMap(hydrofoilMap, goal, towers, vehicles);
    
        // Move all vehicles
        for (int v = 0; v < VEHICLES; v++) {
            if (vehicles[v].isMoving() == 1) {
                if (vehicles[v].getSymbol() == 'C') vehicles[v].move(cruiserMap, goal);
                if (vehicles[v].getSymbol() == 'B') vehicles[v].move(battleshipMap, goal);
                if (vehicles[v].getSymbol() == 'H') vehicles[v].move(hydrofoilMap, goal);
            }
        }
        
        // Draw the board (user choice defines which, if any influence map is displayed)
        if (choice == 'n') draw(goal, towers, vehicles, 'n', cruiserMap);
        if (choice == 'c') draw(goal, towers, vehicles, 'm', cruiserMap);
        if (choice == 'b') draw(goal, towers, vehicles, 'm', battleshipMap);
        if (choice == 'h') draw(goal, towers, vehicles, 'm', hydrofoilMap);
        
        // All towers fire!
        for (int i = 0; i < TOWERS; i++) {
            towers[i].fire(vehicles);
        }
        
        // Ask what map to display next move
        cout << "Next move, displaying n)no map, c) Cruiser map, b) Battleship map, h) Hydrofoil map, q) quit: ";
        cin >> choice;
        if (choice == 'q') return 0;
        
    }
    
    return 0;
}

/* Very useful utility for computing distances */

float distance(int x1, int y1, int x2, int y2) {
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

/*************************************************************************/
void Cruiser::computeMap(float map[COLS][ROWS], Goal goal, Tower towers[TOWERS], Vehicle vehicles[VEHICLES]) {
    for (int x = 0; x < COLS; x++) {
        for (int y = 0; y < ROWS; y++) {
            map[x][y] = 0;
            
            // Add influences for goal, based on distances to the goal
            float goalDist = distance(x, y, goal.getX(), goal.getY());            
            map[x][y] += ((ROWS+COLS)-goalDist)* 4;
            
            // Add negative influences for towers based on ranges and strengths
            // (not really rationally determined)
            for (int i = 0; i < TOWERS; i++) {
                float tDist = distance(x, y, towers[i].getX(), towers[i].getY());                
                char ttype = towers[i].getSymbol();
                if (ttype == 'G' && tDist <= GUN_RANGE) {
                    map[x][y] -= 5;
                }
                if (ttype == 'L' && tDist <= LASER_RANGE) {
                    map[x][y] -= (LASER_RANGE-tDist)* 1;
                }
                if (ttype == 'M' && tDist <= MISSILE_RANGE) {
                    map[x][y] -= (MISSILE_RANGE-tDist)* 3;
                }
            }
            
            // Add slightly positive influences from other vehicles to keep them closer
            // together (which may not always be the best idea!)
            for (int i = 0; i < VEHICLES; i++) {
                float vDist = distance(x, y, vehicles[i].getX(), vehicles[i].getY());  
                if (vDist <= 2) {                      
                    map[x][y] += (2-vDist);
                }
            }             
            
        }
    }
        
    
}

void Battleship::computeMap(float map[COLS][ROWS], Goal goal, Tower towers[TOWERS], Vehicle vehicles[VEHICLES]) {
    for (int x = 0; x < COLS; x++) {
        for (int y = 0; y < ROWS; y++) {
            map[x][y] = 0;
            
            // Add influences for goal, based on distances to the goal
            float goalDist = distance(x, y, goal.getX(), goal.getY());            
            map[x][y] += ((ROWS+COLS)-goalDist)* 4;
        }
    }
}

void Hydrofoil::computeMap(float map[COLS][ROWS], Goal goal, Tower towers[TOWERS], Vehicle vehicles[VEHICLES]) {
    for (int x = 0; x < COLS; x++) {
        for (int y = 0; y < ROWS; y++) {
            map[x][y] = 0;
            
            // Add influences for goal, based on distances to the goal
            float goalDist = distance(x, y, goal.getX(), goal.getY());            
            map[x][y] += ((ROWS+COLS)-goalDist)* 4;
        }
    }
}


