#include <exec/types.h>

#define CUE 0
#define BLK 5

#define BASE 200 /* V.important constant. Used in ALL physical calcs. */
#define BASESP 1000
#define BASEII 4 /* For root calcs which need plenty of precision */

#define TABLELENGTH (272 * BASE) /* 272 is actual length in pixels */
#define TABLEWIDTH ((TABLELENGTH * 5 / 8)-BASE)

#define X1CORNER 24
#define Y1CORNER 62
#define X2CORNER (X1CORNER + TABLELENGTH / BASE)
#define Y2CORNER (Y1CORNER + TABLEWIDTH / BASE)

#define BALLRADIUS (7 * BASE) /* 7 pixels */
#define BALLDIAMETER (BALLRADIUS * 2)
#define SCRNBALLDIAMETER (BALLDIAMETER / BASE)
#define SEMICIRCRAD (TABLEWIDTH/6)
#define PLACEPACK (TABLELENGTH * 5 / 8)
#define PACKHORIZSEP (BALLDIAMETER - 2*BASE)
#define POCKET (BALLRADIUS + 5*BASE)
#define NEARPOCKET (9*BASE)
#define SCRNPOCKET (POCKET / BASE)
#define CUETIP 10
#define MAXCUELENGTH 80

#define CLOCKWISE 1
#define ANTICLOCKWISE 2
#define SMALL 1
#define LARGE 2
#define UP 1
#define DOWN 2
#define NOHIT -1
#define ON TRUE
#define OFF FALSE
#define HIT 3
#define UPDATE 3
#define ICON 2

#define FRICTION (BASESP / 500)
#define SPINFRICTION 3
#define SPEEDINC (BASESP / 16)
#define MAXSPEED (BASESP * 4)

#define DEFAULT 30000

#define RACKED 1
#define BROKEN 0
#define OPEN 2

#define PRACTICE 2

#define CLEARJAWS 0
#define BALLINJAWS 1
#define AVOIDJAWS 2

void ChangePower();
void ChangeSpin(void);
void DrawCross(short);
void MoveCue(short,short);
short FindAngle(WORD,WORD,WORD);
short TouchingBall(short);
void PlayComputerShot(void);
void CopyBalls(struct ballstruct *,struct ballstruct *);
WORD QuickRoot(long);
void CheckAngle(short *);
short NearPocket(short);
short CheckPocket(short);
UWORD Rand(UWORD);

enum msg { MSG_PLACECUEBALL,MSG_FOUL,MSG_WINS,MSG_MICHAEL,MSG_JOSH,
	MSG_CLAIRE,MSG_SALLY,MSG_PLAYER1,MSG_PLAYER2,MSG_QUITTING,
	MSG_VS,MSG_GAME,MSG_OF,MSG_0,MSG_1,MSG_2,MSG_3,MSG_4,MSG_5,
	MSG_GOODSHOT,MSG_SHOTCANCELLED,MSG_BADLUCK,MSG_WELLDONE,
	MSG_QUITGAME,MSG_ISTHECHAMP,MSG_OOOPS };

enum balltype { white,black,yellow,red, anyball,baize };
struct ballstruct
{
	enum balltype colour;
	char potted,justpotted,hasmoved,lasthit,olddx,olddy;
	USHORT x,y;
	short speed,angle,scrnx,scrny,oldscrnx,oldscrny;
};
struct ballstruct ball[16],tempball[16];

struct playerstruct
{
	enum balltype colour;
	char goes,wins;
	char nametag;
	short maxpower;
	char cuepulls,skill;
};
struct playerstruct player[8];

short *memblock, *quicksin, *quickcos, *quickarcsin;

WORD cueangle,cuespeed,powerdirn,spinx,spiny;

short gametype,cloth,firsthit,packstate,turn,foul,balls,
	endofgame,cheatangle,cheatball;

USHORT pocketx[6] = { (TABLELENGTH-BALLRADIUS),(TABLELENGTH-BALLRADIUS),
	(TABLELENGTH/2),(TABLELENGTH/2),BALLRADIUS,BALLRADIUS };

USHORT pockety[6] = { (TABLEWIDTH-BALLRADIUS),BALLRADIUS,
	(TABLEWIDTH-BALLRADIUS),BALLRADIUS,(TABLEWIDTH-BALLRADIUS),BALLRADIUS };
	
char angleused[360];	

