
/*
 * Copyright (c) 1991    Jon Wesener 
 * Gaming Software
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation.  No representations are made about the
 * suitability of this software for any purpose.  It is
 * provided "as is" without express or implied warranty.
 *
 */

#ident	"@(#)client.c	1.19 :/usr/key/jojow/X/src/guts/SCCS/s.client.c 3/19/91 10:15:36 "

/* client.c - tank client */

#include <stdio.h>
#include <rpc/rpc.h>
#include "tankmsg.h"

#include <signal.h>
#include <string.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "client.h"

CLIENT *cl;
char *server = NULL;
char msgbuf[256];


Display *display;
Window  root, window;
int     screen, depth, black, white;
GC      copyGC;
KeySym  last_key;
int     ascent, descent;
Font    font;

long    frame_delay = 10000L;

long	lastact = 0;

blob_t	blobs[MAZE_XL][MAZE_YL];
blob_t	*Bvalid;
stat_t	stats[MAXSTAT];
stat_t	estat;

int		plyrid;
char *sprintf();

main(ac, av)
	int     ac;
	char   *av[];

{
	XEvent  event;
	unsigned long event_mask;
	int     dummy;
	XCharStruct chars;
	char	*getenv();
	void	ignoresig(), entrysig(), hup();


	/* open the display */
	display = XOpenDisplay(NULL);
	screen = DefaultScreen(display);
	root = DefaultRootWindow(display);
	depth = DefaultDepth(display, screen);
	black = BlackPixel(display, screen);
	white = WhitePixel(display, screen);

	/* get a font */
	font = XLoadFont(display, "fixed");
	XQueryTextExtents(display, font, "000000", 6, &dummy,
			  &ascent, &descent, &chars);

	create_GCs();
	create_cells();
	create_window(ac, av);

	XSetBackground(display,copyGC,black);
	XSetForeground(display,copyGC,white);

	server = getenv("STANKSERVER");

	/* the command line overrides the environment variable */
	if( ac == 2 )
		server = av[1];

	if( !server ) {
		(void)fprintf(stderr,"usage: %s host\n",av[0]);
		exit(1);
	}

	/* catch various signals */
	ignoresig();

	/* create client handle */
	cl = clnt_create(server,MESSAGEPROG,MESSAGEVERS,"tcp");
	if( cl == NULL ) {
		/* couldn't establish connection */
		clnt_pcreateerror(server);
		exit(1);
	}

	if( enter_game() ) {
		(void)printf("game is full, try again later.\n");
		exit(1);
	}

	(void)sprintf(msgbuf,"i%c ",'0'+plyrid);

	/* select the event mask for the window */
	event_mask = ExposureMask | KeyPressMask;
	XSelectInput(display, window, event_mask);

	init_blobs();

	/* display the window */
	XMapWindow(display, window);
	while (True) {
		XNextEvent(display, &event);
		if (event.xany.window != window)
			continue;
		if (event.type == Expose)
			break;
	}

	/* go to it */
	sit_n_spin();

	exit_game();

	entrysig();

	/* exit */
	do_exit();
}


int Done = 0;

/*
 * The following function contains the main game loop.
 */
sit_n_spin()
{
	char    c_buf;
	XComposeStatus status;
	XEvent  event;


	draw_screen();

	if( update_pos() )
		update_screen();

	XSync(display, False);

	while( !Done ) {
		while (QLength(display) > 0) {
			XNextEvent(display, &event);
			if (event.xany.window != window)
				continue;
			switch (event.type) {
			case KeyPress:
				lastact = 0;
				XLookupString(&event, &c_buf, 1, &last_key, &status);
				if (last_key == XK_q)
					Done = 1;
				break;
			case Expose:
				draw_screen();
				break;
			default:
				break;
			}
		}

		if( update_pos() )
			update_screen();

		/*-- Synchronization And Delay ------------------------------*/

		XSync(display, False);

		/* has the player been idle too long? */
		if( ++lastact > LASTACT) {
			(void)fprintf(stderr,"player dropped due to inactivity\n");
			Done = 1;
		}
		usleep((unsigned)frame_delay);
	}
}


