/////////////////////////////////////////////////////////////////////////
// Tron version 1.1, by Jeff Chien
//
// Description:
// A remake of a timeless classic. (If it's not a timeless classic, then
// it should be.) 2-4 players battle it out to try and make the opposing
// players crash their light cycle.
//
// Known issues:
// No two players will ever die at the exact same time. (Meaning no ties)
// Player 1 has less manueverabilty and responsiveness due to arrow keys.
//
// Changes:
// 1.1 No longer needs a large apmatrix thanks to a function in GFX320

#include <fstream.h>
#include <conio.h>
#include <dos.h>
#include "bool.h"
#include "apvector.h"
#include "gfx320.h"

enum Direct {north,east,south,west};
// Enumerated data type to hold directions

struct Coord {
// Holds a pair of coordinates for a 2-dimensional plane
	int x;
	int y;
	Coord();
	Coord(int,int);
	void change(int,int);
};

class Player {
// Each individual player
	public:
		Player();
		void setplayer(GFX320 &,int);
		void pixel(GFX320 &);
		bool chkdth(GFX320 &);
		bool crossable();
		void cross();
		void move(GFX320 &);
		bool chktrn(int);
		void changedir(int);
		bool stat();
	private:
		Coord head;
		Direct dir;
		int status;
		int col;
};

class Environ {
// Holds the game board and players
	public:
		Environ();
		Environ(int);
		void step();
		void turn(char);
		bool chkply(int);
		bool gameover();
		int winner();
	private:
		apvector <Player> p;
		GFX320 g;
};

// Coordinate constructors and modifiers
Coord::Coord():x(0),y(0) {}
Coord::Coord(int a,int b):x(a),y(b) {}
void Coord::change(int a,int b)
{
	x=a;
	y=b;
}

Player::Player()
// Default constructor that does nothing
// main() uses setplayer(), which works like a constructor
{

}

void Player::setplayer(GFX320 &field, int playnum)
// Sets up to 4 player's stats
{
	switch(playnum)
	{
		case 0:
			head.change(MAXX,MIDY);
			dir=west;
			status=0;
			col=3;
			pixel(field);
			break;
		case 1:
			head.change(0,MIDY);
			dir=east;
			status=0;
			col=4;
			pixel(field);
			break;
		case 2:
			head.change(MIDX,0);
			dir=south;
			status=0;
			col=10;
			pixel(field);
			break;
		case 3:
			head.change(MIDX,MAXY);
			dir=north;
			status=0;
			col=5;
			pixel(field);
			break;
		default:
			cout << "Cannot instantiate player. Max players: 4";
			break;
	}
}

void Player::pixel(GFX320 &field)
// Displays the head / tail
{
	field.SetColor(col);
	field.PutPixel(head.x,head.y);
}

bool Player::chkdth(GFX320 &field)
// Checks to see if the player lands in an illegal location
{
	if(field.GetPixel(head.x,head.y))
	{
		status=1;
		return true;
	}
	return false;
}

bool Player::crossable()
// Checks to see if it can cross the screen
{
	if((head.x==0 && dir==west) || (head.x==MAXX && dir==east) || (head.y==0 && dir==north) || (head.y==MAXY && dir==south))
		return true;
	return false;
}

void Player::cross()
// If crossable, do this instead of step
{
	if(head.x==0)
		head.x=MAXX;
	else if(head.x==MAXX)
		head.x=0;

	if(head.y==0)
		head.y=MAXY;
	else if(head.y==MAXY)
		head.y=0;
}

void Player::move(GFX320 &field)
// Moves the player
{

	if(!status)
	{
		if(crossable())
			cross();

		else
			switch(dir)
			{
				case north:
					head.change(head.x,head.y-1);
					break;
				case east:
					head.change(head.x+1,head.y);
					break;
				case south:
					head.change(head.x,head.y+1);
					break;
				case west:
					head.change(head.x-1,head.y);
					break;
				default:
					cout << "Invalid direction. Max dir: 3";
					break;
			}

		status = chkdth(field);
		pixel(field);
	}
}

bool Player::chktrn(int x)
// Returns false if user tries to do a 180 with one keystroke
{
	switch(x)
	{
		case 0:
			if(dir==2)
				return false;
			break;
		case 1:
			if(dir==3)
				return false;
			break;
		case 2:
			if(dir==0)
				return false;
			break;
		case 3:
			if(dir==1)
				return false;
			break;
	}
	return true;
}

void Player::changedir(int x)
// Changes direction
{
	if(chktrn(x))
		dir=x;
}

