/* xtmines: game where you try to cross a minefield */
/* play.c: */
/* Written by Timothy Tsai  April 13, 1992 */

#include "xtmines.h"

/* This is a kludge for systems without usleep() */
#ifdef NOUSLEEP
#define USLEEP_LOOPS	15000
void usleep(usecs)
unsigned usecs;
{
	int	i;
	for (i=0;i<USLEEP_LOOPS;i++);
}
#endif

void PrintStr(window,str)
windtype window;
char *str;
{
	unsigned int	ww,wh;	/* get width and height of window */
	unsigned int	wbw;	/*     border width               */
	Font		font;
	XFontStruct	*fs;
	Window		a;
	int		b,c;	/* dummy vars */
	unsigned int	e;	/* dummy vars */
	GC		gc;

	gc = (window==status) ? gcs : (window==time_left) ? gct : gcm;
	XSetForeground(disp,gc,color[font_color].pixel);
	XSetBackground(disp,gc,color[background_color].pixel);
	XGetGeometry(disp,wind[window],&a,&b,&c,&ww,&wh,&wbw,&e);
	fs = (window==status) ? fontsstruct : 
	     (window==time_left) ? fonttstruct : fontmstruct;
	XDrawImageString(disp,wind[window],gc,
		((ww-2*wbw)-XTextWidth(fs,str,strlen(str)))/2,
		(wh - fs->ascent - fs->descent)/2 +
			fs->ascent,str,strlen(str));
	XFlush(disp);
}

void InvPrintStr(window,str)
windtype window;
char *str;
{
	unsigned int	ww,wh;	/* get width and height of window */
	unsigned int	wbw;	/*     border width               */
	Font		font;
	XFontStruct	*fs;
	Window		a;
	int		b,c;	/* dummy vars */
	unsigned int	e;	/* dummy vars */
	GC		gc;

	gc = (window==status) ? gcs : (window==time_left) ? gct : gcm;
	XSetForeground(disp,gc,color[background_color].pixel);
	XSetBackground(disp,gc,color[font_color].pixel);
	XGetGeometry(disp,wind[window],&a,&b,&c,&ww,&wh,&wbw,&e);
	fs = (window==status) ? fontsstruct : 
	     (window==time_left) ? fonttstruct : fontmstruct;
	XDrawImageString(disp,wind[window],gc,
		((ww-2*wbw)-XTextWidth(fs,str,strlen(str)))/2,
		(wh - fs->ascent - fs->descent)/2 +
			fs->ascent,str,strlen(str));
	XFlush(disp);
}

void WindPrint(window,str)
windtype window;
char *str;
{
	XClearWindow(disp,wind[window]);
	PrintStr(window,str);
}

char *num_rank_to_words(num_rank)
ranktype num_rank;
{
	switch (num_rank) {
		case grunt	: return ("grunt"); break;
		case corporal	: return ("corporal"); break;
		case lieutenant	: return ("lieutenant"); break;
		case captain	: return ("captain"); break;
		case mmajor	: return ("major"); break;
		case general	: return ("general"); break;
		case president	: return ("president"); break;
		case king	: return ("king"); break;
		case emperor	: return ("emperor"); break;
		case angel	: return ("angel"); break;
		case god	: return ("god"); break;
		default		: fprintf(stderr,"Error: Illegal rank\n");
				  exit(2);
	}
}

char	last_status_str[MAXSTRLEN];
void print_status(str)
char *str;
{

	strcpy(last_status_str,str);
	WindPrint(status,str);
}

char	*clock_time_str(time)
int time;
{
	int		h,m,s;
	static char	str[MAXSTRLEN];

	h = time/3600;
	m = (time-(h*3600))/60;
	s = (time-(h*3600)-(m*60));
	if (h != 0)
		sprintf(str,"%d:%02d:%02d",h,m,s);
	else if (m != 0)
		sprintf(str,"%d:%02d",m,s);
	else
		sprintf(str,"%d",s);
	return (str);
}

