#include <exec/types.h>
#include <exec/memory.h>
#include <graphics/view.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <dos.h>
#include <proto/dos.h>
#include <math.h>
#include <exec/devices.h>
#include <devices/audio.h>
#include <hardware/custom.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 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 VIEW 1
#define NOREFRESH 2

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

#undef SINGLE

enum icons { PLYR1,PLYR2,PRACTICE,DEMO,PLAY,QUIT,_BAIZE,_BALL1,_BALL2,
SINGLE,BEST3,BEST5,UNLIMITED,STRIP,BALL10,BALL16,
OPPONENTBASE,ICON17,ICON18,ICON19,STRIPPERBASE };

enum args { GAMETYPE,GAMELENGTH,BAIZE,BALL1,BALL2,BALLCOUNT,OPPONENT,STRIPPER };

#define HUMANPLYR 0
#define COMPLYR   1

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

#define ESC 69
#define RETURN 68
#define SPACE 64
#define LCSR 79
#define RCSR 78
#define UCSR 76
#define DCSR 77
#define F1 80
#define F2 81
#define F3 82
#define F4 83
#define F6 85
#define F10 89

#define FLASH 2

#define JOYFIRE 0xbfe001
#define LMOUSE 1
#define RMOUSE 2

#define MEMSIZE 7000

void DoControl(void);
void DrawCue(short);
void ChangePower();
short ChangeSpin(void);
void DrawCross(short);
void MoveCue(short,short);
void DoAssigns(void);
void SetUpTable(void);
void DoOptions(void);
short GetInput(void);
char UpdateBallPos(void);
void WaitBeam(short);
short CheckPotted(short);
void CheckBounce(short);
void CheckCollision(void);
void MakeCollision(short,short);
void DrawBall(short,short);
void DrawBallDirect(short,short,short);
short FindAngle(WORD,WORD,WORD);
void SuperImpose(struct RastPort *,short,short,struct RastPort *,
	short,short,short,short,short,short);
void WriteMessage(short,short,short,short);
void ScrubMessage(void);
void PlayShot(void);
void CheckResult(void);
void CheckWhitePotted(void);
short TouchingBall(short);
void PlayComputerShot(void);
void CopyBalls(struct ballstruct*,struct ballstruct*);
void CopyPlayer(short,short);
WORD QuickRoot(long);
short GetMouse(void);
char LoadIFF(struct IFFGraphic *,struct Screen *,char);
char ClickDelay(short);
void CueJumpAnim(void);
void CheckAngle(short *);
UWORD Rand(UWORD);
short AudioOpenIt(void);
long MyRead(struct FileHandle *,long *,long);
char OpenIt(void);
void CloseIt(void);

APTR GetDeviceBlock(ULONG);
void FreeDeviceBlock(struct IOAudio *,BYTE);
void Audio_PerVol(struct IOAudio *,UWORD,UWORD);
void Audio_Write(struct IOAudio *,UBYTE *,UWORD);
void PlaySound(struct audioinfostruct *,short);

/******************* Graphics defines ******************/
struct NewScreen ns;
struct NewWindow nw;

/* Extra padding for use with tempmask. Must set ballmask to point to */
/* first 'real' byte (8) at start of program */

USHORT chip crossmask[5];
USHORT chip mask[37];
USHORT chip mask2[10];
USHORT chip hybridmask[13];
USHORT chip hybridmask2[10];
USHORT chip miniball[8];

USHORT *ballmask = &mask[12];
USHORT *ballmask2 = mask2;
USHORT chip tempmask[256];

USHORT chip buffer[20];

/********************* System defines ************************/
struct Screen 		*s,*s2;
struct Window 		*w,*w2;
struct RastPort	*rp,*rp2,bufrp;
struct BitMap		*bm,*bm2,bufbm;
struct ViewPort 	*vp;
struct ColorMap 	*cm;
struct IntuiMessage *msg;
struct FileHandle	*fh;
struct Preferences  prefs;

UWORD *colorpalette;

char *bp0,*bp1,*bp2,*bp3,*bp4;

long oldkeyspeed,oldkeydelay;

/******************* Audio Defines ******************/

#define SAMPLES 8
#define AM2 106 /* Audio Master II info size */
#define BUFWAVELEN 100

enum sound { COLLIDE,STRIKE,BOUNCE,POTTED,CHEER,LONGCHEER,CHANT,CRASH };

#define AUDIO_LEN (sizeof(struct IOAudio))
#define PRECIDENCE 127
#define CLOSE_DEVICE 1

struct IOAudio *audio[4];
char Channel_Map[] = { 1,2,4,8 },*bufwave;

struct audioinfostruct
{
	struct IOAudio *audio;
	char *wave,bufstate,channel,flag;
	long wavelen;
	short volume,period;
	char filename[30];
};

#define BUFFER 0
#define RESET  1

struct audioinfostruct audioinfo[SAMPLES];

/********************* Gameplay defines **********************/
UWORD rgb[32];
UWORD rgb2[32];

enum col {
 _back,_leather,_dkleather,_ltbaize,_dkbaize,_dkwalnut,_walnut,_ltwalnut,
 _baize,_yellow,_red,_ltbaize2,_white,_highyel,_highred,_ltbaize3,
 _cross,_ptr17,_ptr18,_ptr19,_cuecol,_ltcuecol,_22,_23,
 _black,_text1,_text2 };

enum balltype { white,black,yellow,red, anyball,baize };
char ballpalette[] = { _white,_black,_yellow,_red, 0,_baize };

char ball9colour[],ball15colour[];

char ballplanes[] = { 4,16,1,2, 0,23 };

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];
 
struct pixelsavestruct
{
	char col;
	short x,y;
};
struct pixelsavestruct pixelsave[32];

struct blitdatstruct
{
	short x,y,dx,dy;
};
struct blitdatstruct blitdat,blitdat2;

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

WORD cueangle=270,cuespeed=MINSPEED,powerdirn=UP,
	spinx,spiny,swerve,swervetotal;

USHORT seed; /* random element */

short gametype,gamelength,stripper,opponent,
	cueline,cloth,firsthit,turn,dotspacing=8,newbreak,cheer,
	comp1,comp2,foul,endofgame,quitgame,winner,matches,coin,balls,
	packstate,plyrbreak;

USHORT pocketx[6];
USHORT pockety[6];
	
char angleused[360];	

char joydelay;

extern struct Custom far custom;

/********* Loader stuff *************/