/**********************/
short TouchingBall(col)
short col;
{
	register short i;
	
	for (i=0; i<balls; i++)
	{
		if (!ball[i].potted && i!=col)
		{
			register long dx = ball[col].x - ball[i].x;
			
			if (dx < BALLDIAMETER && dx > (-BALLDIAMETER))
			{
				register long dy = ball[col].y - ball[i].y;
				if (dy < BALLDIAMETER && dy > (-BALLDIAMETER))
					if (QuickRoot(dx*dx + dy*dy) < BALLDIAMETER)
						return(i);
			}
		}
	}
	return(NOHIT);
}
/************************/
short NearPocket(i)
short i;
{
	char j;
	
	for (j=0; j<6; j++)
		if (abs(ball[i].x - pocketx[j]) < NEARPOCKET &&
			abs(ball[i].y - pockety[j]) < NEARPOCKET) return(TRUE);
	return(FALSE);
}
/************************/
short CheckPocket(i)
short i;
{
	short j;
	
	for (j=1; j<balls; j++)
	{
		if (!ball[j].potted &&
			abs(ball[j].x - pocketx[i]) < NEARPOCKET &&
			abs(ball[j].y - pockety[i]) < NEARPOCKET) return(j);
	}
	return(FALSE);
}
/************************/
void PlayComputerShot()
{
	short i,j,k,l,limit,opp,adj,dx,dy,angle,hitball,
		hypcue,anglecol,hypcol,anglecue,relangle,
		potspeed=DEFAULT,hitspeed=DEFAULT,speed,
		potangle,hitangle,potbackspin,nearpocket,
		jaws,forgetcolour=FALSE;
	
	long x,y;
	
	CopyBalls(ball,tempball);
	
	/* CPU can forget its ball colour! */
	if (player[turn].skill==0 && player[turn].colour!=black && !(Rand(10)))
		forgetcolour=TRUE;
	
	for(i=1; i<balls; i++)
	if ((ball[i].colour==player[turn].colour ||
		(forgetcolour && ball[i].colour!=black) ||
		(ball[i].colour!=black && player[turn].colour==anyball) ||
		gametype==PRACTICE) && !ball[i].potted)
	{
		for (j=0; j < (6+1); j++)
		{
			if (j==6)
			{
				opp = (ball[i].x - ball[CUE].x) / BASEII;
				adj = (ball[i].y - ball[CUE].y) / BASEII;
				hypcue = QuickRoot(opp*opp + adj*adj);
				anglecue = FindAngle(opp,adj,hypcue);
				anglecol = anglecue; hypcol = hypcue;
				relangle = 0;
			}
			else
			{
				opp = (pocketx[j] - ball[i].x) / BASEII;
				adj = (pockety[j] - ball[i].y) / BASEII;
				hypcol = QuickRoot(opp*opp + adj*adj);
				anglecol = FindAngle(opp,adj,hypcol);

				x = ball[i].x - SCRNBALLDIAMETER * quicksin[anglecol];
				y = ball[i].y - SCRNBALLDIAMETER * quickcos[anglecol];
				opp = (x - ball[CUE].x) / BASEII;
				adj = (y - ball[CUE].y) / BASEII;
				hypcue = QuickRoot(opp*opp + adj*adj);
				anglecue = FindAngle(opp,adj,hypcue);
				relangle = abs(anglecol-anglecue);
				if (relangle > 180) relangle=360-relangle;	
			}
			if (relangle < 70)
			{
				dx = quicksin[anglecue]<<1;
				dy = quickcos[anglecue]<<1;
				limit = hypcue / ((BASE<<1)/BASEII) + 10;
				
				for (k=0; k<limit; k++)
				{
					hitball=TouchingBall(CUE);
					if (hitball!=NOHIT) break;
					ball[CUE].x+=dx; ball[CUE].y+=dy;
				}
				nearpocket = NearPocket(CUE);

				ball[CUE].x = tempball[CUE].x;
				ball[CUE].y = tempball[CUE].y;
	
				if (hitball!=NOHIT && hitball!=i && !nearpocket &&
					(ball[hitball].colour==player[turn].colour ||
					(player[turn].colour==anyball &&
					ball[hitball].colour!=black)))
				{
					speed = hypcue * (BASEII*MAXSPEED/BASE) / 600;
				
					if (speed < hitspeed)
					{ hitspeed = speed; hitangle = anglecue; }
				}
				else if (hitball==i && !nearpocket)
				{
					/* Cue ball path is clear */
					
					dx = quicksin[anglecol] << 1;
					dy = quickcos[anglecol] << 1;

					do
					{
						ball[i].x += dx; ball[i].y += dy;
						hitball = TouchingBall(i);
						if (ball[i].x < BALLRADIUS ||
							ball[i].x > (TABLELENGTH-BALLRADIUS) ||
							ball[i].y < BALLRADIUS ||
							ball[i].y > (TABLEWIDTH-BALLRADIUS)) break;
					
					} while (hitball==NOHIT);

					ball[i].x = tempball[i].x;
					ball[i].y = tempball[i].y;
					 
					if (hitball==NOHIT && j!=6) /* We've found a pot! */
					{
						speed = (hypcol+hypcue) * (BASEII*MAXSPEED/BASE)
							/ ((100-relangle)*4)
							* (hypcol+(TABLELENGTH/BASEII))
							/ (hypcue+(TABLELENGTH/BASEII));
					
						if (speed < potspeed)
						{
							potspeed = speed; potangle = anglecue;
							/* check for in/off situ */
							if (relangle < 5 || NearPocket(i))
								potbackspin = TRUE;
							else potbackspin = FALSE;
							if (player[turn].nametag==MSG_JOSH ||
								player[turn].nametag==MSG_PLAYER1 ||
								player[turn].nametag==MSG_PLAYER2)
							{ cheatangle = anglecol; cheatball = i; }
						}
					}
					else if (potspeed==DEFAULT)
					{
						/* Can hit but not pot */
					
						if (hitball!=NOHIT && NearPocket(hitball))
						{
							if (ball[hitball].colour==
								player[turn].colour ||
								(player[turn].colour==anyball &&
								ball[hitball].colour!=black))
								jaws = BALLINJAWS;
							else jaws = AVOIDJAWS;
						}
						else jaws = CLEARJAWS;
				
						speed = hypcue * (BASEII*MAXSPEED/BASE)
							/ ((100-relangle)*3);
						if (jaws==BALLINJAWS) speed<<=2;
						
						if (speed < hitspeed && jaws!=AVOIDJAWS)
						{
							hitspeed = speed;
							hitangle = anglecue;
						}
					}
				}
			}
		}
	}
	
	if (hitspeed==DEFAULT && potspeed==DEFAULT) /* Snooker situation */
	{
		for(i=1; i<balls; i++)
		if ((ball[i].colour==player[turn].colour ||
			(ball[i].colour!=black && player[turn].colour==anyball) ||
			gametype==PRACTICE) && !ball[i].potted)
		{
			for (j=0; j<4; j++) for (l=0; l<5; l++)
			{
				short xdisp,ydisp;
				long dx,dy;
								
				switch(l)
				{
					case 0: xdisp = 0; ydisp = 0; break;
					
					case 1: xdisp = (-BALLRADIUS);
						ydisp = (-BALLRADIUS); break;
						
					case 2: xdisp = BALLRADIUS;
						ydisp = (-BALLRADIUS); break;
						
					case 3: xdisp = (-BALLRADIUS);
						ydisp = BALLRADIUS; break;
						
					case 4: xdisp = BALLRADIUS;
						ydisp = BALLRADIUS; break;
				}
				
				dx = ball[i].x+xdisp - ball[CUE].x;
				dy = ball[i].y+ydisp - ball[CUE].y;
				
				switch(j)
				{
					case 0:	y = ball[CUE].y +
							dy * ball[CUE].x /
							(ball[i].x+xdisp + ball[CUE].x);
							x = BALLRADIUS; break;

					case 1:	y = ball[CUE].y +
							dy * (TABLELENGTH - ball[CUE].x) /
							((TABLELENGTH - (ball[i].x+xdisp)) +
							(TABLELENGTH - ball[CUE].x));
							x = TABLELENGTH - BALLRADIUS; break;

					case 2:	x = ball[CUE].x +
							dx * ball[CUE].y /
							(ball[i].y+ydisp + ball[CUE].y);
							y = BALLRADIUS; break;

					case 3:	x = ball[CUE].x +
							dx * (TABLEWIDTH - ball[CUE].y) /
							((TABLEWIDTH - (ball[i].y+ydisp)) +
							(TABLEWIDTH - ball[CUE].y));
							y = TABLEWIDTH - BALLRADIUS; break;
				}
				opp = (x - ball[CUE].x) / BASEII;
				adj = (y - ball[CUE].y) / BASEII;
				hypcue = QuickRoot(opp*opp + adj*adj);
				anglecue = FindAngle(opp,adj,hypcue);

				opp = (x - (ball[i].x+xdisp)) / BASEII;
				adj = (y - (ball[i].y+ydisp)) / BASEII;
				hypcol = QuickRoot(opp*opp + adj*adj);

				dx = quicksin[anglecue] << 1;
				dy = quickcos[anglecue] << 1;
				
				do
				{
					ball[CUE].x += dx; ball[CUE].y += dy;
					if (ball[CUE].x < BALLRADIUS ||
						ball[CUE].x > (TABLELENGTH-BALLRADIUS) ||
						ball[CUE].y < BALLRADIUS ||
						ball[CUE].y > (TABLEWIDTH-BALLRADIUS)) break;
					
					hitball = TouchingBall(CUE);
					
				} while (hitball==NOHIT);
				
				if (hitball==NOHIT && !(NearPocket(CUE)))
				{
					/* Path to cushion is clear */
					if (j <= 1) dx = -dx; else dy = -dy;
	
					limit = (hypcol / ((BASE<<1)/BASEII)) + 10;
				
					for (k=0; k<limit; k++)
					{
						hitball=TouchingBall(CUE);
						if (hitball!=NOHIT) break;
						ball[CUE].x += dx; ball[CUE].y += dy;
					}
			
					if (hitball==i)
					{
						/* Found a path off a cushion! */
						speed = (hypcue+hypcol) *
							(BASEII*MAXSPEED/BASE) / 300;
						if (speed < hitspeed)
						{
							hitspeed = speed;
							hitangle = anglecue;
							ball[CUE].x = tempball[CUE].x;
							ball[CUE].y = tempball[CUE].y;
							break;
						}
					}
				}
				ball[CUE].x = tempball[CUE].x;
				ball[CUE].y = tempball[CUE].y;
			}
		}
	}

	spiny = (Rand(1200)+Rand(1200))/2 - 800;	

	if (potspeed != DEFAULT)
	{
		ball[CUE].speed = potspeed + Rand((UWORD)(potspeed>>1)); 
		ball[CUE].angle = potangle;
		if (potbackspin) /* could be an "in/off" situ */
			spiny = (Rand(800)+Rand(800))/2 - 800;
	}
	else if (hitspeed != DEFAULT)
	{
		ball[CUE].speed = hitspeed + Rand(MAXSPEED);
		ball[CUE].angle = hitangle;
	}
	else /* Ooops! We're fully snookered! */
	{
		short lasthope=FALSE;
		
		/* Try to illegally knock in any of our balls over pockets */
		for (i=0; i<6; i++)
		{
			if ((j = CheckPocket(i)) &&
				ball[j].colour==player[turn].colour &&
				(player[turn].colour!=black ||
				(player[turn].colour==anyball &&
				ball[j].colour!=black)))
			{
				opp = (pocketx[i] - ball[CUE].x) / BASEII;
				adj = (pockety[i] - ball[CUE].y) / BASEII;
				hypcue = QuickRoot(opp*opp + adj*adj);
				ball[CUE].angle = FindAngle(opp,adj,hypcue);
				ball[CUE].speed = player[turn].maxpower;
				spiny = (Rand(800)+Rand(800))/2 - 800;
				lasthope = TRUE; break;
			}
		}
		if (!lasthope)
		{
			ball[CUE].angle = Rand(360);
			ball[CUE].speed = player[turn].maxpower;
			/* little anim! */
			j = 36 + Rand(49);
			for (i=0; i<j; i++) MoveCue(CLOCKWISE,LARGE);
		}
	}
	if (packstate==RACKED) ball[CUE].speed = player[turn].maxpower;
	
	/* Add player characteristics */
	angle = ((Rand(8)-4) * (3-player[turn].skill))/3;
	if (player[turn].colour==black) angle*=2;
	ball[CUE].angle+=angle;
	CheckAngle(&ball[CUE].angle);

	ball[CUE].speed = ball[CUE].speed * player[turn].maxpower / MAXSPEED;
	if (ball[CUE].speed > player[turn].maxpower)
		ball[CUE].speed = player[turn].maxpower;
	if (ball[CUE].speed < BASESP) ball[CUE].speed=BASESP;
	if (ball[CUE].speed > MAXSPEED) ball[CUE].speed=MAXSPEED;
	
	angle = ball[CUE].angle+180;
	CheckAngle(&angle);
	
	i=LARGE; j=0;
	while(cueangle!=angle)
	{
		if ((angle-cueangle<180 && angle-cueangle>=0) ||
			angle-cueangle<-180)
		{
			if (j==2) i=SMALL;
			j=1; MoveCue(CLOCKWISE,i);
		}
		else
		{
			if (j==1) i=SMALL;
			j=2; MoveCue(ANTICLOCKWISE,i);
		}
	}

	while(cuespeed < ball[CUE].speed) { ChangePower(); WaitTOF(); }
	PlayShot();
}