void print_display()
{
	char		str[MAXSTRLEN];

	sprintf(str,"Bombs: %d",num_bombs_left);
	WindPrint(bombs_left,str);

	sprintf(str,"Bonus: %d",num_bonus);
	WindPrint(bonus_wind,str);

	sprintf(str,"Time: %s",clock_time_str(num_time_left));
	WindPrint(time_left,str);

	sprintf(str,"Score: %d",num_score);
	WindPrint(score,str);

	sprintf(str,"Grenades: %d",num_grenades_left);
	WindPrint(grenades_left,str);

	sprintf(str,"Rank: %s",num_rank_to_words(level));
	WindPrint(rank,str);

	WindPrint(quit_wind,"Quit");

	if (used_showfig)
		sprintf(str,"Show = *%s", (show==sh_man) ? "man" : "fig");
	else
		sprintf(str,"Show = %s", (show==sh_man) ? "man" : "fig");
	WindPrint(show_wind,str);

	WindPrint(tgrenade_wind,"Throw Grenade");

	WindPrint(giveup_wind,"Surrender");

	sprintf(str,"Automark = %s",automark ? "on" : "off");
	WindPrint(automark_wind,str);

	sprintf(str,"Lives: %d",num_lives);
	WindPrint(lives_left,str);

	WindPrint(pause_wind,"Pause");

	if (used_sanity)
		sprintf(str,"Sanity = *%s",sanity ? "on" : "off");
	else
		sprintf(str,"Sanity = %s",sanity ? "on" : "off");
	WindPrint(sanitycheck_wind,str);

	if (used_eautomark)
		sprintf(str,"ExtAmark = *%s",extended_automark ? "on" : "off");
	else
		sprintf(str,"ExtAmark = %s",extended_automark ? "on" : "off");
	WindPrint(eautomark_wind,str);

	WindPrint(refresh_wind,"Refresh");

	WindPrint(status,last_status_str);

	XFlush(disp);
}

Pixmap bitmap[NUMBITMAPS];
int bmwidth[NUMBITMAPS];
int bmheight[NUMBITMAPS];
/* This function will draw the bitmap indicated by bm in the field at the */
/*   position (vx,vy).  If show==sh_fig, then a figure will be drawn      */
/*   instead of the man bitmap.  The figure is the number of surrounding  */
/*   bombs.  The font for figures is the same as the font for the status. */
void draw(bm,vx,vy)
bmtype bm;
int vx,vy;
{
	int	x,y;
	int	numbombs;
	char	str[2];

	setxy(vx,vy,&x,&y);
	if ((bm==bm_man) && (show==sh_fig)) {
	   clear(vx,vy);
	   numbombs = bomb_status(vx,vy);
	   str[0] = '0' + numbombs; str[1]=NULL;
	   XDrawString(disp,field,gcs,
		1+vx*(man_width+1)+(man_width-fontshoww)/2,
		1+vy*(man_height+1)+(man_height-fontshowa)/2+fontshowa,
		str,strlen(str));
	}
	else
	   XCopyPlane(disp,bitmap[bm],field,gcm,0,0,bmwidth[bm],bmheight[bm],
		x+1,y+1,1);
	XFlush(disp);
}

void clear(vx,vy)
int vx,vy;
{
	int	x,y;

	setxy(vx,vy,&x,&y);
	XClearArea(disp,field,x+1,y+1,bmwidth[bm_man],bmwidth[bm_safe],FALSE);
}

fieldstruct FIELD[NUMCOLS][NUMROWS];
void draw_field()
{
	int	r,c;
	double	rowsep,colsep;

	/* draw vertical lines */
	colsep = ((double)(w-2*bw)/(double)NUMCOLS);
	for (c=0; c<NUMCOLS; c++) {
		XSetForeground(disp,gcm,color[field_color].pixel);
		XSetBackground(disp,gcm,color[background_color].pixel);
		XDrawLine(disp,field,gcm,(int)(c*colsep),0,(int)(c*colsep),fh);
	}

	/* draw horizontal lines */
	rowsep = ((double)fh/(double)NUMROWS);
	for (r=0; r<NUMROWS; r++) {
		XSetForeground(disp,gcm,color[field_color].pixel);
		XSetBackground(disp,gcm,color[background_color].pixel);
		XDrawLine(disp,field,gcm,0,(int)(r*rowsep),w,(int)(r*rowsep));
	}

	/* draw contents */
	for (c=0;c<NUMCOLS;c++)
		for (r=0;r<NUMROWS;r++)
			if (FIELD[c][r].m==fm_safe)
				draw(bm_safe,c,r);
			else if (FIELD[c][r].m==fm_bomb)
				draw(bm_bombmark,c,r);
			else switch (FIELD[c][r].c) {
				case fc_empty	: break;
				case fc_man	: draw(bm_man,c,r); break;
				case fc_bomb	: if (dead)
							draw(bm_bomb,c,r);
						  break;
				case fc_tombstone
						: draw(bm_tombstone,c,r); break;
				case fc_goal	: draw(bm_goal,c,r); break;
				case fc_trail	: draw(bm_trail,c,r); break;
				default		: fprintf(stderr,
					"Error: Illegal FIELD content\n");
					exit(1);
			}
	XFlush(disp);
}

/* remove all Exposure events from the events queue; return the number
   of actual events removed */
int remove_expose_events()
{
	int	r=0;		/* number of events removed so far */
	XEvent	event;		/* not used */

	while (XCheckMaskEvent(disp,ExposureMask,&event))
		r++;
	return (r);
}

