/* Version 2 4/22/90	*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <math.h>

/* Indexes for 1st dimension of obj	*/
/* The order they are in is important	*/
#define	AST	0
#define	ENEMY	96
#define ENEMYBUL 97
#define	FBUL	98
#define	LASTBUL	102
#define	SHIP	103
#define LASTSHAPE 103

/* Shapes	*/
#define	ASTSHAPE1	0
#define ASTSHAPE2	1
#define ASTSHAPE3	2
#define	SHIPSHAPE	3
#define	ENEMYSHAPE	4
#define ENBULSH		5
#define	BULSHAPE	6

#define	pi	3.1415926535897932384
#define letheight	20	/* height of font	*/

/* Global variables:	*/
typedef struct {double angle; int length} PolarPair;
typedef struct {int shape, alive, time; double x, y, xvel, yvel, rot, rotvel} Objtype;
Objtype	obj[SHIP+1];
/*	In shapes pairs, 1st # is radians, 2nd is length in pixels.
	Degrees: 0 ->, pi/2 down, pi <-, 3*pi/2 up
*/
PolarPair shapes[BULSHAPE+1][11] =
	{	{{0,0}, {3*pi/2,40}, {0,20}, {pi/4,28}, {pi/2,40}, /* just crossed 0-deg line */
		 {3*pi/4,28},{pi,40},{5*pi/4,28},{3*pi/2,40},{7*pi/4,28},{0,20}},
/*	hexagon if you prefer
		{{0,0}, {3*pi/2, 20}, {pi/6, 20}, {pi/2, 20},
                 {5*pi/6, 20}, {7*pi/6, 20}, {3*pi/2, 20}, {11*pi/6, 20}},
*/
		{{0,0}, {3*pi/2,20}, {0,10}, {pi/4,14}, {pi/2,20},
		 {3*pi/4,14},{pi,20},{5*pi/4,14},{3*pi/2,20},{7*pi/4,14},{0,10}},
		{{0,0}, {3*pi/2,10}, {0,5}, {pi/4,7}, {pi/2,10},
		 {3*pi/4,7},{pi,10},{5*pi/4,7},{3*pi/2,10},{7*pi/4,7},{0,5}},
		{{0,0}, {5*pi/4,28}, {0,20}, {pi/4,28},{3*pi/4,28},{pi,20},{7*pi/4,28}},	/* Ship */
		{{0,0}, {pi,20},{7*pi/4,28},{pi/4,28},{pi,20}},
		{{0,0}, {7*pi/4, 4}, {pi/4, 4}, {3*pi/4, 4}, {5*pi/4, 4}},
		{{0,0}, {0,10}}
	};
int	width, height, numasts, ships, score,
	highscore = 0,
	rndint = 73,
	oldships = 99, oldscore = 99,
	nextbul = FBUL,			/* Obj# of next bullet fired	*/
	numpairs[BULSHAPE+1]	= {11, 11, 11, 7, 5, 5, 2},
	shapesize[BULSHAPE+1]	= {44*44, 21*21, 10*10, 28*28, 20*20, 2, 1};

double square(n)
	double n;
{	return n*n;
}

int myrand()
{	extern int rndint;
	rndint = rand(rndint);
	return rndint;
/*	Random # generator that could work if fiddled with
	if (!i) i = (int) 890934;	need some 1 bits
	i = i >> 1 ^ (i & 4)<<13;
	i = i >> 1 ^ (i & 64)<<9;
	i = i >> 1 ^ (i & 2048)<<5;
	return i;
*/
}

initasts()
{	int i;
	extern Objtype obj[SHIP+1];

	for (i = 0; i < LASTSHAPE+1; i++)
	{	obj[i].rot = 0;
		obj[i].rotvel = 0;
	}
	for (i = 0; i < ENEMY; i++)
	{	obj[i].shape = ASTSHAPE1;
	}
	obj[SHIP].shape = SHIPSHAPE;
	obj[ENEMY].shape = ENEMYSHAPE;
	obj[ENEMYBUL].shape = ENBULSH;
	for (i = FBUL; i < LASTBUL+1; i++)
		obj[i].shape = BULSHAPE;
}

