/* xtmines: game where you try to cross a minefield */
/* windows.c: routines to initialize windows, fonts, GCs, etc */
/* Written by Timothy Tsai  April 13, 1992 */

#include "xtmines.h"

#ifdef SVR4
#define srandom		srand
#define random		rand
#endif

/* open_display:  open the display;                */
/*                set up default values            */
/*                   (eg. root, white, black, etc) */
/*                set random seed                  */
void open_display()
{
	if ((disp = XOpenDisplay(NULL)) == NULL) {
		fprintf(stderr, "Can't Open Display\n");
		exit(1);
	}
	root	= DefaultRootWindow(disp);
	screen	= DefaultScreen(disp);
	depth	= DefaultDepth(disp,screen);
	visual	= DefaultVisual(disp,screen);
	white	= WhitePixel(disp,screen);
	black	= BlackPixel(disp,screen);

	srandom(time(0));
}

/* load_fonts:  load 3 fonts:  fonts (status line and showfig) */
/*                             fontm (main -- for most text)   */
/*                             fontt (time and hiscores list)  */
void load_fonts()
{
	fonts		= XLoadFont(disp,FONTS);
	fontsstruct	= XQueryFont(disp,fonts);
	/* fontsw, fontsh, fontsa are used by PrintStr() for status line */
	fontsw		= fontsstruct->max_bounds.rbearing -
				fontsstruct->min_bounds.lbearing;
	fontsh		= fontsstruct->ascent + fontsstruct->descent;
	fontsa		= fontsstruct->ascent;
	/* fonstshoww, fontshowh, fontshowa are used by draw() for showfig */
	fontshoww	= (fontsstruct->per_char + '0')->rbearing -
				(fontsstruct->per_char + '0')->lbearing;
	fontshowh	= (fontsstruct->per_char + '0')->ascent +
				(fontsstruct->per_char + '0')->descent;
	fontshowa	= (fontsstruct->per_char + '0')->ascent;

	fontm		= XLoadFont(disp,FONTM);
	fontmstruct	= XQueryFont(disp,fontm);
	fontmw		= fontmstruct->max_bounds.rbearing -
				fontmstruct->min_bounds.lbearing;
	fontmh		= fontmstruct->ascent + fontmstruct->descent;
	fontma		= fontmstruct->ascent;

	fontt		= XLoadFont(disp,FONTT);
	fonttstruct	= XQueryFont(disp,fontt);
	fonttw		= fonttstruct->max_bounds.rbearing -
				fonttstruct->min_bounds.lbearing;
	fontth		= fonttstruct->ascent + fonttstruct->descent;
	fontta		= fonttstruct->ascent;
}