do_exit()
{
	XUnmapWindow(display, window);
	XFlush(display);
	XCloseDisplay(display);
	exit(1);
}

create_window(ac, av)
	int     ac;
	char   *av[];

{
	XSizeHints hints;
	XWMHints xwmh;
	char   *name = "tank";

	window = XCreateSimpleWindow(display, root, 0, 0, WIN_XL,
				     WIN_YL, 1, white, black);

	hints.flags = PSize | PMinSize | PMaxSize;
	hints.width = hints.min_width = hints.max_width = WIN_XL;
	hints.height = hints.min_height =
		hints.max_height = WIN_YL;

	XSetStandardProperties(display, window, name, name, NULL,
			       av, ac, &hints);

	xwmh.input = True;
	xwmh.initial_state = NormalState;
	xwmh.flags = InputHint | StateHint;
	XSetWMHints(display, window, &xwmh);
}


create_GCs()
{
	XGCValues gcv;
	unsigned long mask;

	mask = GCForeground | GCBackground | GCFunction |
		GCFont | GCGraphicsExposures;

	/* context for copying; used for animation */
	gcv.foreground = black;
	gcv.background = white;
	gcv.font = font;
	gcv.function = GXcopy;
	gcv.graphics_exposures = False;
	copyGC = XCreateGC(display, root, mask, &gcv);
}

update_pos()
{
	char **result;
	char *move;
	char *scr, *dostats();

	switch ((int)last_key) {
		case XK_Left:
		case XK_h:
			msgbuf[2] = 'h';
			break;
		case XK_Down:
		case XK_j:
			msgbuf[2] = 'j';
			break;
		case XK_Up:
		case XK_k:
			msgbuf[2] = 'k';
			break;
		case XK_Right:
		case XK_l:
			msgbuf[2] = 'l';
			break;
		case XK_f:
		case XK_space:
			msgbuf[2] = 'f';
			break;
		case XK_c:
			msgbuf[2] = 'c';
			break;
		case XK_d:
			msgbuf[2] = 'd';
			break;
		case XK_s:
			msgbuf[2] = 's';
			break;
		default:
			msgbuf[2] = '\0';
			break;
	}

	last_key = 0;

	move = msgbuf;

	/* call remote procedure */
	result = getpos_1(&move,cl);

	if( result == NULL ) {
		/* an error occurred while calling the server */
		clnt_perror(cl,server);
		exit(1);
	}

	/* screen info? */
	scr = *result;
	if( ! (*scr) )
		return 0;
	
	/* check for a terminate message */
	if( *scr == 'T' ) {
		(void)printf("get_pos: Terminate\n");
		exit( -1 );
	}

	if( *scr == 'F' ) {
#if DEBUG
	(void)printf("FORCE-----\n");
#endif /* DEBUG */
		return 1;
	}

	/* Reset the screen map */

#if DEBUG
	(void)printf("P---------\n");
#endif /* DEBUG */
	while( *scr ) {
		int x, y, btype, id, dir;
		blob_t *bp;

		btype = *scr++;

		/* special stat processing */
		if( (btype == 'E') || (btype == 'S') ) {
			scr = dostats(btype,scr);
			continue;
		}

		x = *scr++ & 0x3f;
		y = *scr++ & 0x3f;
		id = *scr++ & 0x3f; 
		dir = *scr++ & 0x3f;

		bp = &blobs[x][y];

		/* reset screen item */
		if( (bp->b_id != id) || (bp->b_dir != dir) || (bp->b_type != btype) ||
			!(bp->b_state & BVALID) ) {

			bp->b_type = btype;
			bp->b_id = id;
			bp->b_dir = dir;

			/* if not already valid, link it in */
			if( !(bp->b_state & BVALID) ) {
				if( bp->b_next = Bvalid )
					Bvalid->b_last = bp;
				bp->b_last = NULL;
				Bvalid = bp;
			}

#if DEBUG
			(void)printf("R%c x= %d y = %d id = %d d = %d\n",bp->b_type,
				bp->b_x,bp->b_y,bp->b_id,bp->b_dir);
#endif /* DEBUG */
			bp->b_state = BVALID | BDRAW;
		}
		else {
#if DEBUG
			(void)printf("N%c x= %d y = %d id = %d d = %d\n",bp->b_type,
				bp->b_x,bp->b_y,bp->b_id,bp->b_dir);
#endif /* DEBUG */
			bp->b_state = BVALID | BNODRAW;
		}
	}

	return 1;
}