makeasts(level)
{	int i;
	extern Objtype obj[SHIP+1];
	extern int numasts;
	unsigned char a;

	for (i = 0; i < LASTSHAPE+1; i++)
		obj[i].alive = 0;	/* Erase objs from last level	*/
	for (i = ENEMYBUL; i < LASTBUL+1; i++)
		obj[i].time = 0;		/* No bullets in the air	*/
	for (i = 0; i < level+4; i++)	/* Asteroids:			*/
	{	a = myrand(); a>>=1;	/* a = rand# from 0 to 127	*/
		if (a > 63)
			obj[i].x = (double) a;
			else obj[i].x = (double) (width - a);
		a = myrand(); a>>=1;	/* Now the same for y		*/
		if (a >  63)
			obj[i].y = (double) a;
			else obj[i].y = (double) height - a;
		a = myrand(); a = 4 - a>>5;
		obj[i].rot = (double) a;
		a = myrand();
		obj[i].rotvel = ((double) a)/2048;
		a = myrand();
		obj[i].xvel = cos((double) a); obj[i].yvel = sin((double) a);
		obj[i].shape = ASTSHAPE1;
		obj[i].alive = 1;
	}
	numasts = i;
}

makeenemy(level)	/* Start an enemy ship	*/
	int level;
{	extern Objtype obj[SHIP+1];
	extern int height;
	unsigned char c;

	obj[ENEMY].alive = 1;
	obj[ENEMY].x = 0;
	obj[ENEMY].y = (double) height/4;
	c = myrand(); obj[ENEMY].y += (double) c; /* May put enemy outside window	*/
	obj[ENEMY].xvel = (double) level/2;
	obj[ENEMY].yvel = 0;
}

int nextast()	/* Find next unused asteroid object	*/
{	extern Objtype obj[SHIP+1];
	int i;
	for (i = 0; obj[i].alive; i++);	/* guaranteed to find one	*/
	return i;
}

int collide(i, j)	/* Returns non-zero if i collided with j	*/
	int i, j;
{	extern Objtype obj[SHIP+1];
	extern int shapesize[BULSHAPE+1];
	int diff, xd, yd;
	xd = obj[i].x - obj[j].x;
	yd = obj[i].y - obj[j].y;
	diff = xd*xd + yd*yd;
	return (diff < shapesize[obj[i].shape] + shapesize[obj[j].shape]) ? 1 : 0;
}

blastpair(i, j)		/* Generate random velocity vector v.	*/
	int i, j ;	/* Add v to i, -v to j.			*/
{	extern Objtype obj[SHIP+1];
	unsigned char c;	/* for rand	*/
	double vx, vy;
	c = myrand();
/*	c = 4 - c>>5;	if you need angles from -3 to 4		*/
	c>>2;		/* possibly save some time on sin/cos	*/
	vx = cos((double) c); vy = sin((double) c);
	obj[i].xvel = obj[i].xvel + vx;
	obj[i].yvel = obj[i].yvel + vy;
	obj[j].xvel = obj[j].xvel - vx;
	obj[j].yvel = obj[j].yvel - vy;
	obj[i].rotvel = obj[i].rotvel + .05;
	obj[j].rotvel = obj[j].rotvel - .05;
}

printss(disp, window, gc)	/* Print ships and score	*/
	Display *disp;
	Drawable window;
	GC gc;
{	extern int ships, score, highscore, height,
	oldships, oldscore;
	extern Objtype obj[SHIP+1];	/* to kill ship	*/
	char sstring[30];
	if (score != oldscore)
	{	if (score/10000 > oldscore/10000) ships++;
		if (score/10000 < oldscore/10000)
		{	ships--;
			if (!ships) obj[SHIP].alive = 0;
		}
		if (score > highscore)
		{	highscore = score;
			sprintf(sstring, "High score: %d", highscore);
			XDrawImageString (disp, window, gc, 330, height+letheight, sstring, strlen(sstring));
		}
		sprintf(sstring, "Score: %d      ", score);
		XDrawImageString (disp, window, gc, 110, height+letheight, sstring, strlen(sstring));
		oldscore = score;
	}
	if (ships != oldships)
	{	sprintf(sstring, "Ships: %d ", ships);
		XDrawImageString (disp, window, gc, 0, height+letheight, sstring, strlen(sstring));
		oldships = ships;
	}	
}