void refresh()
{
	XEvent		event;
	int		done=FALSE;	/* finished dequeuing all exposes? */

	print_display();
	draw_field();
	draw(bm_man,manvx,manvy);
	XFlush(disp);

	/* now dequeue all immediately succeeding exposure events */
	/*    to prevent multiple, useless refreshes              */
	remove_expose_events();
}

/* set up windows for pausing;  the field should be blanked to prevent
   cheating;  the time should be adjusted so there is no penalty for pausing;
*/
int do_pause()
{
	int		time_at_start_of_pause;

	char		str[MAXSTRLEN];
	unsigned int	ww,wh;	/* get width and height of window */
	unsigned int	wbw;	/*     border width               */
	XFontStruct	*fs;
	Window		a;
	int		b,c;	/* dummy vars */
	unsigned int	e;	/* dummy vars */

	int			wait=TRUE;
	XEvent			event;
	XButtonPressedEvent	*eventbp;
	XKeyPressedEvent	*eventkp;
	KeySym			ksym;
	char			s;
	int			done;

	WindPrint(pause_wind,"Continue");
	time_at_start_of_pause = time(0);

	/* blank out field and write "P-A-U-S-E" message on field */
	XClearWindow(disp,field);
	strcpy(str,"P-A-U-S-E");
	XSetForeground(disp,gcs,color[font_color].pixel);
	XSetBackground(disp,gcs,color[background_color].pixel);
	XGetGeometry(disp,field,&a,&b,&c,&ww,&wh,&wbw,&e);
	fs = fontsstruct;
	XDrawImageString(disp,field,gcs,
		((ww-2*wbw)-XTextWidth(fs,str,strlen(str)))/2,
		(wh - fs->ascent - fs->descent)/2 +
			fs->ascent,str,strlen(str));
	XFlush(disp);

	/* Wait for keypress or button click to signal done with pause */
	while (wait == TRUE) {
	   while (!XPending(disp))
		usleep(XPENDING_UDELAY);
	   XNextEvent(disp,&event);
	   if (event.type==ButtonPress) {
		eventbp = (XButtonPressedEvent *) &event;
		if (eventbp->window == wind[pause_wind])
	                wait = FALSE;
	   }
	   else if (event.type == KeyPress) {
		eventkp = (XKeyPressedEvent *) &event;
		XLookupString(&event,&s,1,&ksym,NULL);
		if ((s == 'c') || (s == 'C'))
			wait = FALSE;
	   }
	   else if (event.type == Expose) {
	   /* do refresh in pause mode; pause_wind and field are different */
		print_display();
		WindPrint(pause_wind,"Continue");
		sprintf(str,"Time: %s",clock_time_str(
			num_time_left_at_start-
			(time_at_start_of_pause-time_at_start_of_level
					       -pause_time)));
		WindPrint(time_left,str);

		/* blank out field and write "P-A-U-S-E" message on field */
		WindPrint(pause_wind,"Continue");
		XClearWindow(disp,field);
		strcpy(str,"P-A-U-S-E");
		XSetForeground(disp,gcs,color[font_color].pixel);
		XSetBackground(disp,gcs,color[background_color].pixel);
		XGetGeometry(disp,field,&a,&b,&c,&ww,&wh,&wbw,&e);
		fs = fontsstruct;
		XDrawImageString(disp,field,gcs,
			((ww-2*wbw)-XTextWidth(fs,str,strlen(str)))/2,
			(wh - fs->ascent - fs->descent)/2 +
				fs->ascent,str,strlen(str));
		XFlush(disp);
	
		/* now dequeue all immediately succeeding exposure events */
		/*    to prevent multiple, useless refreshes              */
		remove_expose_events();
	   }
	}

	pause_time += time(0) - time_at_start_of_pause;

	/* Restore windows to continue game */
	XClearWindow(disp,field);
	refresh();

	WindPrint(pause_wind,"Pause");
}

int setxy(vx,vy,x,y)
int vx,vy,*x,*y;
{
/* convert virtual coordinates to actual field pixel coordinates */
	double	rowsep,colsep;

	if (((vx>=0) && (vx<NUMCOLS)) &&
	    ((vy>=0) && (vy<NUMROWS))) {
		colsep = ((double)(w-2*bw)/(double)NUMCOLS);
		rowsep = ((double)fh/(double)NUMROWS);
		*x = vx*colsep;
		*y = vy*rowsep;
		return (SUCCESS);
	}
	else
		return (OUT_OF_RANGE);
}

int setvxvy(x,y,vx,vy)
int x,y,*vx,*vy;
{
/* convert actual field pixel coordinates to virtual coordinates */
	double	rowsep,colsep;

	if (((x>=0) && (x<w-2*bw)) &&
	    ((y>=0) && (y<fh))) {
		colsep = ((double)(w-2*bw)/(double)NUMCOLS);
		rowsep = ((double)fh/(double)NUMROWS);
		*vx = x/colsep;
		*vy = y/rowsep;
		return (SUCCESS);
	}
	else
		return (OUT_OF_RANGE);
}