bool Player::stat()
// Returns true if the player has lost
{
	if(status==1)
		return true;
	return false;
}

Environ::Environ()
// Initializes players, defaulted to 2
{
	p.resize(2);
	for(int a=0;a<2;a++)
		p[a].setplayer(g,a);
}

Environ::Environ(int x)
// Sets desired number of players up to 4
{
	p.resize(x);
	for(int a=0;a<x;a++)
		p[a].setplayer(g,a);
}


void Environ::step()
// Moves all players
{
	for(int i=0;i<p.length();i++)
		p[i].move(g);
}

void Environ::turn(char ch)
// Allows turning
{
	switch(ch)
	{
		case 'H':
			if(chkply(0))
				p[0].changedir(0);
			break;
		case 'M':
			if(chkply(0))
				p[0].changedir(1);
			break;
		case 'P':
			if(chkply(0))
				p[0].changedir(2);
			break;
		case 'K':
			if(chkply(0))
				p[0].changedir(3);
			break;

		case 'w':
			if(chkply(1))
				p[1].changedir(0);
			break;
		case 'd':
			if(chkply(1))
				p[1].changedir(1);
			break;
		case 's':
			if(chkply(1))
				p[1].changedir(2);
			break;
		case 'a':
			if(chkply(1))
				p[1].changedir(3);
			break;

		case '8':
			if(chkply(2))
				p[2].changedir(0);
			break;
		case '6':
			if(chkply(2))
				p[2].changedir(1);
			break;
		case '5':
			if(chkply(2))
				p[2].changedir(2);
			break;
		case '4':
			if(chkply(2))
				p[2].changedir(3);
			break;

		case 'i':
			if(chkply(3))
				p[3].changedir(0);
			break;
		case 'l':
			if(chkply(3))
				p[3].changedir(1);
			break;
		case 'k':
			if(chkply(3))
				p[3].changedir(2);
			break;
		case 'j':
			if(chkply(3))
				p[3].changedir(3);
			break;
	}
}

bool Environ::chkply(int x)
// Check to see if a player exists, helps prevent out of index errors
{
	if(x<p.length())
		return true;
	return false;
}

bool Environ::gameover()
// Checks to see if the game is over due to everyone but one person dying
{
	int chk=0;
	for(int i=0;i<p.length();i++)
		chk+=p[i].stat();
	if(chk==p.length()-1)
		return true;
	return false;
}

int Environ::winner()
// Returns the winning player's ID number
{
	for(int i=0;i<p.length();i++)
		if(!p[i].stat())
			return i+1;
	return 0;
}

void instructions()
{
	cout << "Welcome to Tron v1.1, by Jeff Chien." << endl;
	cout << "The object of the game is to make your opponent crash by running" << endl;
	cout << "into your tail. Your tail endlessly grows from your starting" << endl;
	cout << "location and unlike in snake, it never stops growing. Players can" << endl;
	cout << "\"jump\" to the other side of the screen by moving off the field." << endl;
	cout << "Try to win by cutting off your opponents. Have fun!" << endl << endl;
	cout << "            Player 1      Player 2      Player 3      Player 4" << endl;
	cout << "Controls:" << endl;
	cout << "            Arrow Keys    Letters       Number Pad    Letters" << endl;
	cout << "               ^             W             8             I" << endl;
	cout << "             < v >         A S D         4 5 6         J K L" << endl << endl;
	cout << "Starting" << endl;
	cout << "Locations:" << endl;
	cout << "            Right         Left          Top           Bottom" << endl << endl;
	cout << "Colors:" << endl;
	cout << "            Cyan          Red           Green         Purple" << endl << endl;
	cout << "NOTE: If running windowed, hit alt-enter to enter full screen mode (Recommended)" << endl << endl;
}

void main()
// Instructions and game loop
{
	int n;
	do{
		clrscr();
		instructions();
		cout << "How many players (2-4)? ";
		cin >> n;
	} while (n < 2 || n > 4);
	cout << endl << endl;
	cout << "Starting in:  ";
	for(int i=5;i>0;i--)
	{
		cout << "\b" << i;
		delay(1000);
	}
	Environ env(n);
	delay(1000);
	do
	{
		if(kbhit())
		{
			char temp;
			temp = getch();
			if(temp==0)
				temp = getch();
			env.turn(temp);
		}
		delay(20);
		env.step();
	} while(!env.gameover());
	cout << "Player " << env.winner() << " wins!" << endl;
	delay(1000);
	getch();
}
</pre>