upscore(killer, up)	/* Only award score for things the player shot */
	int killer, up;
{	extern int score;
	if (killer != ENEMYBUL && killer != SHIP)
		score = score + up;
}

killast(killer, i)
	int killer, i;		/* i = Asteroid # to kill	*/
{	extern Objtype obj[SHIP+1];
	extern int numasts;
	int k, na, oldna;

	if (obj[i].shape == ASTSHAPE1)
	{	na = nextast();		/* Could put 6 lines in a sub */
		obj[na].x = obj[i].x;
		obj[na].y = obj[i].y;
		obj[na].xvel = obj[i].xvel;
		obj[na].yvel = obj[i].yvel;
		obj[na].alive++;
		obj[na].shape = ASTSHAPE2;
		obj[i].shape = ASTSHAPE2;
		blastpair(i, na);
		numasts = numasts + 1;
		upscore(killer, 25);
	}
	else if (obj[i].shape == ASTSHAPE2)
	{
		for (k = 0; k < 3; k++)
		{	oldna = na;
			na = nextast();
			obj[na].x = obj[i].x;
			obj[na].y = obj[i].y;
			obj[na].xvel = obj[i].xvel;
			obj[na].yvel = obj[i].yvel;
			obj[na].alive++;
			obj[na].shape = ASTSHAPE3;
			if (k == 1) blastpair(oldna,na);
		}
		obj[i].shape = ASTSHAPE3;
		blastpair(na, i);
		numasts = numasts + 3;
		upscore(killer, 50);
	}
	else if (obj[i].shape == ASTSHAPE3)
	{	obj[i].alive = 0; numasts--; upscore(killer, 100);}
	else	/* enemy {ship or bullet}	*/
	{	obj[i].alive = 0; upscore(killer, 500);}
}
moveobjs(crash)
	int *crash;
{	extern Objtype obj[SHIP+1];
	extern int ships;
	int i, j;	/* Indexes	*/
	double *temp;

	for (i = 0; i < LASTSHAPE+1; i++)
		if (obj[i].alive)
		{	temp = &obj[i].x;
			*temp = *temp + obj[i].xvel;
			while (*temp < 0) *temp = *temp + (double) width;
			while (*temp > width) *temp = *temp - (double) width;
			temp = &obj[i].y;
			*temp = *temp + obj[i].yvel;
			while (*temp < 0) *temp = *temp + height;
			while (*temp > height) *temp = *temp - height;
			obj[i].rot = obj[i].rot + obj[i].rotvel;
		}
	for (i = 0; i < FBUL; i++)
	    if (obj[i].alive)
	    {
		if (obj[SHIP].alive && collide(i, SHIP))
		{	*crash = 2;
			ships--; obj[SHIP].alive = 0;
			killast(SHIP, i);
		}
		for (j = ENEMYBUL; j < LASTBUL+1; j++)
			if (obj[j].alive && collide(i, j) && (j != ENEMYBUL || (i != ENEMYBUL && i != ENEMY)))
			{	obj[j].alive = 0;	/* Kill the bullet	*/
				killast(j,i);
			}
	    }
}

fire()
{	extern Objtype obj[SHIP+1];
	extern int width, nextbul;
	double *shiprot, cosrot, sinrot;

	obj[nextbul].alive++;
	shiprot = &obj[SHIP].rot;
	cosrot = cos(*shiprot); sinrot = sin(*shiprot);
	obj[nextbul].x = obj[SHIP].x + 20 * cosrot;
	obj[nextbul].y = obj[SHIP].y + 20 * sinrot;
	obj[nextbul].xvel = obj[SHIP].xvel + 10 * cosrot;
	obj[nextbul].yvel = obj[SHIP].yvel + 10 * sinrot;
	obj[nextbul].rot = *shiprot;
	obj[nextbul].time = width/11;		/* loops before bullet expires	*/
	nextbul++; if (nextbul == LASTBUL+1) nextbul = FBUL;
}

hyper()
{	extern Objtype obj[SHIP+1];
	extern int width, height;
	unsigned char c;
	unsigned int i;

	c = myrand(); i = c; i<<=2;	/* 0 - 1024	*/
	while (i > width) i -= width;
	obj[SHIP].x = (double) i;
	c = myrand(); i = c; i<<=2;	/* 0 - 1024	*/
	while (i > height) i -= height;
	obj[SHIP].y = (double) i;
}