/* create_map_frame:  actually create each window and map to screen */
/*                    this is where each window's position and size */
/*                    are specified;  also, create all bitmaps and  */
/*                    initialize bitmaps dimension arrays           */
void create_map_frame()
{
	int	ww,	/* window width of small button windows */
		wh1,	/* window height of status line right under field */
		wh2,	/*                  2nd row of buttons */
		wh3,	/*                  3rd row of buttons */
		wh4;	/*                  last row of buttons */

	/*---First, create and map windows----------------------------*/

	/* first wind button widths and heights */
	/* NUM1WINDS = num of buttons in one row */
	ww = (w-(NUM1WINDS+1)*bw)/(NUM1WINDS);
	wh1 = wh2 = wh3 = (h-fh-6*bw)/4;  wh4 = h-fh-6*bw-wh1-wh2-wh3;

	/* create frame and field */
	frame	= XCreateSimpleWindow(disp,root,x,y,w,h,bw,black,white);
	field			= XCreateSimpleWindow(disp,frame,
					0,0,w-2*bw,fh,bw,
					black,white);

	/* create status line */
	wind[status]		= XCreateSimpleWindow(disp,frame,
					0,fh+bw,w-2*bw,wh1,bw,
					black,white);

	/* create 2nd button row */
	wind[bombs_left]	= XCreateSimpleWindow(disp,frame,
					0,fh+wh1+2*bw,ww,
					wh2,bw,black,white);
	wind[bonus_wind]	= XCreateSimpleWindow(disp,frame,
					bonus_wind*(ww+bw),
					fh+wh1+2*bw,ww,
					wh2,bw,black,white);
	wind[time_left]		= XCreateSimpleWindow(disp,frame,
					time_left*(ww+bw),
					fh+wh1+2*bw,ww,
					wh2,bw,black,white);
	wind[score]		= XCreateSimpleWindow(disp,frame,
					score*(ww+bw),
					fh+wh1+2*bw,ww,
					wh2,bw,black,white);
	wind[grenades_left]	= XCreateSimpleWindow(disp,frame,
					grenades_left*(ww+bw),
					fh+wh1+2*bw,ww,
					wh2,bw,black,white);
	wind[rank]		= XCreateSimpleWindow(disp,frame,
					rank*(ww+bw),
					fh+wh1+2*bw,w-2*bw-(rank)*(ww+bw),
					wh2,bw,black,white);

	/* create 3rd button row */
	wind[quit_wind]		= XCreateSimpleWindow(disp,frame,
					rank*(ww+bw),
					fh+3*bw+wh1+wh2,w-2*bw-(rank)*(ww+bw),
					wh3,bw,black,white);
	wind[show_wind]		= XCreateSimpleWindow(disp,frame,
					grenades_left*(ww+bw),
					fh+3*bw+wh1+wh2,ww,
					wh3,bw,black,white);
	wind[tgrenade_wind]	= XCreateSimpleWindow(disp,frame,
					score*(ww+bw),
					fh+3*bw+wh1+wh2,ww,
					wh3,bw,black,white);
	wind[giveup_wind]	= XCreateSimpleWindow(disp,frame,
					time_left*(ww+bw),
					fh+3*bw+wh1+wh2,ww,
					wh3,bw,black,white);
	wind[automark_wind]	= XCreateSimpleWindow(disp,frame,
					bonus_wind*(ww+bw),
					fh+3*bw+wh1+wh2,ww,
					wh3,bw,black,white);
	wind[lives_left]	= XCreateSimpleWindow(disp,frame,
					0,fh+3*bw+wh1+wh2,ww,
					wh3,bw,black,white);

	/* create last button row */
/* These are for future expansion
	wind[]			= XCreateSimpleWindow(disp,frame,
					rank*(ww+bw),
					fh+4*bw+wh1+wh2+wh3,
					w-2*bw-(rank)*(ww+bw),
					wh4,bw,black,white);
	wind[]			= XCreateSimpleWindow(disp,frame,
					grenades_left*(ww+bw),
					fh+4*bw+wh1+wh2+wh3,ww,
					wh4,bw,black,white);
*/
	wind[pause_wind]	= XCreateSimpleWindow(disp,frame,
					score*(ww+bw),
					fh+4*bw+wh1+wh2+wh3,ww,
					wh4,bw,black,white);
	wind[sanitycheck_wind]	= XCreateSimpleWindow(disp,frame,
					time_left*(ww+bw),
					fh+4*bw+wh1+wh2+wh3,ww,
					wh4,bw,black,white);
	wind[eautomark_wind]	= XCreateSimpleWindow(disp,frame,
					bonus_wind*(ww+bw),
					fh+4*bw+wh1+wh2+wh3,ww,
					wh4,bw,black,white);
	wind[refresh_wind]	= XCreateSimpleWindow(disp,frame,
					0,fh+4*bw+wh1+wh2+wh3,ww,
					wh4,bw,black,white);

	/* set window and icon names */
	XStoreName(disp,frame,"xtmines");
	XSetIconName(disp,frame,"xtmines");

	/* Map all windows to screen */
	XMapRaised(disp,frame);
	XMapRaised(disp,field);
	XMapRaised(disp,wind[bombs_left]);
	XMapRaised(disp,wind[bonus_wind]);
	XMapRaised(disp,wind[time_left]);
	XMapRaised(disp,wind[score]);
	XMapRaised(disp,wind[grenades_left]);
	XMapRaised(disp,wind[rank]);
	XMapRaised(disp,wind[quit_wind]);
	XMapRaised(disp,wind[show_wind]);
	XMapRaised(disp,wind[tgrenade_wind]);
	XMapRaised(disp,wind[giveup_wind]);
	XMapRaised(disp,wind[automark_wind]);
	XMapRaised(disp,wind[lives_left]); 
	XMapRaised(disp,wind[pause_wind]); 
	XMapRaised(disp,wind[sanitycheck_wind]); 
	XMapRaised(disp,wind[eautomark_wind]); 
	XMapRaised(disp,wind[refresh_wind]); 
	XMapRaised(disp,wind[status]);

	/*---Now initialize all bitmaps and accompanying dimensions---*/
	
	/* load all bitmaps into memory */
	bitmap[bm_safe]		= XCreateBitmapFromData(disp,field, 
					safe_bits,safe_width,safe_height);
	bitmap[bm_man]		= XCreateBitmapFromData(disp,field,
					man_bits,man_width,man_height);
	bitmap[bm_bomb]		= XCreateBitmapFromData(disp,field,
					bomb_bits,bomb_width,bomb_height);
	bitmap[bm_tombstone]	= XCreateBitmapFromData(disp,field,
					tstone_bits,tstone_width,tstone_height);
	bitmap[bm_goal]		= XCreateBitmapFromData(disp,field,
					goal_bits,goal_width,goal_height);
	bitmap[bm_trail]	= XCreateBitmapFromData(disp,field,
					trail_bits,trail_width,trail_height);
	bitmap[bm_pow]		= XCreateBitmapFromData(disp,field,
					pow_bits,pow_width,pow_height);
	bitmap[bm_bombmark]	= XCreateBitmapFromData(disp,field,
					bombmark_bits,bombmark_width,
					bombmark_height);
	bitmap[bm_safewrong]	= XCreateBitmapFromData(disp,field,
					safewrong_bits,safewrong_width,
					safewrong_height);
	bitmap[bm_bombmarkwrong]= XCreateBitmapFromData(disp,field,
					bombmarkwrong_bits,bombmarkwrong_width,
					bombmarkwrong_height);

	/* put all bitmap dimensions into width and height arrays */
	bmwidth[bm_safe]	= safe_width;
	bmwidth[bm_man]		= man_width;
	bmwidth[bm_bomb]	= bomb_width;
	bmwidth[bm_tombstone]	= tstone_width;
	bmwidth[bm_goal]	= goal_width;
	bmwidth[bm_trail]	= trail_width;
	bmwidth[bm_pow]		= pow_width;
	bmwidth[bm_bombmark]	= bombmark_width;
	bmwidth[bm_safewrong]	= safewrong_width;
	bmwidth[bm_bombmarkwrong]	= bombmarkwrong_width;

	bmheight[bm_safe]	= safe_height;
	bmheight[bm_man]	= man_height;
	bmheight[bm_bomb]	= bomb_height;
	bmheight[bm_tombstone]	= tstone_height;
	bmheight[bm_goal]	= goal_height;
	bmheight[bm_trail]	= trail_height;
	bmheight[bm_pow]	= pow_height;
	bmheight[bm_bombmark]	= bombmark_height;
	bmheight[bm_safewrong]	= safewrong_height;
	bmheight[bm_bombmarkwrong]	= bombmarkwrong_height;
}