extern struct IFFGraphic /* defined in load.c */
{
char		FileName[30];	/* Name of IFF file */
BYTE		ReqDepth;		/* How many bitplanes wish to be Displayed */  
USHORT	*RGB;		/* RGB colour values, filled by LoadIFF */ 
short	Width;		/* Width of graphic, filled by LoadIFF */ 
short	Height;		/* Height of graphic, filled by LoadIFF */ 
BYTE		Depth;		/* Depth of graphic, filled by LoadIFF */ 
};
extern char arg[8];
struct IFFGraphic tableIFF =  { "fnpool:table.img",5,NULL };

UWORD RGBbaize[20];
UWORD RGBball[7];
UWORD RGBroom[16];

/********** Strip stuff *****************/

struct IFFGraphic roomIFF;
struct IFFGraphic stripIFF[4];

struct stripstruct
{ short imgx1[4],imgy1[4],imgdx[4],imgdy[4],imgx2[4],imgy2[4],
	textfiletag[5],textx[5],texty[5],textdx[5],textdy[5]; };

/******* Message stuff *****************/

#define NEW 0
#define FOLLOW 1

short cursor;

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 };

struct messagestruct { short x1,y1,x2,y2; };
struct messagestruct message[];

/************************/
char OpenIt()
{
	short i;
	
	if (!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)))	return(0);
	if (!(IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library",0))) return(0);

	if (!(s2 = OpenScreen(&ns))) return(0);
	if (!(s = OpenScreen(&ns))) return(0);
	nw.Screen = s;
	if (!(w = OpenWindow(&nw))) return(0);
	nw.Screen = s2;
	if (!(w2 = OpenWindow(&nw))) return(0);
	if (!AudioOpenIt()) return(0);
	if (!(memblock = AllocMem(MEMSIZE,MEMF_PUBLIC))) return(0);
	
	cm = GetColorMap(32);
	s->ViewPort.ColorMap = cm;
	s2->ViewPort.ColorMap = GetColorMap(32);
	rp = &s->RastPort; bm = &s->BitMap;
	rp2 = &s2->RastPort; bm2 = &s2->BitMap;
	colorpalette = (UWORD *)cm->ColorTable;
	bp0 = s->BitMap.Planes[0]; bp1 = s->BitMap.Planes[1];
	bp2 = s->BitMap.Planes[2]; bp3 = s->BitMap.Planes[3];
	bp4 = s->BitMap.Planes[4];
	for (i=0; i<32; i++) colorpalette[i] = 0;	
	MakeScreen(s); RethinkDisplay();

	InitBitMap(&bufbm,5,320,256);
	for (i=0; i<5; i++) 
		if (!(bufbm.Planes[i] = AllocRaster(320,256))) return(0);
	
	InitRastPort(&bufrp); bufrp.BitMap = &bufbm;
	bufrp.Mask = 31;
	
	while (!(fh = (struct FileHandle *) Open("FNPGRAPHICS:message.img",MODE_OLDFILE)));

	Seek((BPTR)fh,8,OFFSET_BEGINNING);
	MyRead(fh,bufbm.Planes[0]+(175*40),(40*78));
	MyRead(fh,bufbm.Planes[1]+(175*40),(40*78));
	Close((BPTR)fh);
	
	GetPrefs(&prefs,sizeof(struct Preferences));
	prefs.KeyRptSpeed.tv_secs = 0;
	oldkeyspeed = prefs.KeyRptSpeed.tv_micro;
	prefs.KeyRptSpeed.tv_micro = 10000;
	prefs.KeyRptDelay.tv_secs = 0;
	prefs.KeyRptDelay.tv_micro = 300000;
	oldkeydelay = prefs.KeyRptDelay.tv_micro;
	prefs.PointerTicks = 1;
	SetPrefs(&prefs,sizeof(struct Preferences),TRUE);
	return(1);
}
/**********************/
void WriteMessage(i,x,y,flag)
short i,x,y,flag;
{
	short dx = 1 + message[i].x2-message[i].x1;
	short dy = 1 + message[i].y2-message[i].y1;

	if (flag==NEW) { ScrubMessage(); cursor=5; }
	
	SetWrMsk(rp,31); SetAPen(rp,_black);
	RectFill(rp,x+cursor,7+y,x+cursor+dx,7+y+dy);

	BltBitMap(&bufbm,message[i].x1,175 + message[i].y1,bm,x+cursor,7+y,
		dx,dy,0xc0,3,buffer);

	cursor += 10 + dx + x;
}
/***********************/
void ScrubMessage()
{
	SetWrMsk(rp,31); SetAPen(rp,_black);
	RectFill(rp,10,12,145,30);
}		
/***********************/
void PlayShot()
{
	short i,volume;
	
	ball[CUE].angle = cueangle+180;
	CheckAngle(&ball[CUE].angle);
	ball[CUE].speed = cuespeed;
	ball[CUE].lasthit = NOHIT;
	firsthit=0; /* Which ball is hit first */
	for (i=0; i<balls; i++) ball[i].justpotted=FALSE;
	DrawCue(OFF); DrawCue(HIT);

	if ((volume = 16 + (ball[CUE].speed<<6)/MAXSPEED) > 64) volume = 64;
	PlaySound(&audioinfo[STRIKE],volume);

	while(UpdateBallPos()) CheckCollision();
	
	for (i=0; i<balls; i++) if (!ball[i].potted) DrawBall(i,ON);
	
	spinx = 0; spiny = 0; swerve = 0; swervetotal = 0;
	if (gametype==PRACTICE)
	{
		for (i=1; i<balls; i++) if (!ball[i].potted) break;
		if (i==balls) /* No balls left on table */ endofgame=TRUE;
	}
	else
	{
		CheckResult();
		if (player[turn].colour==black && !endofgame && cheer)
			PlaySound(&audioinfo[CHANT],64);
	}
	if (packstate!=RACKED) newbreak=FALSE;

	if (packstate==OPEN)
	{
		for (i=1; i<balls; i++) if (!ball[i].hasmoved) break;		
		if (i==balls) packstate=SPREAD;
	}
	cuespeed=MINSPEED;
	if (endofgame) { DrawCue(ON); return; }
	CheckWhitePotted();
	WriteMessage(player[turn].nametag,5,5,NEW);
	Delay(20); DrawCue(ON);
}
/**********************/
void CheckWhitePotted()
{
	short returnhit=FALSE,angle,hyp;
	
	if (ball[CUE].potted)
	{
		WriteMessage(MSG_PLACECUEBALL,5,5,NEW);	Delay(50);
		
		ball[CUE].potted = FALSE;
		ball[CUE].x = (TABLELENGTH/4);
		ball[CUE].y = (TABLEWIDTH/2 - BASE*3) + Rand(BASE*6);
		ball[CUE].speed = 0;

		while(TouchingBall(CUE)!=NOHIT)
		{
			/* White ball has been placed over another ball! */
			
			angle = Rand(180);
			hyp = Rand(SEMICIRCRAD);
				
			ball[CUE].x = TABLELENGTH/4 -
						 quicksin[angle]*hyp/BASE;
			ball[CUE].y = TABLEWIDTH/2 +
						 quickcos[angle]*hyp/BASE;
		}
		ball[CUE].scrnx = ball[CUE].x/BASE;
		ball[CUE].scrny = ball[CUE].y/BASE;

		DrawBall(CUE,ON);
		if (!(gametype == DEMO || (gametype==PLYR1 && turn==COMPLYR)))
		while(!returnhit)
		{
			short dx,dy,opp,adj;
						
			dx=0; dy=0;
			switch (GetInput())
			{	
				case 76: dy=BASE; break;
				case 77: dy=-BASE; break;
				case 78: dx=BASE; break;
				case 79: dx=-BASE; break;
				case 68: returnhit = TRUE; break;
			}
			WaitTOF(); DrawBall(CUE,OFF);
			
			opp = ((ball[CUE].x+=dx) - TABLELENGTH/4)/BASE;
			adj = ((ball[CUE].y+=dy) - TABLEWIDTH/2)/BASE;
			ball[CUE].scrnx = ball[CUE].x/BASE;
			ball[CUE].scrny = ball[CUE].y/BASE;
			
			if (opp > 0 ||
				QuickRoot(opp*opp + adj*adj) > SEMICIRCRAD/BASE ||
				TouchingBall(CUE)!=NOHIT)
				{ ball[CUE].x-=dx; ball[CUE].y-=dy; }
			DrawBall(CUE,ON);
		}
		ball[CUE].oldscrnx = ball[CUE].scrnx;
		ball[CUE].oldscrny = ball[CUE].scrny;
	}
}
/**********************/
void CopyPlayer(from,to)
short from,to;
{
	player[to].nametag = player[from].nametag;
	player[to].maxpower = player[from].maxpower;
	player[to].cuepulls = player[from].cuepulls;
	player[to].skill = player[from].skill;
	player[to].wins = player[from].wins;
}	
/**********************/
void SuperImpose(srcrp,x0,y0,destrp,x1,y1,dx,dy,transp,offset)
short x0,y0,dx,dy,x1,y1,transp,offset;
struct RastPort *srcrp,*destrp;
{
	register short x,y,pixel;
	
	for (y=0; y < dy; y++)
	for (x=0; x < dx; x++)
	if ((pixel = ReadPixel(srcrp,x0+x,y0+y)) != transp)
	{ SetAPen(destrp,offset+pixel); WritePixel(destrp,x1+x,y1+y); }
}
/**********************/
short ChangeSpin()
{
	short input,retnval=TRUE,new=FALSE,spinx2=spinx,spiny2=spiny;
	
	rp->Mask = 31; DrawCross(ON);

	do
	{
		input = GetInput();
		switch(input)
		{
			case UCSR: if (spiny < 400) { spiny2=spiny+100; new=1; }
					break;
			case DCSR: if (spiny > -800) { spiny2=spiny-100; new=1; }
					break;
			case RCSR: if (spinx < 600) { spinx2=spinx+100; new=2; }
					break; 
			case LCSR: if (spinx > -600) { spinx2=spinx-100; new=2; }
					break;
			case ESC: spinx2=0; spiny2=0; new=1; retnval=FALSE; break;
		}		
		switch (new)
		{
			case 2:
			{
				short tempx = spinx,tempy = spiny;
				
				DrawCue(OFF); spinx=spinx2; spiny=spiny2; DrawCue(ON);
				spinx = tempx; spiny = tempy;
			}
			case 1:
				DrawCross(OFF); spinx=spinx2; spiny=spiny2;
				DrawCross(ON); new=FALSE; break;
		}

	} while (input!=RETURN && input!=ESC);
	
	DrawCross(OFF);
	swerve = abs(spiny) * spinx / 4800;
	return retnval;
}
/**********************/
void DrawCross(status)
short status;
{
	blitdat2.x = 157+spinx/100;
	blitdat2.y = 14-spiny/100;
	blitdat2.dx = 5; blitdat2.dy = 5;

	if (status==OFF)
	{
		WaitBeam(40);
		BltBitMap(&bufbm,0,85,bm,blitdat2.x,blitdat2.y,
			blitdat2.dx,blitdat2.dy,0xc0,31,buffer);
	}
	else
	{
		SetAPen(rp,_cross);		
		BltBitMap(bm,blitdat2.x,blitdat2.y,&bufbm,
			0,85,blitdat2.dx,blitdat2.dy,0xc0,31,buffer);
	
		BltPattern(rp,crossmask,
			157+spinx/100,14-spiny/100,
			161+spinx/100,18-spiny/100,2);
	}
}
/**********************/
void ChangePower()
{
	short offset;
	
	if (powerdirn==DOWN)
	{
		if ((cuespeed-=(SPEEDINC/2)) < MINSPEED)
		{ powerdirn=UP; cuespeed=MINSPEED; }
	}
	else if ((cuespeed+=SPEEDINC) > MAXSPEED)
	{ powerdirn=DOWN; cuespeed=MAXSPEED; }
	
	offset = (cuespeed*16)/BASESP;
	
	rp->Mask = 31;
	WaitBeam(21);
	SetAPen(rp,_back); RectFill(rp,201+offset,18,265,21);
	SetAPen(rp,_white); RectFill(rp,199,18,200+offset,21);
}
/**********************/
void WaitBeam(y)
short y;
{
	short vert;
	while(1)
	{
		vert = VBeamPos();
		if (vert > y+40 && vert < y+70) break;
	}
}
/**********************/
void MoveCue(dirn,increment)
short dirn,increment;
{
	DrawCue(OFF);
	
	if (dirn==CLOCKWISE) cueangle += (increment==SMALL)? 1:10;
	else cueangle -= (increment==SMALL)? 1:10;
	CheckAngle(&cueangle);
	DrawCue(ON);
}	
/**********************/
void DoAssigns()
{	
/*	short i; */

	quicksin = memblock;
	quickcos = quicksin+361;
	quickarcsin = quickcos+361;
	
/*
	for (i=0; i<361; i++)
	{
		quicksin[i] = sin((double)i * PI / 180.0) * (double)BASE;
		quickcos[i] = cos((double)i * PI / 180.0) * (double)BASE;
	}
	for (i=0; i<5001; i++)
		quickarcsin[i] = asin((double)i / 5000.0) * 180.0 / PI;
*/
	while (!(fh = (struct FileHandle *)Open("FNP:trig.dat",MODE_OLDFILE)));
	
	Read((BPTR)fh,quicksin,361*2);
	Read((BPTR)fh,quickcos,361*2);
	Read((BPTR)fh,quickarcsin,5001);
	Close((BPTR)fh);
}
/**********************/
short GetInput()
{
	short code = NULL,code2;
	ULONG joy = (ULONG)custom.joy1dat;		/* joystick info */

	if (joydelay==1) { Delay(10); joydelay=2; }
	else if (!(gametype==PLYR2 && turn == 1))
	{
		if ((long)(joy & 768) == 512L) code = LCSR;	/* Up-left */
		else if ((long)(joy & 3) == 2L) code = RCSR; /* Down-right */
		else if ((long)(joy & 3) == 3L) code = RCSR;
		else if ((long)(joy & 768L) == 768L) code = LCSR;
		else if ((long)(joy & 768) == 256L) code = UCSR;
		else if ((long)(joy & 3) == 1L) code = DCSR;
		else joydelay = 0;
				
		if (!joydelay && code) joydelay=1;
		
		while (!(*(char *)JOYFIRE & 0x80)) code = RETURN;
		if (code==RETURN) Delay(20); /* Joystick fire */
	}

	if (!(msg = (struct IntuiMessage *)GetMsg(w->UserPort)))
		 return code;
	do
	{
		code2 = msg->Code;
		ReplyMsg((struct Message *)msg);
	
	} while((msg = (struct IntuiMessage *)GetMsg(w->UserPort)) &&
		code2!=ESC);

	if (gametype==PLYR2 && turn == 0)
	{
		switch (code2)
		{
			case UCSR:
			case DCSR:
			case LCSR:
			case RCSR:
			case RETURN: return code;
		}
	}
	return code2;
}
/**********************/
char UpdateBallPos()
{
	short i,j,x1,y1,x2,y2,dx,dy;
	char ballsmoving = FALSE;
	
	for(i=0; i<balls; i++) /* Update ball position */
	if (!ball[i].potted && ball[i].speed > 0)
	{
		ballsmoving=TRUE; ball[i].hasmoved=TRUE;
		ball[i].x += ball[i].speed * quicksin[ball[i].angle] / BASESP;
		ball[i].y += ball[i].speed * quickcos[ball[i].angle] / BASESP;
		
		if ((ball[i].scrnx = ball[i].x/BASE) - ball[i].oldscrnx |
			(ball[i].scrny = ball[i].y/BASE) - ball[i].oldscrny)
		{
			if (CheckPotted(i))
			{ ball[i].potted = TRUE; ball[i].justpotted = TRUE; }
			else CheckBounce(i);
			
			if (ball[i].potted)
			{
				ball[i].scrnx = ball[i].oldscrnx;
				ball[i].scrny = ball[i].oldscrny;
				DrawBall(i,OFF);
			}
			else
			{
				USHORT *tmask = tempmask+(i<<4);	
				
				x1 = (X1CORNER-8) + ball[i].oldscrnx;
				y1 = (Y2CORNER-7) - ball[i].oldscrny;
				x2 = (X1CORNER-8) + ball[i].scrnx;
				y2 = (Y2CORNER-7) - ball[i].scrny;
				dx = x2-x1; dy = y2-y1;

				if (dx != ball[i].olddx || dy != ball[i].olddy)
				{
					register USHORT *quickmask = ballmask;
			
					if (dx >= 0) for (j=0; j<13; j++)
					tmask[j] = quickmask[j] ^
						(quickmask[j] & (*(quickmask+j-dy) >> dx));
		
					else for (j=0; j<13; j++)
					tmask[j] = quickmask[j] ^
						(quickmask[j] & (*(quickmask+j-dy) << -dx));
				
					ball[i].olddx = dx; ball[i].olddy = dy;
				}				

				if (ball[i].colour != white)
				{
					register char *ptr = bp2 + (y1+3)*40 + ((x1+7)>>3);
					register short data = 128 >> ((x1+7) % 8);
					
					ptr[0] &= ~data; ptr[80] &= ~data;
					if (data==1)
					{ ptr[40] &= 252; ptr[41] &= 127; }
					else if (data==128)
					{ ptr[40] &= 63; ptr[39] &= 254; }
					else ptr[40] &= ~(data + (data>>1) + (data<<1));
				}
				DrawBallDirect(ball[i].colour,x2,y2);
				SetAPen(rp,_baize);
				rp->Mask = ballplanes[ball[i].colour];
				BltPattern(rp,tmask,x1,y1,x1+15,y1+12,2);

				ball[i].oldscrnx = ball[i].scrnx;
				ball[i].oldscrny = ball[i].scrny;
			}
		}
		if (balls==10) ball[i].speed-=(FRICTION/2);
		else ball[i].speed-=FRICTION;
		if (ball[i].speed < 0) ball[i].speed = 0;
	}
	spinx -= (spinx > 0)? SPINFRICTION:-SPINFRICTION;
	spiny -= (spiny > 0)? SPINFRICTION:-SPINFRICTION;
	if ((swervetotal+=abs(swerve)) > 99)
	{
		ball[CUE].angle += (swerve > 0)? 1:-1;
		CheckAngle(&ball[CUE].angle);
		swervetotal-=100;
	}
	swerve -= (swerve > 0)? 1:-1;
	return ballsmoving;
}
/**********************/
short CheckPotted(i)
short i;
{
	BOOL potted = FALSE;
	short volume;
	register USHORT x = ball[i].x, y = ball[i].y;
		
	if (ball[i].potted) return(FALSE);
	
	if (x > (TABLELENGTH-BALLRADIUS) &&
	    (y < POCKET || y > (TABLEWIDTH-POCKET)))
			potted = TRUE;
	
	else if (x < BALLRADIUS && (y < POCKET || y > (TABLEWIDTH-POCKET)))
		potted = TRUE;
						
	else if (y < BALLRADIUS &&
		    (x < POCKET || 
		    (x > (TABLELENGTH/2 - POCKET/2) &&
			x < (TABLELENGTH/2 + POCKET/2)) ||
			x > (TABLELENGTH-POCKET)))
				potted = TRUE;
				
	else if (y > (TABLEWIDTH-BALLRADIUS) &&
		    (x < POCKET ||
			(x > (TABLELENGTH/2 - POCKET/2) &&
			 x < (TABLELENGTH/2 + POCKET/2)) ||
			 x > (TABLELENGTH-POCKET)))
				potted = TRUE;

	if (potted)
	{
		if ((volume = 16 + (ball[i].speed<<6)/MAXSPEED) > 64) volume = 64;
		PlaySound(&audioinfo[POTTED],volume);
		if (ball[i].speed > (MAXSPEED*5/8) && !(Rand(4)))
		{
			ball[i].angle+=180; CheckAngle(&ball[i].angle);
			ball[i].x += ball[i].speed * quicksin[ball[i].angle] / BASESP;
			ball[i].y += ball[i].speed * quickcos[ball[i].angle] / BASESP;
			ball[i].scrnx = ball[i].x/BASE; ball[i].scrny = ball[i].y/BASE;
			ball[i].speed >>= 2; ball[i].lasthit=NOHIT; potted=FALSE;
		}
	}
	return(potted);
}
/**********************/
void CheckBounce(i)
short i;
{
	short bounce=FALSE,volume;
	register USHORT x = ball[i].x, y = ball[i].y;
	register short angle = ball[i].angle;
	
	if (ball[i].potted) return;
	
	if (x < BALLRADIUS && angle > 180)
	{ angle=360-ball[i].angle; bounce=TRUE; }

	else if (x > (TABLELENGTH-BALLRADIUS) && angle < 180)
	{ angle=360-ball[i].angle; bounce=TRUE; }
	
	else if (y < BALLRADIUS && angle < 270 && angle > 90)
	{
		if (angle <= 180) angle=180-ball[i].angle;
		else angle=540-ball[i].angle; bounce=TRUE;
	}
	else if (y > (TABLEWIDTH-BALLRADIUS) && (angle > 270 || angle < 90))
	{
		if (angle <= 180) angle=180-ball[i].angle;
		else angle=540-ball[i].angle; bounce=TRUE;
	}
	if (bounce)
	{
		if ((volume = 16 + (ball[i].speed<<6)/MAXSPEED) > 64) volume = 64;
		PlaySound(&audioinfo[BOUNCE],volume);
		if (ball[CUE].speed > (MAXSPEED*3/4) && spiny < -600 && !(Rand(3)))
			CueJumpAnim();

		ball[i].angle = angle;
		ball[i].x += ball[i].speed * quicksin[ball[i].angle] / BASESP;
		ball[i].y += ball[i].speed * quickcos[ball[i].angle] / BASESP;
		ball[i].scrnx = ball[i].x/BASE; ball[i].scrny = ball[i].y/BASE;
		if (ball[i].scrnx < (BALLRADIUS/BASE)) ball[i].scrnx = (BALLRADIUS/BASE);
		else if (ball[i].scrnx > ((TABLELENGTH-BALLRADIUS)/BASE))
			ball[i].scrnx = ((TABLELENGTH-BALLRADIUS)/BASE);
		if (ball[i].scrny < (BALLRADIUS/BASE)) ball[i].scrny = (BALLRADIUS/BASE);
		else if (ball[i].scrny > ((TABLEWIDTH-BALLRADIUS)/BASE))
			ball[i].scrny = ((TABLEWIDTH-BALLRADIUS)/BASE);
		ball[i].speed = ((ball[i].speed << 3) - ball[i].speed) >> 3; 
		ball[i].lasthit = NOHIT;
		if (i==CUE)
		{
			ball[CUE].angle -= spinx / 25;
			spinx = 0; spiny = 0; swerve = 0;
			CheckAngle(&ball[CUE].angle);
		}
		if (packstate==BROKEN) packstate=OPEN; /* can now hear all collisions */
	}
}
/**********************/
short GetMouse()
{
	short status=0;

	UBYTE *leftmouse = (UBYTE *)0xbfe001;
	UWORD *rightmouse = (UWORD *)0xdff016;
	
	if (!(*leftmouse & 0x40)) status |= LMOUSE;
	if ((*rightmouse & 0x0400) != 0x0400) status |= RMOUSE;
	return status;
}
/****************/
void SetUpTable()
{
	short i,j,k,offset,oldoffset=0;
	
	for (i=0; i<balls; i++) if (!ball[i].potted && ball[i].x)
	{
		ball[i].x = ball[i].oldscrnx*BASE;
		ball[i].y = ball[i].oldscrny*BASE;
		DrawBall(i,OFF);
	}
	ball[CUE].x = (TABLELENGTH/4);
	ball[CUE].y = (TABLEWIDTH/2 - BASE*3) + Rand(BASE*6);
	
	for (i=0,k=1; i<5; i++)
	for (j=0; j<=i; j++,k++)
	{
		ball[k].x = (PLACEPACK-BASE) + PACKHORIZSEP*i;
		ball[k].y = (TABLEWIDTH / 2) + (BALLDIAMETER *j) -
				(BALLDIAMETER * i / 2) - BASE;
		ball[k].x += Rand((BASE/2));
		ball[k].y += Rand((BASE/2));
	}
	
	if (balls==10)
	{
		ball[7].y+=BALLDIAMETER; ball[8].y+=BALLDIAMETER;
		ball[9].x+=PACKHORIZSEP; ball[9].y-=(BALLDIAMETER/2);
		for (i=0; i<10; i++) ball[i].colour = ball9colour[i];
	}	
	else for (i=0; i<16; i++) ball[i].colour = ball15colour[i];
	
	BltBitMap(&bufbm,200,0,bm,182,107,71,79,0xc0,31,buffer);
	if (gametype!=PRACTICE && cheer) PlaySound(&audioinfo[CHEER],64);

	for (i=0; i<balls; i++)
	{
		ball[i].potted = FALSE; ball[i].speed = 0;
		ball[i].lasthit = NOHIT;
		ball[i].hasmoved = 0;
		ball[i].scrnx = ball[i].x/BASE;
		ball[i].scrny = ball[i].y/BASE;
		ball[i].oldscrnx = ball[i].scrnx;
		ball[i].oldscrny = ball[i].scrny;
		ball[i].olddx = -99; ball[i].olddy = -99;
		DrawBall(i,ON);
	}
	if (balls==16) for (j=0,k=25; j<5; j++,k-=5)
	{
		for (i=0; i<=360; i+=30)
		{
			offset = quicksin[i]*k/BASE;
			WaitBeam(185);
			BltBitMap(bm,(182-15)-oldoffset,107,
				bm,(182-15)-offset,107,(71+30),79,0xc0,31,buffer);
			if (i==90 || i==270) PlaySound(&audioinfo[COLLIDE],64);
			oldoffset = offset;
		}
	}
	Delay(30);
	BltBitMap(bm,100,107,bm,182,107,71,79,0xc0,31,buffer);
	for (i=1; i<balls; i++) DrawBall(i,ON);
		
	if (coin==-1) coin = Rand(2);
	coin = 1-coin; turn = coin;
	if (balls==16)
	{ player[0].colour = anyball; player[1].colour = anyball; }
	else { player[0].colour = red; player[1].colour = red; }
	
	player[turn].goes = 1;
	
	if (gametype!=PRACTICE) DrawBall(player[turn].colour,ICON);	
	cueangle = 270; DrawCue(ON);
	packstate=RACKED; plyrbreak=0; newbreak=TRUE;
}
/**********************/
void CheckBallDrag()
{
	short i,x,y,button;
	
	if (button = GetMouse())
	{
		x = s->MouseX; y = s->MouseY;

		for (i=0; i<balls; i++)
		{
			if (!ball[i].potted &&
				x > ball[i].scrnx+(X1CORNER-7) &&
				x < ball[i].scrnx+(X1CORNER+8) &&
				y > (Y2CORNER-8)-ball[i].scrny &&
				y < (Y2CORNER+8)-ball[i].scrny &&
				ReadPixel(rp,x,y) != _baize)
			{
				DrawCue(OFF);
				if (button==RMOUSE && i!=CUE)
				{
					DrawBall(i,OFF);
					ball[i].potted = TRUE;
					PlaySound(&audioinfo[POTTED],64);
				}
				else if (button==LMOUSE)
				{
					PlaySound(&audioinfo[COLLIDE],64);
					while (GetMouse() == LMOUSE)
					{
						short oldx,oldy;
					
						x = s->MouseX; y = s->MouseY;
						if (x < (X1CORNER+7)) x = (X1CORNER+7);
						if (x > (X2CORNER-8)) x = (X2CORNER-8);
						if (y < (Y1CORNER+8)) y = (Y1CORNER+8);
						if (y > (Y2CORNER-7)) y = (Y2CORNER-7);
						{
							oldx = ball[i].x; oldy = ball[i].y;
							ball[i].x = (x-X1CORNER)*BASE;
							ball[i].y = (Y2CORNER-y)*BASE;
							if (TouchingBall(i)==NOHIT)
							{
								WaitBeam(y+7); DrawBall(i,OFF);
								ball[i].scrnx = ball[i].x/BASE;
								ball[i].scrny = ball[i].y/BASE;
								DrawBall(i,ON);
							}
							else { ball[i].x = oldx; ball[i].y = oldy; }
						}
					}
				}
				ball[i].oldscrnx = ball[i].scrnx;
				ball[i].oldscrny = ball[i].scrny;
				DrawCue(ON); while (GetMouse());
			}
		}
		if (button==(LMOUSE|RMOUSE))
		{
			for (i=1; i<balls; i++)
			if (ball[i].potted &&
				x > (X1CORNER+6) && x < (X2CORNER-8) &&
				y > (Y1CORNER+8) && y < (Y2CORNER-6))
			{
				ball[i].potted = FALSE;
				ball[i].x = (x-X1CORNER)*BASE;
				ball[i].y = (Y2CORNER-y)*BASE;
				if (TouchingBall(i)==NOHIT)
				{
					DrawCue(OFF);
					ball[i].scrnx = ball[i].x/BASE;
					ball[i].scrny = ball[i].y/BASE;
					ball[i].oldscrnx = ball[i].scrnx;
					ball[i].oldscrny = ball[i].scrny;
					ball[i].speed = 0;
					WaitBeam(y+7); DrawBall(i,ON);
					PlaySound(&audioinfo[COLLIDE],64);
					DrawCue(ON); break;
				}
				else ball[i].potted = TRUE;
			}
			while (GetMouse());
		}
	}
}

/**********************/
void CheckCollision()
{
	char i;
	BOOL collision;

	for(i=0; i<balls; i++) /* Check for collisions */
	{
		register short x1 = ball[i].scrnx + SCRNBALLDIAMETER;
		register short y1 = ball[i].scrny + SCRNBALLDIAMETER;
		register short x2 = x1 - (SCRNBALLDIAMETER*2);
		register short y2 = y1 - (SCRNBALLDIAMETER*2);
		register char j;
		
		for(j=i+1; j<balls; j++)
		{
			/* Quick bounding box test to see if balls touch */
			if (ball[j].scrnx < x1 && ball[j].scrnx > x2 &&
				ball[j].scrny < y1 && ball[j].scrny > y2)
			{
				short dx = ball[j].x - ball[i].x;
				short dy = ball[j].y - ball[i].y;
			
				if (dx*dx + dy*dy < (BALLDIAMETER*BALLDIAMETER))
				{
					collision=TRUE;
				
					/* Bug-fix to stop multiple collisions for same balls */
					if (ball[j].lasthit==i && ball[i].lasthit==j)
						collision=FALSE;
				
					/* Balls are both stationary */
					else if (!ball[i].speed && !ball[j].speed)
						collision=FALSE;

					/* Balls must be unpotted */
					else if (ball[j].potted || ball[i].potted)
						collision=FALSE;

					if (collision) MakeCollision(i,j);
				}
			}
		}
	}
}

/**********************/
void DrawBallDirect(col,x,y)
short col,x,y;
{
	SetAPen(rp,ballpalette[col]);
	rp->Mask = ballplanes[col];
	BltPattern(rp,ballmask,x,y,x+15,y+12,2);

	if (col==baize) return;

	if (col != white)
	{
		register char *ptr = bp2 + (y+3)*40 + ((x+7)>>3);
		register short data = 128 >> ((x+7) % 8);

		ptr[0] |= data; ptr[80] |= data;
		if (data==1) { ptr[40] |= 3; ptr[41] |= 128; }
		else if (data==128) { ptr[40] |= 192; ptr[39] |= 1; }
		else ptr[40] |= data + (data>>1) + (data<<1);
	}						
}
/**********************/
void DrawBall(i,status)
short i,status;
{
	short x = X1CORNER - 8 + ball[i].scrnx,
		 y = Y2CORNER - 7 - ball[i].scrny;
	
	if (status==ON) DrawBallDirect(ball[i].colour,x,y);
	else if (status==OFF) DrawBallDirect(baize,x,y);
	else if (status==ICON)
	{
		DrawBallDirect(baize,287,11);
		
		switch(i)
		{
			case red:
			case yellow:
			case black:
				DrawBallDirect(i,287,11);
				break;
				
			case anyball:
				DrawBallDirect(red,287,11);
				ballmask = hybridmask; /* ballmask2 = hybridmask2; */
				DrawBallDirect(baize,287,11);
				DrawBallDirect(yellow,287,11);
				ballmask = &mask[12]; /* ballmask2 = mask2; */ break;
		}
	}		
}
/**********************/
void CheckResult()
{
	short i,redpot=0,yellowpot=0,whitepot=0,blackpot=0,
		redsleft=FALSE,yellowsleft=FALSE;

	foul=FALSE;
	
	for (i=0; i<balls; i++)
	{
		if (ball[i].justpotted) switch (ball[i].colour)
		{
			case red:		redpot++; break;
			case yellow:	yellowpot++; break;
			case black:	blackpot++; break;
			case white:	whitepot++; break;
		}
		
		if (!ball[i].potted) switch (ball[i].colour)
		{
			case red:		redsleft=TRUE; break;
			case yellow:	yellowsleft=TRUE; break;
		}
	}	
	switch(player[turn].colour)
	{
		case black:
			if (blackpot)
			{
				endofgame=TRUE;
				if (whitepot) foul=TRUE;
			}
			if (redpot || yellowpot) foul=TRUE;
			if (ball[firsthit].colour!=black) foul=TRUE;
			if (!blackpot) player[turn].goes=0;
			break;
			
		case red: /* Includes 9 ball variations... */
			if (!redsleft)
			{
				player[turn].colour=black;
				if (balls==10) player[1-turn].colour=black;
			}
			if (!yellowsleft && balls==16) player[1-turn].colour=black;
			if (!redpot) player[turn].goes--;
			if (blackpot)
			{
				endofgame=TRUE;
				if (!newbreak) foul=TRUE;
				break;
			}
			if (yellowpot) foul=TRUE;
			if (ball[firsthit].colour!=red) foul=TRUE;
			break;
		case yellow:
			if (!yellowsleft) player[turn].colour=black;
			if (!redsleft) player[1-turn].colour=black;
			if (!yellowpot) player[turn].goes--;
			if (blackpot) { foul=TRUE; endofgame=TRUE; }
			if (redpot) foul=TRUE;
			if (ball[firsthit].colour!=yellow) foul=TRUE;
			break;
		case anyball:
			if (blackpot)
			{
				endofgame=TRUE;
				if (!newbreak) foul=TRUE;
				break;
			}
			if (whitepot) foul=TRUE;
			else if (ball[firsthit].colour != red &&
				ball[firsthit].colour != yellow) foul=TRUE;
			else if (yellowpot && !redpot)
			{ player[turn].colour=yellow; player[1-turn].colour=red; }
			else if (redpot && !yellowpot)
			{ player[turn].colour=red; player[1-turn].colour=yellow; }
			else if (!redpot && !yellowpot) player[turn].goes--;
			break;	
	}
	if (whitepot) foul=TRUE;
	if (endofgame) winner = (foul)? winner = 1-turn:turn;
	
	if (foul)
	{
		turn=1-turn; plyrbreak = 0;
		if (player[turn].colour==black) player[turn].goes = 1;
		else player[turn].goes = 2;
		for (i=0; i<3; i++)
		{
			WriteMessage(MSG_FOUL,40,5,NEW); Delay(20);
			ScrubMessage(); Delay(10);
			if (GetInput() == RETURN) break;
		}
	}
	else if (player[turn].goes==0)
	{
		if (plyrbreak > 2 && cheer) PlaySound(&audioinfo[CHEER],32);
		turn=1-turn; plyrbreak = 0; player[turn].goes = 1;
	}
	else if ((redpot>1 || yellowpot>1 ||
		(!ball[firsthit].potted && (redpot || yellowpot))) &&
		!newbreak)
	{
		plyrbreak++; WriteMessage(MSG_GOODSHOT,5,5,NEW);
		if (cheer) PlaySound(&audioinfo[CHEER],64); Delay(50);
	}
	else plyrbreak++;
	
	if (gametype!=PRACTICE) DrawBall(player[turn].colour,ICON);
}
/**********************/
void DoOptions()
{
	short i,j;
	
	gametype = arg[GAMETYPE];
	gamelength = arg[GAMELENGTH];
	stripper = arg[STRIPPER];
	opponent = arg[OPPONENT];
	balls = arg[BALLCOUNT];
		
	switch(gametype)
	{
		case PLYR1: CopyPlayer(2,0); CopyPlayer(opponent+4,1); break;
		case PLYR2: CopyPlayer(2,0); CopyPlayer(3,1); break;
		case PRACTICE: CopyPlayer(2,0); CopyPlayer(2,1); break;
		case DEMO:
			i = Rand(4); do j = Rand(4); while (i==j);
			CopyPlayer(4+i,0); CopyPlayer(4+j,1); break;
	}
	if (gamelength==BEST3) matches=3;
	else if (gamelength==BEST5) matches=5;
		
	rgb2[_ltbaize] = RGBbaize[arg[BAIZE]*5];
	rgb2[_dkbaize] = RGBbaize[arg[BAIZE]*5+1];
	rgb2[_baize] = RGBbaize[arg[BAIZE]*5+2];
	rgb2[_ltbaize2] = RGBbaize[arg[BAIZE]*5+3];
	rgb2[_ltbaize3] = RGBbaize[arg[BAIZE]*5+4];

	rgb2[_red] = RGBball[arg[BALL1]];
	rgb2[_highred] = 0xfff;
	rgb2[_yellow] = RGBball[arg[BALL2]];
	rgb2[_highyel] = 0xfff;	
	
	FadeScreen(s,DOWN); SetRast(rp,_back);
	for (i=0; i<32; i++) rgb[i] = rgb2[i];

	if (gametype!=PRACTICE)
	{
		WriteMessage(player[0].nametag,80,80,NEW);
		WriteMessage(MSG_VS,0,80,FOLLOW);
		WriteMessage(player[1].nametag,0,80,FOLLOW);
		FadeScreen(s,UP); ClickDelay(150);
	}
	LoadIFF(&tableIFF,s,VIEW);
	BltBitMap(bm,182,107,&bufbm,200,0,71,79,0xc0,31,buffer);
}
/********************* Allocatation Routines ***********************/
short AudioOpenIt()
{
	short i;	

	bufwave = AllocMem(BUFWAVELEN,MEMF_CLEAR | MEMF_CHIP);

	for (i=0; i<4; i++)
	{
		if (!(audio[i] = (struct IOAudio *) GetDeviceBlock(AUDIO_LEN)))
			return(0);
	
		audio[i]->ioa_Data = &Channel_Map[i];
		audio[i]->ioa_Length = 1; /* sizeof(Channel_Map); */
		audio[i]->ioa_Request.io_Message.mn_Node.ln_Pri = PRECIDENCE;
		if (OpenDevice("audio.device",NULL,audio[i],NULL)) return(0);
		/* Cut out initial noise */
		Audio_PerVol(audio[i],124,0);
		Audio_Write(audio[i],bufwave,BUFWAVELEN);
	}
	for (i=0; i<SAMPLES; i++)
	{		
		while (!(fh = (struct FileHandle *)
			Open(audioinfo[i].filename,MODE_OLDFILE)));
		
		audioinfo[i].audio = audio[audioinfo[i].channel];
		audioinfo[i].wave =
			AllocMem(audioinfo[i].wavelen,MEMF_CLEAR | MEMF_CHIP);
		Seek((BPTR)fh,AM2,OFFSET_BEGINNING);
		MyRead(fh,audioinfo[i].wave,audioinfo[i].wavelen);
		Close((BPTR)fh);
	}
	return(1);
}
/************************/
void CloseIt()
{
	short i;

	prefs.KeyRptSpeed.tv_micro = oldkeyspeed;
	prefs.KeyRptDelay.tv_micro = oldkeydelay;
	SetPrefs(&prefs,sizeof(struct Preferences),TRUE);

	for (i=0; i<4; i++)
		FreeDeviceBlock(audio[i],(BYTE)CLOSE_DEVICE);	

	for (i=0; i<SAMPLES; i++) if (audioinfo[i].wave)
		FreeMem(audioinfo[i].wave,audioinfo[i].wavelen);
	if (bufwave) FreeMem(bufwave,BUFWAVELEN);

	for (i=0; i<5; i++)
		if (bufbm.Planes[i]) FreeRaster(bufbm.Planes[i],320,256);

	if (memblock) FreeMem(memblock,MEMSIZE);
	if (w) CloseWindow(w);
	if (w2) CloseWindow(w2);
	if (s) CloseScreen(s);
	if (s2) CloseScreen(s2);
	if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
	if (GfxBase) CloseLibrary((struct Library *)GfxBase);
}
/**********************/
UWORD Rand(max)
UWORD max;
{
	seed = seed*seed + 5*seed + VBeamPos();
	return (seed % max);
}
/**********************/
void CheckAngle(angle)
short *angle;
{
	if (*angle > 359) *angle-=360;
	else if (*angle < 0) *angle+=360;
}
/**********************/
short FindAngle(opp,adj,hyp)
WORD opp,adj,hyp;
{
	register short angle;
	
	if (opp > hyp) opp=hyp;
	else if (-opp > hyp) opp=-hyp;
	angle = quickarcsin[abs(opp * 5000 / hyp)];
	if (adj < 0 && opp >= 0) angle=180-angle;
	else if (adj < 0 && opp < 0) angle+=180;
	else if (adj >= 0 && opp < 0) angle=360-angle;
	return angle;
}
/**********************/
WORD QuickRoot(x)
long x;
{
	register USHORT y=2,dy;
	register ULONG ysq;
	
	while(y*y < x) y<<=1;
	dy=y>>1;
	
	while(1)
	{	
		ysq = y*y;
		if (ysq==x) return y;
		if (ysq < x) y+=dy;
		else y-=dy;
		if (!(dy>>=1))
		{
			if (y*y < x) y++; /* add 1? - hyp not < opp or adj! */ 
			return y;
		}
	}
}
/********************/
APTR GetDeviceBlock(size)
ULONG size;
{
	struct MsgPort *port;
	APTR request;
	
	if (!(port = (struct MsgPort *) CreatePort(0,0))) return(NULL);
	
	if (!(request = (APTR) CreateExtIO(port,size)))
	{ DeletePort(port); return(NULL); }
	
	return(request);
}
/********************/
void FreeDeviceBlock(audio,close)
struct IOAudio *audio;
BYTE close;
{
	if (audio->ioa_Request.io_Message.mn_ReplyPort)
		DeletePort(audio->ioa_Request.io_Message.mn_ReplyPort);
	if (close && audio->ioa_Request.io_Device) CloseDevice(audio);
	DeleteExtIO(audio);
}
/********************/
void PlaySound(info,volume)
struct audioinfostruct *info;
short volume;
{
	if (CheckIO(info->audio))
	{
		info->volume = volume;
		if (info->flag==RESET)
		{
			Audio_PerVol(info->audio,124,0);
			Audio_Write(info->audio,info->wave,info->wavelen);
			while(!(CheckIO(info->audio)));
			Audio_PerVol(info->audio,info->period,info->volume);
			Audio_Write(info->audio,bufwave,BUFWAVELEN);
		}
		else /* flag == BUFFER */
		{
			Audio_PerVol(info->audio,info->period,info->volume);
			if (info->bufstate = 1 - info->bufstate)
				Audio_Write(info->audio,info->wave,info->wavelen);
			else Audio_Write(info->audio,bufwave,BUFWAVELEN);
		}
	}
}
/********************/
void Audio_PerVol(audio,period,volume)
struct IOAudio *audio;
UWORD period,volume;
{
	audio->ioa_Request.io_Flags = ADIOF_SYNCCYCLE;
	audio->ioa_Period = period;
	audio->ioa_Volume = volume;
	audio->ioa_Request.io_Command = ADCMD_PERVOL;
	SendIO(audio);
}
/********************/
void Audio_Write(audio,waveform,wavelength)
struct IOAudio *audio;
UBYTE *waveform;
UWORD wavelength;
{
	audio->ioa_Data = waveform;
	audio->ioa_Request.io_Command	= CMD_WRITE;
	audio->ioa_Request.io_Flags = 0;
	audio->ioa_Length = wavelength;
	audio->ioa_Cycles = 1;
	SendIO(audio);
}

/************************/
void CopyBalls(fromball,toball)
struct ballstruct *fromball,*toball;
{
	short i;
	
	for (i=0; i<balls; i++)
	{
		toball[i].x = fromball[i].x;
		toball[i].y = fromball[i].y;
		toball[i].scrnx = fromball[i].scrnx;
		toball[i].scrny = fromball[i].scrny;
		toball[i].angle = fromball[i].angle;
		toball[i].speed = fromball[i].speed;
		toball[i].potted = fromball[i].potted;
		toball[i].justpotted = fromball[i].justpotted;
	}
} 