int bomb_status(vx,vy)
int vx,vy;
{
	int	numbombs=0;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].c==fc_bomb))
			numbombs++;
		if (FIELD[vx][vy-1].c==fc_bomb)
			numbombs++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].c==fc_bomb))
			numbombs++;
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].c==fc_bomb))
			numbombs++;
		if (FIELD[vx][vy+1].c==fc_bomb)
			numbombs++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].c==fc_bomb))
			numbombs++;
	}
	if ((vx>0) && (FIELD[vx-1][vy].c==fc_bomb))
		numbombs++;
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].c==fc_bomb))
		numbombs++;

	return (numbombs);
}

void show_bomb_status(vx,vy)
int vx,vy;
{
	int	numbombs;
	char	str[MAXSTRLEN];

	numbombs = bomb_status(vx,vy);

	sprintf(str,"%d surrounding bomb%c",numbombs,numbombs==1 ? ' ' : 's');
	print_status(str);

	if ((automark) && (numbombs==0))
		mark_allaround_ok(manvx,manvy,0);
} 

/* show_all_bombs:  shows all bombs on field   */
/*                  if any marks are incorrect */
/*                     highlight those squares */
void show_all_bombs()
{
	int	c,r;	/* column and row under consideration */

	for (c=0;c<NUMCOLS;c++)
	   for (r=0;r<NUMROWS;r++)
		if (FIELD[c][r].c == fc_bomb)
		   if (FIELD[c][r].m == fm_safe)
			draw(bm_safewrong,c,r);	/* bomb, marked safe */
		   else
			draw(bm_bomb,c,r);	/* bomb, unmarked or correct */
		else if (FIELD[c][r].m == fm_bomb)
		   draw(bm_bombmarkwrong,c,r);	/* no bomb, marked unsafe */
}

void do_promotion()
{
        char                    str[MAXSTRLEN];
	XEvent			event;
        XButtonPressedEvent     *eventbp;
        XKeyPressedEvent        *eventkp;
        int                     wait = TRUE;

	finished = TRUE;
	num_score += current_bonus();
	printf("Your bonus for reaching the goal at rank %s is\n",
		num_rank_to_words(level));
	printf("     %5d = %d*%d \t (level*Time left)\n",
		current_bonus(),level+1,num_time_left);
	if (num_bombs_left>0)
		printf("           + %d    \t (Num bombs left)\n",
		num_bombs_left);
	if (num_grenades_left>0)
		printf("           + %d*%d \t (Num grenades left*ppg)\n",
		num_grenades_left,POINTS_PER_GRENADE);
	if (num_tombstones>0)
		printf("           - %d*%d \t (Num tombstones*ppts)\n",
		num_tombstones,POINTS_PER_TOMBSTONE);
/* This used to be used for ver 1.0
	if (used_showfig)
		printf("           - %d \t (Used showfig?)\n",
		POINTS_FOR_USED_SHOWFIG);
*/
	if (used_sanity)
		printf("           - %d \t (Used sanity check?)\n",
		(PERCENT_FOR_USED_SANITY*bonus_without_penalties())/100);
	if (used_eautomark)
		printf("           - %d \t (Used extended_automark?)\n",
		(PERCENT_FOR_USED_EAUTOMARK*bonus_without_penalties())/100);
	printf("Your current score is %d = %d + %d\n",
		num_score,num_score-current_bonus(),current_bonus());

	show_all_bombs();

	/* now update score on screen incrementally */
	{
		int i;
		/* this will make sure that at most 100 steps are made */
		int step = current_bonus()/100;

		for (i=num_score-current_bonus();i<num_score;i+=step) {
			sprintf(str,"Score: %d",i);
			PrintStr(score,str);
		}
		sprintf(str,"Score: %d",num_score);
		WindPrint(score,str);
	}

	if (level == HIGHEST_PLAYABLE_RANK) {
	   sprintf(str,
	   "Congratulations, you have reached completed the highest rank!");
	   level++;
	   show_and_add_scores();
	}
	else
	   sprintf(str,"Congratulations, you have been promoted to %s",
		num_rank_to_words(level+1));
	print_status(str);

	/* reward additional lives and grenades */
	if ((level>=LOWEST_LEVEL_FOR_REWARD) &&
	    (num_score>=LOWEST_SCORE_FOR_REWARD)) {
		if (++num_grenades_left > NUM_GRENADES_AT_START)
			num_grenades_left = NUM_GRENADES_AT_START;
		sprintf(str,"Grenades: %d",num_grenades_left);
		WindPrint(grenades_left,str);
		if (++num_lives>MAXLIVES) num_lives=MAXLIVES;
		sprintf(str,"Lives: %d",num_lives);
		WindPrint(lives_left,str);
	}

	sleep(3);

	print_status ("In the field, press key or click button to continue");
	while (wait == TRUE) {
	   while (!XPending(disp))
		usleep(XPENDING_UDELAY);
	   XNextEvent(disp,&event);
	   if (event.type==ButtonPress) {
		eventbp = (XButtonPressedEvent *) &event;
		if (eventbp->window == field)
			wait = FALSE;
		else if (eventbp->window == wind[quit_wind]) {
			level++;
			show_and_add_scores();
		}
	   }
	   else if (event.type == KeyPress) {
		eventkp = (XKeyPressedEvent *) &event;
		if (eventkp->window == field)
			wait = FALSE;
	   }
	   else if (event.type == Expose) {
		refresh();
		show_all_bombs();
	   }
	}
}