/* create_GC_colormap:  set up Graphics Contexts and colormaps             */
/*                      right now, there are only 2 colors (black & white) */
void create_GC_colormap()
{
	XGCValues	gcv;	/* to determine attributes to set */
	XColor		exact;	/* used by XAllocNamedColor;     */
				/*    return value is never used */
	
	/* set up gcs -- GC for status line and showfig */
	gcv.font = fonts;
	gcv.line_width = 1;
	gcv.graphics_exposures = FALSE;
	gcs	 = XCreateGC(disp,frame,
			GCFont | GCLineWidth | GCGraphicsExposures,
			&gcv);

	/* set up gcm -- GC for main (most text) */
	gcv.font = fontm;
	gcv.line_width = 1;
	gcv.graphics_exposures = FALSE;
	gcm	 = XCreateGC(disp,frame,
			GCFont | GCLineWidth | GCGraphicsExposures,
			&gcv);

	/* set up gct -- GC for time and hiscores list */
	gcv.font = fontt;
	gcv.line_width = 1;
	gcv.graphics_exposures = FALSE;
	gct	 = XCreateGC(disp,frame,
			GCFont | GCLineWidth | GCGraphicsExposures,
			&gcv);

	/* Set up colormap */
	cmap	 = XDefaultColormap(disp,screen);
	XAllocNamedColor(disp,cmap,FONTCOLOR,&color[font_color],&exact);
	XAllocNamedColor(disp,cmap,BACKGROUNDCOLOR,&color[background_color],
		&exact);
	XAllocNamedColor(disp,cmap,FIELDCOLOR,&color[field_color],
		&exact);
}