char *
dostats(f,scr)
	char f;
	char *scr;
{
	int cc, r, i;
	char btmp[MAXSTRLEN], *bp;


	/* energy stat */
	if( f == 'E' ) {
		for( bp = btmp, cc = 0 ; *scr != '#' ; cc++, scr++ )
			if( cc < MAXSTRLEN )
				*bp++ = *scr;

		if( cc < MAXSTRLEN )
			*bp = '\0';
		else
			btmp[MAXSTRLEN] = '\0';

		if( strcmp(btmp, estat.st_str) ) {
			(void)strcpy(estat.st_str,btmp);
			estat.st_state = BVALID | BDRAW;
		}
		else
			estat.st_state = BVALID | BNODRAW;

		scr++;
		return scr;
	}

	for( r = 0 ; r < MAXSTAT ; r++ )
		if( stats[r].st_state & BVALID )
			stats[r].st_state = BVALID | BERASE;
	/* player stats */
	while( *scr != '$') {

		r = (*scr++ & 0x3f) * 2;

		for( i = 0 ; i < 2 ; i++,  r++ ) {
			for( bp = btmp, cc = 0 ; *scr != '#' ; cc++, scr++ )
				if( cc < MAXSTRLEN )
					*bp++ = *scr;

			if( cc < MAXSTRLEN )
				*bp = '\0';
			else
				btmp[MAXSTRLEN] = '\0';
			scr++;

			if( strcmp(btmp, stats[r].st_str) ) {
				(void)strcpy(stats[r].st_str,btmp);
				stats[r].st_state = BVALID | BDRAW;
			}
			else
				stats[r].st_state = BVALID | BNODRAW;
		}

	}
	scr++;
	return scr;
}