/* next_to_trail:  returns TRUE is at least one of the up to eight adjacent */
/*                 squares is already traversed and marked as fc_trail;     */
/*                 vx and vy is the square in question                      */
int next_to_trail(vx,vy)
int vx,vy;
{
	int	numbombs=0;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].c==fc_trail))
			return (TRUE);
		if (FIELD[vx][vy-1].c==fc_trail)
			return (TRUE);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].c==fc_trail))
			return (TRUE);
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].c==fc_trail))
			return (TRUE);
		if (FIELD[vx][vy+1].c==fc_trail)
			return (TRUE);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].c==fc_trail))
			return (TRUE);
	}
	if ((vx>0) && (FIELD[vx-1][vy].c==fc_trail))
		return (TRUE);
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].c==fc_trail))
		return (TRUE);

	return (FALSE);
}

int move_man(vx,vy)
int vx,vy;
{
	int	ax,ay;		/* adjusted x and y */
	int	reached_goal=FALSE;

	setxy(vx,vy,&ax,&ay);

	if (FIELD[vx][vy].m == fm_bomb) {
		XBell(disp,0);
		print_status("Square is marked as unsafe!");
		return (SQUARE_IS_UNSAFE);
	}
	if (((abs(manvx-vx)<=1) && (abs(manvy-vy)<=1)) ||
	    (FIELD[vx][vy].c==fc_trail) ||
	    (next_to_trail(vx,vy) && (FIELD[vx][vy].m==fm_safe))) {
	   switch (FIELD[vx][vy].c) {
		case fc_goal	:
		case fc_trail	:
		case fc_empty	: /* only allow movement of one square */
				  if ((FIELD[vx][vy].c==fc_goal) && (!dead))
					reached_goal = TRUE;
				  draw(bm_trail,manvx,manvy);
				  FIELD[manvx][manvy].c = fc_trail;
				  manvx = vx; manvy = vy;
				  draw(bm_man,vx,vy);
				  FIELD[manvx][manvy].c = fc_man;
				  FIELD[manvx][manvy].m = fm_nomark;
				  if (reached_goal)
					do_promotion();
				  break;
		case fc_man	: show_bomb_status(manvx,manvy);
				  draw(bm_man,manvx,manvy);
				  break;
		case fc_bomb	: draw(bm_trail,manvx,manvy);
				  FIELD[manvx][manvy].c = fc_trail;
				  die(vx,vy);
				  break;
		case fc_tombstone:
				  break;
		default		: fprintf(stderr,
					"Error: Illegal FIELD contents\n");
				  exit(2);
	   }
	   show_bomb_status(manvx,manvy);
	}
	else {
	   print_status("Can't move that far!");
	   XBell(disp,0);	      
	}
	return (SUCCESS);
}

int remove_mark(vx,vy)
int vx,vy;
{
	if (FIELD[vx][vy].m == fm_nomark)
		return (NO_MARK_TO_REMOVE);	
	else {
		FIELD[vx][vy].m = fm_nomark;
		clear(vx,vy);
		return (SUCCESS);
	}
}

int mark_ok(vx,vy)
int vx,vy;
{
	switch (FIELD[vx][vy].c) {
		case fc_empty:
		case fc_bomb:	FIELD[vx][vy].m = fm_safe;
				draw(bm_safe,vx,vy);
				return (SUCCESS);
		case fc_trail:
		case fc_man:
		case fc_tombstone:
		case fc_goal:	return (CANT_MARK);
		default:	fprintf(stderr,
					"Error: Illegal FIELD contents\n");
				exit(3);
	}
}