/* set_event_masks:  all event masks are set here */
void set_event_masks()
{
	int i;
	windtype windownum;

	/* set exposure mask for all windows */
	XSelectInput(disp,frame,ExposureMask);
	for (windownum=0;windownum<NUMWINDS;windownum++)
		XSelectInput(disp,wind[windownum],ExposureMask);

	/* check to see if quit */
	XSelectInput(disp,wind[quit_wind],ButtonPressMask|ExposureMask);

	/* check to see if toggle show */
	XSelectInput(disp,wind[show_wind],ButtonPressMask|ExposureMask);

	/* check to see if throw grenades */
	XSelectInput(disp,wind[tgrenade_wind],ButtonPressMask|ExposureMask);

	/* check to see if give up */
	XSelectInput(disp,wind[giveup_wind],ButtonPressMask|ExposureMask);

	/* check to see if toggle automark */
	XSelectInput(disp,wind[automark_wind],ButtonPressMask|ExposureMask);

	/* check to see if toggle pause */
	XSelectInput(disp,wind[pause_wind],ButtonPressMask|ExposureMask);

	/* check to see if toggle sanity check */
	XSelectInput(disp,wind[sanitycheck_wind],ButtonPressMask|ExposureMask);

	/* check to see if toggle extended_automark */
	XSelectInput(disp,wind[eautomark_wind],ButtonPressMask|ExposureMask);

	/* check to see if refresh */
	XSelectInput(disp,wind[refresh_wind],ButtonPressMask|ExposureMask);

	/* allow mouse button and key presses in field */
	XSelectInput(disp,field,KeyPressMask|ButtonPressMask|ExposureMask);
}

/* init_values:  called at the beginning of every level      */
/*               sets num_bombs_left, num_time_left_at_start */
/*                    num_time_left, used_showfig,           */
/*                    used_sanity, used_eautomark            */
int	num_bombs_at_start[NUMRANKS];
void init_values()
{
	num_bombs_at_start[grunt]	= 50;
	num_bombs_at_start[corporal]	= 100;
	num_bombs_at_start[lieutenant]	= 150;
	num_bombs_at_start[captain]	= 200;
	num_bombs_at_start[mmajor]	= 250;
	num_bombs_at_start[general]	= 300;
	num_bombs_at_start[president]	= 350;
	num_bombs_at_start[king]	= 400;
	num_bombs_at_start[emperor]	= 450;
	num_bombs_at_start[angel]	= 500;

	num_tombstones		= 0;
	num_bombs_left		= num_bombs_at_start[level];
	num_time_left_at_start	= SECONDS_PER_LEVEL*(level+1);
	num_time_left		= num_time_left_at_start;
	pause_time		= 0;
	used_showfig		= (show==sh_fig) ? TRUE : FALSE;
	used_sanity		= (sanity) ? TRUE : FALSE;
	used_eautomark		= (extended_automark) ? TRUE : FALSE;

	num_bonus		= current_bonus();
}

/* illegal_bomb_position:  input a virtual field position             */
/*                         output a boolean=is bomb allowed here?     */
/*                         illegal is more than two bombs around goal */
/*                                    4 most top-left squares         */
int illegal_bomb_position(vx,vy)
int vx,vy;
{
	int	num_bombs_around_goal;

	/* find how many of the 3 squares around the goal has bombs */
	num_bombs_around_goal =
		(FIELD[NUMCOLS-1][NUMROWS-2].c==fc_bomb) +
		(FIELD[NUMCOLS-2][NUMROWS-2].c==fc_bomb) +
		(FIELD[NUMCOLS-2][NUMROWS-1].c==fc_bomb);

	/* check to see if 4 most top-left squares are free */ 
	if (((vx==0) && (vy==0)) || ((vx==0) && (vy==1)) ||
	    ((vx==1) && (vy==0)) || ((vx==1) && (vy==1)) ||
	    ((vx==NUMCOLS-1) && (vy==NUMROWS-1)) ||
	    (FIELD[vx][vy].c==fc_bomb) ||
	    ((((vx==NUMCOLS-1) && (vy=NUMROWS-2)) ||
	      ((vx==NUMCOLS-2) && (vy=NUMROWS-2)) ||
	      ((vx==NUMCOLS-2) && (vy=NUMROWS-1))) &&
	     (num_bombs_around_goal==2)))
		return (TRUE);
	else
		return (FALSE);
}