vdraw(disp, window, gc, shape, x, y, rot)
	Display *disp;
	Drawable window;
	GC gc;
	int shape, x, y;
	double rot;
{	int line;
	extern PolarPair shapes[BULSHAPE+1][11];
	extern int numpairs[BULSHAPE+1];
	XPoint figure[20];
	figure[0].x = x; figure[0].y = y;
	for (line=1; line < numpairs[shape]; line++)	/* 2 pairs = 1 line */
	{	figure[line].x  = shapes[shape][line].length *
			cos(shapes[shape][line].angle + rot);
		figure[line].y  = shapes[shape][line].length *
			sin(shapes[shape][line].angle + rot);
	}
	XDrawLines (disp, window, gc, figure, numpairs[shape], CoordModePrevious);
}

main(argc, argv)
	int argc;
	char **argv;
{	Display *disp;
	Pixmap pixmap;
	Window window;
	GC gc, pmgc;
	Font font;
	XEvent event;
	KeySym key;
	XSizeHints hint;
	extern int width, height;
	int screen, depth;
	char text[10];
	unsigned long fg, bg;

	int level, crashed, flashon, len, pause = 0, delay = 64,
	enemycount,
	i;	/* index for drawing objs, counting bullets */
	unsigned char c;	/* for rand	*/
	double *temp, dx, dy, dist;
	extern Objtype obj[SHIP+1];
	extern int numasts, ships, score, oldscore, oldships;

	disp = XOpenDisplay(0);
	screen = DefaultScreen(disp);
	bg = BlackPixel(disp, screen);
	fg = WhitePixel(disp, screen);
	hint.x = 150; hint.y = 200; hint.width = 550; hint.height = 550;
	hint.flags = PPosition | PSize;
	width = hint.width; height = hint.height-letheight-1;
	depth = DefaultDepth (disp, screen);
	window = XCreateSimpleWindow (disp, DefaultRootWindow(disp),
		hint.x, hint.y, hint.width, hint.height, 5, fg, bg);
	pixmap = XCreatePixmap (disp, window, width, height, depth);
	XSetStandardProperties (disp, window, "asteroids", "asteroids", None,
		argv, argc, &hint);
	gc = XCreateGC (disp, window, 0, 0);
	XSetGraphicsExposures(disp, gc, 0);	/* IMPORTANT!  If you do not
		specifically ask not to get Expose events, every XCopyArea
		will generate one, & the event queue will fill up.	*/
	font = XLoadFont(disp, "10x20\0");	/* If you don't have this
		font, try replacing it with 9x15\0	*/
	XSetFont(disp, gc, font);
	pmgc = XCreateGC (disp, window, 0, 0);
	XSetBackground (disp, gc, bg);
	XSetForeground (disp, gc, fg);
	XSetForeground (disp, pmgc, bg);	/* fg of pixmap is bg of window	*/
	XSelectInput (disp, window, KeyPressMask | StructureNotifyMask);
	XMapRaised (disp, window);

/*	srand();	*/
	initasts();
Newgame:
	ships = 3;
	score = 0;
	printss(disp, window, gc);
	XSync(disp, 0);		/* Should let user see ships & score
				   but apparently XSync doesn't work	*/
	for (level = 0; ;)
	{	if (level < 8) level++;
		makeasts (level);
Newship:	obj[SHIP].alive = 1;
		obj[SHIP].x = width/2;
		obj[SHIP].y = height/2;
		obj[SHIP].xvel = 0;
		obj[SHIP].yvel = 0;
		obj[SHIP].rot = 3*pi/2;
		obj[SHIP].rotvel = 0;
		crashed = 0; flashon = 0; enemycount = 20;
		while (numasts)
		{	for (i = FBUL; i < LASTBUL+1; i++)	/* Bullet timer	*/
				if (obj[i].alive)
				{	obj[i].time--;
					if (!obj[i].time) obj[i].alive = 0; /* Not --! */
				}
			if (XEventsQueued(disp, QueuedAfterReading))
			{    XNextEvent(disp, &event);
			     switch (event.type)
			     {	case MappingNotify:
					XRefreshKeyboardMapping (&event);
					break;
				case ConfigureNotify:
					width = event.xconfigure.width;
					height = event.xconfigure.height-letheight-1;
					XFreePixmap (disp, pixmap);
					pixmap = XCreatePixmap (disp, window, width, height, depth);
					break;
				case KeyPress:
					len = XLookupString (&event, text, 10, &key, 0);
					if (len == 1) switch (text[0])
					{	case 'e':
							obj[SHIP].rot = obj[SHIP].rot - .2; break;
						case 'r':
							obj[SHIP].rot = obj[SHIP].rot + .2; break;
						case 'd':
							obj[SHIP].rotvel = obj[SHIP].rotvel - .02; break;
						case 'f':
							obj[SHIP].rotvel = obj[SHIP].rotvel + .02; break;
						case ';':	/* thrust	*/
							temp = &obj[SHIP].xvel;
							*temp = *temp +
								cos(obj[SHIP].rot);
							temp = &obj[SHIP].yvel;
							*temp = *temp +
								sin(obj[SHIP].rot);
							break;
						case '\'':
							if (obj[SHIP].alive) fire(); break;
						case ' ':
							if (obj[SHIP].alive)
							{	hyper(); flashon = 1;
/*								XSetForeground (disp, gc, bg);
	If you set the fg black, & print the highscore, it will effectively erase it.	*/
								XSetForeground (disp, pmgc, fg);
							}
							break;
						case '<':	/* decrease delay	*/
							if (delay > 1) delay >>=1; break;
						case '>':	/* increase delay	*/
							delay <<=1; break;
						case 'p':	/* pause	*/
							pause = 1 - pause; break;
						case 'q':	/* quit		*/
							goto End;
						case 's':	/* start new ship	*/
							if (!obj[SHIP].alive)
								if (!ships) goto Newgame;
								else goto Newship;
							break;
					}
/*					break;		*/
			}    }
			if (!pause)
			{	moveobjs(&crashed);
				/*	Draw objects	*/
				if (crashed == 2)
				{	crashed--; flashon++; 
/*					XSetForeground (disp, gc, bg);
*/
					XSetForeground (disp, pmgc, fg);
				}
				for (i = 0; i <= LASTSHAPE; i++)
					if (obj[i].alive)
						vdraw(disp, pixmap, gc, obj[i].shape,
							(int) obj[i].x, (int) obj[i].y, obj[i].rot);
				/* update display:	*/
				if (ships) score--;	/* timer effect	*/
				printss(disp, window, gc);
				XCopyArea(disp, pixmap, window, gc, 0, 0, width, height, 0, 0);
				XFillRectangle (disp, pixmap, pmgc, 0, 0, width, height); /* erase pixmap; */
				if (flashon)
				{	flashon--;
/*					XSetForeground (disp, gc, fg);
*/
					XSetForeground (disp, pmgc, bg);
				}
				XSync(disp, 0);
				c = myrand()>>8;
				if (!obj[ENEMY].alive)
				{	if (c < level)
					{	c = myrand();
						if (c < level * 10) makeenemy(level);
				}	}
				else
					obj[ENEMY].yvel += (c>128+6*obj[ENEMY].yvel) ? .5 : -.5;
				enemycount--; if (!enemycount)
				{	enemycount = 100;
					if (obj[ENEMY].alive)
					{	obj[ENEMYBUL].alive++;
						obj[ENEMYBUL].x = obj[ENEMY].x;
						obj[ENEMYBUL].y = obj[ENEMY].y;
						dx = obj[SHIP].x - obj[ENEMY].x;
						dy = obj[SHIP].y - obj[ENEMY].y;
						dist = sqrt(square(dx) + square(dy));
						obj[ENEMYBUL].xvel = 3*dx/dist;
						obj[ENEMYBUL].yvel = 3*dy/dist;
					}
					else	obj[ENEMYBUL].alive = 0;
				}
				for (i = 0; i < delay; i++);
			}
		}
	}
End:	printf("\nYour high score was %d\n", highscore);
	XFreeGC (disp, gc);
	XFreeGC (disp, pmgc);
	XFreePixmap (disp, pixmap);
	XDestroyWindow (disp, window);
	XCloseDisplay (disp);
	exit(0);
}