/* if level<0, then only mark immediately surrounding squares;  don't do
   extended automarking
*/
int mark_allaround_ok(vx,vy,level)
int vx,vy,level;	/* level=level of recursion */
{
	int	c,r;	/* col and row to clear FIELD[][].done flags */

	/* clear all done flags if on original call */
	if (level==0)
		for (c=0;c<NUMCOLS;c++)
		   for (r=0;r<NUMROWS;r++)
			FIELD[c][r].done = FALSE;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].m==fm_nomark))
			mark_ok(vx-1,vy-1);
		if (FIELD[vx][vy-1].m==fm_nomark)
			mark_ok(vx,vy-1);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].m==fm_nomark))
			mark_ok(vx+1,vy-1);
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].m==fm_nomark))
			mark_ok(vx-1,vy+1);
		if (FIELD[vx][vy+1].m==fm_nomark)
			mark_ok(vx,vy+1);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].m==fm_nomark))
			mark_ok(vx+1,vy+1);
	}
	if ((vx>0) && (FIELD[vx-1][vy].m==fm_nomark))
		mark_ok(vx-1,vy);
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].m==fm_nomark))
		mark_ok(vx+1,vy);

	if ((level>=0) && (extended_automark) && (!FIELD[vx][vy].done)) {
	/* mark square as being already marked-allaround to prevent repeat */
		FIELD[vx][vy].done = TRUE;
		if (bomb_status(vx,vy) == 0) {
			draw(bm_trail,vx,vy);
			FIELD[vx][vy].c = fc_trail;
			FIELD[vx][vy].m = fm_nomark;
		}
		if (vy>0) {
			if ((vx>0) && (FIELD[vx-1][vy-1].m==fm_safe) &&
			    (bomb_status(vx-1,vy-1)==0))
				mark_allaround_ok(vx-1,vy-1,level+1);
			if ((FIELD[vx][vy-1].m==fm_safe) &&
			    (bomb_status(vx,vy-1)==0))
				mark_allaround_ok(vx,vy-1,level+1);
			if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].m==fm_safe) &&
			    (bomb_status(vx+1,vy-1)==0))
				mark_allaround_ok(vx+1,vy-1,level+1);
		}
		if (vy<NUMROWS-1) {
			if ((vx>0) && (FIELD[vx-1][vy+1].m==fm_safe) &&
			    (bomb_status(vx-1,vy+1)==0))
				mark_allaround_ok(vx-1,vy+1,level+1);
			if ((FIELD[vx][vy+1].m==fm_safe) &&
			    (bomb_status(vx,vy+1)==0))
				mark_allaround_ok(vx,vy+1,level+1);
			if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].m==fm_safe) &&
			    (bomb_status(vx+1,vy+1)==0))
				mark_allaround_ok(vx+1,vy+1,level+1);
		}
		if ((vx>0) && (FIELD[vx-1][vy].m==fm_safe) &&
		    (bomb_status(vx-1,vy)==0))
			mark_allaround_ok(vx-1,vy,level+1);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].m==fm_safe) &&
		    (bomb_status(vx+1,vy)==0))
			mark_allaround_ok(vx+1,vy,level+1);
	}

	/* put man marker back on field */
	if (level<=0)
		draw(bm_man,manvx,manvy);

	return (SUCCESS);
}

int mark_bomb(vx,vy)
int vx,vy;
{
	switch (FIELD[vx][vy].c) {
		case fc_empty:
		case fc_bomb:	FIELD[vx][vy].m = fm_bomb;
				draw(bm_bombmark,vx,vy);
				return (SUCCESS);
		case fc_trail:
		case fc_man:
		case fc_tombstone:
		case fc_goal:	return (CANT_MARK);
		default:	fprintf(stderr,
					"Error: Illegal FIELD contents\n");
				exit(3);
	}
}

void die(vx,vy)
int vx,vy;
{
	char			str[MAXSTRLEN];

	explode(vx,vy);
	draw(bm_tombstone,vx,vy);
	FIELD[vx][vy].c = fc_tombstone;
	num_tombstones++;
	if (--num_lives<0)
		num_lives = 0;
	sprintf(str,"Lives: %d",num_lives);
	WindPrint(lives_left,str);
	if ((num_lives<=0) && (!dead)) {
		show_bomb_status(manvx,manvy);
		dead = TRUE;
		show_all_bombs();
		print_status("You have just died!  GAME OVER!");
		XBell(disp,0);
		XBell(disp,0);
		XBell(disp,0);
		XFlush(disp);
		sleep(3);
	}
	draw(bm_man,0,0);
	FIELD[0][0].c = fc_man;
	FIELD[0][0].m = fm_nomark;
	manvx = manvy = 0;
	show_bomb_status(manvx,manvy);
}