update_screen()
{
	blob_t *bp;
	int x;


#if DEBUG
	(void)printf("S---------\n");
#endif /* DEBUG */
	/* walk down the list */
	for( bp = Bvalid ; bp ; bp = bp->b_next ) {

		/* erase object */
		if( bp->b_state == BVALID ) {

#if NOTDEF
			XFillRectangle(display, window, copyGC, bp->b_x, bp->b_y, 
				TANK_LEN, TANK_LEN);
#endif /* NOTDEF */
			XCopyArea(display,xcell,window,copyGC,0,0,TANK_LEN,TANK_LEN,
				bp->b_x*TANK_LEN,bp->b_y*TANK_LEN);

			if( bp->b_last )
				bp->b_last->b_next = bp->b_next;
			else
				Bvalid = bp->b_next;

			if( bp->b_next )
				bp->b_next->b_last = bp->b_last;

			bp->b_state = 0;
#if DEBUG
			(void)printf("x%c x= %d y = %d id = %d d = %d\n",bp->b_type,
				bp->b_x,bp->b_y,bp->b_id,bp->b_dir);
#endif DEBUG

			continue;
		}
			

		/* redraw item? */
		if( bp->b_state & BDRAW ) {
			Pixmap	pm;

			switch( (int)bp->b_type ) {
				case 't' : pm = tcells[bp->b_id][bp->b_dir];
					break;
				case 'h' : pm = hcells[bp->b_dir];
					break;
				case 's' : pm = scells[bp->b_dir];
					break;
				case 'e' : pm = ecells[bp->b_dir];
					break;
				case 'b' : pm = bcells[bp->b_dir];
					break;
				case 'm' : pm = mcell;
					break;
				case 'p' : pm = pcell;
					break;
				default : (void)printf("update_screen: invalid type '%c'\n",
					bp->b_type);
					break;
			}

#if DEBUG
			(void)printf("D%c x= %d y = %d id = %d d = %d\n",bp->b_type,
				bp->b_x,bp->b_y,bp->b_id,bp->b_dir);
#endif /* DEBUG */
			XCopyArea(display,pm,window,copyGC,0,0,TANK_LEN,TANK_LEN,
				bp->b_x*TANK_LEN,bp->b_y*TANK_LEN);
		}

		bp->b_state = BVALID;
	}

	/* draw stats */
	if( estat.st_state & BVALID ) {
		if( estat.st_state & BNODRAW )
			estat.st_state = BVALID | BERASE;
		else {
			if( estat.st_state & BERASE ) {
				XDrawImageString(display,window,copyGC,ESTATX,ESTATY,
					"                    ",20);
				estat.st_state = 0;
				estat.st_str[0] = '\0';
			}
			else {
				XDrawImageString(display,window,copyGC,ESTATX,ESTATY,
					estat.st_str,strlen(estat.st_str));
				estat.st_state = BVALID | BERASE;
			}
		}
	}

	for( x = 0 ; x < MAXSTAT ; x++ ) {
		if( !(stats[x].st_state & BVALID) )
			continue;

		if( stats[x].st_state & BNODRAW ) {
			stats[x].st_state = BVALID;
			continue;
		}

		/* identify tank? */
		if( ! (x & 1) ) {
			if( stats[x].st_state & BERASE )
				XCopyArea(display,xcell,window,copyGC,0,0,TANK_LEN,TANK_LEN,
					STATIDX,STATIDY(x));
			else
				XCopyArea(display,tcells[(x>>1) & 3][0],window,copyGC,0,0,
					TANK_LEN,TANK_LEN,STATIDX,STATIDY(x));
		}

		/* stat text */
		if( stats[x].st_state & BERASE ) {
			XDrawImageString(display,window,copyGC,STATX,STATY(x),
				"                    ",20);
			stats[x].st_state = 0;
			stats[x].st_str[0] = '\0';
		}
		else {
			XDrawImageString(display,window,copyGC,STATX,STATY(x),
				stats[x].st_str,strlen(stats[x].st_str));
			stats[x].st_state = BVALID;
		}
	}
}


draw_screen()
{
	blob_t *bp;
	int x, y;
	Pixmap pm;


#if DEBUG
	(void)printf("DS--------\n");
#endif /* DEBUG */
	XClearWindow(display,window);
	for( x = 0 ; x < MAZE_XL ; x++ )
		for( y = 0 ; y < MAZE_YL ; y++ ) {
			
			bp = &blobs[x][y];

			if( !(bp->b_state & BVALID) ) {
				pm = xcell;
				bp->b_type = '-';
			}
			else {
				switch( (int)bp->b_type) {
					case 't' : pm = tcells[bp->b_id][bp->b_dir];
						break;
					case 'h' : pm = hcells[bp->b_dir];
						break;
					case 's' : pm = scells[bp->b_dir];
						break;
					case 'e' : pm = ecells[bp->b_dir];
						break;
					case 'm' : pm = mcell;
						break;
					case 'p' : pm = pcell;
						break;
					case 'w' : pm = wcells[bp->b_dir];
						break;
					case 'b' : pm = bcells[bp->b_dir];
						break;
					default : 
						continue;
				}
			}

#if DEBUG
			(void)printf("D%c x= %d y = %d id = %d d = %d\n",bp->b_type,
				bp->b_x,bp->b_y,bp->b_id,bp->b_dir);
#endif /* DEBUG */
			XCopyArea(display,pm,window,copyGC,0,0,TANK_LEN,TANK_LEN,
				bp->b_x*TANK_LEN,bp->b_y*TANK_LEN);
		}

	/* draw stats */
	if( estat.st_state & BVALID )
		XDrawImageString(display,window,copyGC,ESTATX,ESTATY,
			estat.st_str,strlen(estat.st_str));

	for( x = 0 ; x < MAXSTAT ; x++ ) {
		if( !(stats[x].st_state & BVALID) )
			continue;

		/* identifying tank? */
		if( ! (x & 1) ) {
			XCopyArea(display,tcells[(x>>1)& 3][0],window,copyGC,0,0,
				TANK_LEN,TANK_LEN,STATIDX,STATIDY(x));
		}

		/* stat text */
		XDrawImageString(display,window,copyGC,STATX,STATY(x),
			stats[x].st_str,strlen(stats[x].st_str));
	}
}