/* blocked:  returns a boolean indicating whether a continuous line of bombs */
/*              from square vx,vy (where vx,vy is on top or right edge) to   */
/*              the bottom or left edge of the field.  If so, then return    */
/*              TRUE, else return FALSE.  As each */
int blocked(vx,vy)
int vx,vy;
{
	/* marked as safe means that square has already been looked at */
	if (FIELD[vx][vy].m == fm_safe)
		return (FALSE);		/* square has already been looked at */
	else
		FIELD[vx][vy].m = fm_safe;	
					/* else mark square as having */
					/*    been looked at          */

	if (FIELD[vx][vy].c != fc_bomb)
		return (FALSE);		/* square has no bomb; thus, */
					/*    no path through here   */
	else {
		if ((vx==0) || (vy==NUMROWS-1))
		   return (TRUE);	/* we have reached the bottom or */
					/*    left edge and found a bomb */
		if ((vy>0) && (FIELD[vx][vy-1].m==fm_nomark) &&
			(blocked(vx,vy-1)))
				return (TRUE);
		if ((vy<NUMROWS-1) && (FIELD[vx][vy+1].m==fm_nomark) &&
			(blocked(vx,vy+1)))
				return (TRUE);
		if ((vx>0) && (FIELD[vx-1][vy].m==fm_nomark) &&
			(blocked(vx-1,vy)))
				return (TRUE);
		if ((vx<NUMCOLS-1) && (FIELD[vx+1][vy].m==fm_nomark) &&
			(blocked(vx+1,vy)))
				return (TRUE);

		/* if we got this far, then there are no path of bombs */
		/*    through this square;  thus, return not blocked   */
		return (FALSE);
	} /* else FIELD[vx][vy] == fc_bomb */
}

/* guaranteed_path:  returns a boolean indicating whether there is a path     */
/*                      through the from start to goal without using grenades */
/*                   all field contents must be set first                     */
/*                   all field marks must be cleared first                    */
int guaranteed_path()
{
	int	c,r;	/* current column and row being considered */

	for (c=1;c<NUMCOLS;c++)		/* c=1, skip starting square */
		if (blocked(c,0))
			return (FALSE);	/* no path from start to finish */
	for (r=0;r<NUMROWS-1;r++)	/* NUMROWS-1, skip goal square */
		if (blocked(NUMCOLS-1,r))
			return (FALSE);	/* no path from start to finish */

	return (TRUE);			/* else there is a guaranteed path */
}

/* manvx, manvy:  current virtual man position on field */
int	manvx,manvy;

/* set_field:  will put the appropriate number of bombs in field */
/*                according to level (makes sure that no illegal */
/*                squares have bombs                             */
/*             places man in top-left corner                     */
/*             places goal in bottom-right corner                */
/*             guarantees that there is a path from start to end */
/*                by repeating until that is true                */
void set_field()
{
	int	c,r,	/* column and row for clearing field */
		i;	/* counter for number of bombs */
			/*    placed so far            */
	int	vx,vy;	/* current bomb position being considered */

	do {
		/* first clear the entire field of all contents and marks */
		for (c=0;c<NUMCOLS;c++)
		   for (r=0;r<NUMROWS;r++) {
			FIELD[c][r].c = fc_empty;	/* clear contents */
			FIELD[c][r].m = fm_nomark;	/* clear marks */
			clear(c,r);			/* erase bitmaps */
		   }

		/* put man in top-left corner */
		FIELD[0][0].c = fc_man;
		manvx = 0; manvy = 0;

		/* place bombs in field */
		for (i=0;i<num_bombs_at_start[level];i++) {
		   do {
			vx = random()%NUMCOLS;
			vy = random()%NUMROWS;
		   } while (illegal_bomb_position(vx,vy));
		   FIELD[vx][vy].c = fc_bomb;
		}

		/* place goal in bottom-right corner */
		FIELD[NUMCOLS-1][NUMROWS-1].c = fc_goal;

	} while (!guaranteed_path());

	/* now clear all marks from field */
	for (c=0;c<NUMCOLS;c++)
	   for (r=0;r<NUMROWS;r++)
		FIELD[c][r].m = fm_nomark;      /* clear marks */
}