int explode(vx,vy)
int vx,vy;
{
	int	i;
	char	str[MAXSTRLEN];

	if (FIELD[vx][vy].c == fc_bomb) {
		num_bombs_left--;
		sprintf(str,"Bombs: %d",num_bombs_left);
		WindPrint(bombs_left,str);
	}

	/* simulate explosion on screen */
	for (i=0;i<10;i++) {
		draw(bm_pow,vx,vy);
		usleep(BOMB_EXPLODE_UDELAY);
		draw(bm_bomb,vx,vy);
		usleep(BOMB_EXPLODE_UDELAY);
	}
	clear(vx,vy);
	FIELD[vx][vy].c = fc_empty;
	FIELD[vx][vy].m = fm_nomark;


	/* check to see if other bombs nearby--Manhattan dirs only */
	if ((vx>0) && (FIELD[vx-1][vy].c==fc_bomb)) explode(vx-1,vy);
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].c==fc_bomb)) explode(vx+1,vy);
	if ((vy>0) && (FIELD[vx][vy-1].c==fc_bomb)) explode(vx,vy-1);
	if ((vy<NUMROWS-1) && (FIELD[vx][vy+1].c==fc_bomb)) explode(vx,vy+1);

	/* check to see if man nearby--Manhattan dirs only */
	if ((vx>0) && (FIELD[vx-1][vy].c==fc_man)) die(vx-1,vy);
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].c==fc_man)) die(vx+1,vy);
	if ((vy>0) && (FIELD[vx][vy-1].c==fc_man)) die(vx,vy-1);
	if ((vy<NUMROWS-1) && (FIELD[vx][vy+1].c==fc_man)) die(vx,vy+1);

	return(SUCCESS);
}

/* bonus_without_penalties:  returns the current bonus remaining for */
/*	this level, without used_* penalties                         */
int bonus_without_penalties()
{
	int	b;	/* temp value for current bonus */

	b = (level+1)*num_time_left
		+ num_bombs_left
		+ num_grenades_left*POINTS_PER_GRENADE
		- num_tombstones*POINTS_PER_TOMBSTONE;

	if (b < 0)
		b = 0;

	return (b);
}

/* current_bonus:  returns the current amount of bonus */
/*                    remaining for this level         */
int current_bonus()
{
	int	b;	/* temp value for current bonus */

	b = bonus_without_penalties()
/* This used to be used for ver 1.0
		- used_showfig*POINTS_FOR_USED_SHOWFIG;
*/
		- used_sanity*
		   (PERCENT_FOR_USED_SANITY*bonus_without_penalties())/100
		- used_eautomark*
		   (PERCENT_FOR_USED_EAUTOMARK*bonus_without_penalties())/100;

	if (b < 0)
		b = 0;

	return (b);
}
		

/* update_bonus:  recalculate the amount of bonus remaining for this level */
/*                display the correct new bonus on screen                  */
int update_bonus()
{
	char		str[MAXSTRLEN];
	static int	last_bonus;

	if ((num_bonus = current_bonus()) != last_bonus) {
		sprintf(str,"Bonus: %d",num_bonus);
		WindPrint(bonus_wind,str);
		last_bonus = num_bonus;
	}
}

int update_time()
{
	long	st,ct;	/* starting and current times */
	char	str[MAXSTRLEN];
	int	previous_num_time_left=num_time_left;

	st = time_at_start_of_level;
	ct = time(0);
	num_time_left = num_time_left_at_start - (ct-st-pause_time);
	update_bonus();
	if (num_time_left<=0) {
		print_status("Time is out!  GAME OVER!");
		sprintf(str,"Time: %s",clock_time_str(num_time_left));
		WindPrint(time_left,str);
		XBell(disp,0);
		XBell(disp,0);
		XBell(disp,0);
		return (TIME_IS_OVER);	/* TIME_IS_OVER = TRUE */
	}
	else if (num_time_left != previous_num_time_left) {
		sprintf(str,"Time: %s",clock_time_str(num_time_left));
		WindPrint(time_left,str);
		return (SUCCESS);
	}
	return (SUCCESS);
}