enter_game()
{
	char **result, *res;
	char *uid, *cuserid();
	char uidbuf[256];
	char hbuf[256];
	char ubuf[256];
	
	if( !cuserid(ubuf) )
		(void)strcpy(ubuf,"anonymous");

	if( gethostname(hbuf,256) )
		(void)strcpy(hbuf,"somewhere");

	(void)sprintf(uidbuf,"%s@%s",ubuf,hbuf);

	uid = uidbuf;

	/* call remote procedure */
	result = tenter_1(&uid,cl);

	if( result == NULL ) {
		/* an error occurred while calling the server */
		clnt_perror(cl,server);
		exit(1);
	}

	if( !strcmp(*result,"iF") )
		return 1;

	/* NOTE: this done like this
	 * to avoid a core dump, very very odd
	 */
	res = *result;

	plyrid = res[1] - '0';

	return 0;
}

exit_game()
{
	char *move;
	char **msg;
	
	move = "i0";

	move[1] = '0'+plyrid;

	/* call remote procedure */
	msg = texit_1(&move,cl);

	if( *msg ) {
		(void)printf("%s\n",*msg);
	}
}


char *
get_maze()
{
	int ret;
	char **result;

	/* call remote procedure */
	result = getmaze_1(&ret,cl);

	if( result == NULL ) {
		/* an error occurred while calling the server */
		clnt_perror(cl,server);
		exit(1);
	}

	return *result;
}


init_blobs()
{
	int x,y,off;
	char *maze;
	blob_t *bp;


	/* clear status */
	estat.st_state = 0;
	estat.st_str[0] = '\0';
	for(x = 0 ; x < MAXSTAT ; x++ ) {
		stats[x].st_state = 0;
		stats[x].st_str[0] ='\0';
	}

	/* setup the maze */
	maze = get_maze();

	off = 0;
	for( y = 0 ; y < MAZE_YL ; y++ )
		for( x = 0 ; x < MAZE_XL ; x++ ) {
			bp = & blobs[x][y];
			bp->b_x = x;
			bp->b_y = y;

			if( maze[off] == 'w') {
				bp->b_state = BVALID;
				bp->b_type = 'w';
				bp->b_dir = 0;
				/* check NORTH */
				if( y && (maze[off - MAZE_YL] == 'w') )
					bp->b_dir |= 0x1;
				/* check EAST */
				if( (x < MAZE_YL - 1) && (maze[off+1] == 'w') )
					bp->b_dir |= 0x2;
				if( (y < MAZE_YL - 1) && (maze[off + MAZE_YL] == 'w') )
					bp->b_dir |= 0x4;
				if( x && (maze[off - 1] == 'w') )
					bp->b_dir |= 0x8;
			}
			else
				bp->b_state = 0;

			off++;
		}

	Bvalid = NULL;
}


static void (*entryint)(), (*entryquit)();

void faterr();

void
ignoresig()
{
	static first = 1;

	if( first ) {
		first = 0;
		entryint = signal(SIGINT,SIG_IGN);
		entryquit = signal(SIGQUIT,SIG_IGN);
		if( (entryint == BADSIG) || (entryquit == BADSIG) )
			faterr("signal");
		if( signal(SIGHUP,hup) == BADSIG )
			faterr("signal hup");
	}
	else if( (signal(SIGINT,SIG_IGN) == BADSIG) ||
		(signal(SIGQUIT,SIG_IGN) == BADSIG) )
		faterr("signal");
}

void
entrysig()
{
	if( (signal(SIGINT,entryint) == BADSIG) ||
		(signal(SIGQUIT,entryquit) == BADSIG) )
		faterr("signal");
}

void
hup()
{
	Done = 1;
}

void
faterr(msg)
	char *msg;
{
	(void)printf("fatal error: %s\n",msg);
	exit(1);
}


/* Lint Output
*/