int throw_grenade()
{
	char			str[MAXSTRLEN];
	XEvent			event;
	XButtonPressedEvent	*eventbp;
	int			vx,vy;

	if (num_grenades_left==0) {
		print_status("No grenades left!");
		XBell(disp,0);
		return (NO_GRENADES_LEFT);
	}

	XClearWindow(disp,wind[tgrenade_wind]);
	InvPrintStr(tgrenade_wind,"Throw Grenade");
	print_status("Ready to throw grenade");

	/* Get position to throw grenade */
	while (!XPending(disp))
		usleep(XPENDING_UDELAY);
	XNextEvent(disp,&event);
	eventbp = (XButtonPressedEvent *) &event;
	if ((event.type != ButtonPress) || (eventbp->window != field) ||
	    (setvxvy(eventbp->x,eventbp->y,&vx,&vy) == OUT_OF_RANGE)) {
		print_status("Click on the position to throw grenade!");
		XBell(disp,0);
		WindPrint(tgrenade_wind,"Throw Grenade");
		return (ILLEGAL_POSITION);
	}

	/* check to see if within range */
	if (abs(manvx-vx) + abs(manvy-vy) > GRENADE_DISTANCE) {
		sprintf(str,
			"Can't toss grenade that far!  Max distance is %d.\n",
			GRENADE_DISTANCE);
		print_status(str);
		XBell(disp,0);
		WindPrint(tgrenade_wind,"Throw Grenade");
		return (TOO_FAR_TO_TOSS_GRENADE);
	}

	/* passed checks -- toss grenade */
	if (FIELD[vx][vy].c == fc_man) {
		die(vx,vy);
		sprintf(str,"Grenades: %d",--num_grenades_left);
		WindPrint(grenades_left,str);
		WindPrint(tgrenade_wind,"Throw Grenade");
		return (DIED);
	}
	else if ((FIELD[vx][vy].c != fc_tombstone) && 
	    (FIELD[vx][vy].c != fc_goal)) {
		explode(vx,vy);
		show_bomb_status(manvx,manvy);
		draw(bm_man,manvx,manvy);
		sprintf(str,"Grenades: %d",--num_grenades_left);
		WindPrint(grenades_left,str);
		WindPrint(tgrenade_wind,"Throw Grenade");
		return (SUCCESS);
	}
	else {
		strcpy(str,"No bomb at that position.  ");
		strcat(str,"Can't blow up tombstone or goal.");
		print_status(str);
		XBell(disp,0);
		WindPrint(tgrenade_wind,"Throw Grenade");
		return (CANT_EXPLODE);
	}
}

/* num_adj_squares_marked_bomb: returns num adjacent squares user has marked
	as being unsafe
*/
int num_adj_squares_marked_bomb(vx,vy)
int vx,vy;
{
	int	nummarks=0;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].m==fm_bomb))
			nummarks++;
		if (FIELD[vx][vy-1].m==fm_bomb)
			nummarks++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].m==fm_bomb))
			nummarks++;
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].m==fm_bomb))
			nummarks++;
		if (FIELD[vx][vy+1].m==fm_bomb)
			nummarks++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].m==fm_bomb))
			nummarks++;
	}
	if ((vx>0) && (FIELD[vx-1][vy].m==fm_bomb))
		nummarks++;
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].m==fm_bomb))
		nummarks++;

	return (nummarks);
}

/* num_adj_squares_marked_safe: returns num adjacent squares user has marked
	as being safe
*/
int num_adj_squares_marked_safe(vx,vy)
int vx,vy;
{
	int	nummarks=0;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].m==fm_safe))
			nummarks++;
		if (FIELD[vx][vy-1].m==fm_safe)
			nummarks++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].m==fm_safe))
			nummarks++;
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].m==fm_safe))
			nummarks++;
		if (FIELD[vx][vy+1].m==fm_safe)
			nummarks++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].m==fm_safe))
			nummarks++;
	}
	if ((vx>0) && (FIELD[vx-1][vy].m==fm_safe))
		nummarks++;
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].m==fm_safe))
		nummarks++;

	return (nummarks);
}

/* num_unvisited_adj_squares: returns num adjacent squares user has
	not yet visited (not marked with fc_trail)
*/
int num_unvisited_adj_squares(vx,vy)
int vx,vy;
{
	int	num_unvisited=0;

	if (vy>0) {
		if ((vx>0) && (FIELD[vx-1][vy-1].c!=fc_trail))
			num_unvisited++;
		if (FIELD[vx][vy-1].c!=fc_trail)
			num_unvisited++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy-1].c!=fc_trail))
			num_unvisited++;
	}
	if (vy<NUMROWS-1) {
		if ((vx>0) && (FIELD[vx-1][vy+1].c!=fc_trail))
			num_unvisited++;
		if (FIELD[vx][vy+1].c!=fc_trail)
			num_unvisited++;
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy+1].c!=fc_trail))
			num_unvisited++;
	}
	if ((vx>0) && (FIELD[vx-1][vy].c!=fc_trail))
		num_unvisited++;
	if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].c!=fc_trail))
		num_unvisited++;

	return (num_unvisited);
}

/* do_sanity_check: Make sure user is not losing his mind!
	If more adjacent squares are marked unsafe than is indicated by the
	current bomb status, warn the user.
	If more adjacent squares are marked safe than the number of unvisited
	squares - the current bomb status, warn the user.
*/
int do_sanity_check(vx,vy)
int vx,vy;
{
	/* Check first for too many squares marked unsafe */
	if (num_adj_squares_marked_bomb(vx,vy) > bomb_status(vx,vy)) {
		print_status("WARNING: too many squares marked UNsafe!");
		XBell(disp,0);
		return (CHECK_FAILED);
	}

	/* Next check for too many squares marked safe */
	if (num_adj_squares_marked_safe(vx,vy) > 
	   num_unvisited_adj_squares(vx,vy) - bomb_status(vx,vy)) {
		print_status("WARNING: too many squares marked safe!");
		XBell(disp,0);
		return (CHECK_FAILED);
	}

	return (SUCCESS);
}
