#define SPACING        2
#define BUTTON_BORDER  1
#define BUTTON_MARGIN  2
#define EDIT_SPACING   5
#define EDIT_BORDER    2
#define DIALOG_BORDER  1
#define DIALOG_MARGIN  2
#define MAXLNLEN      64

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <varargs.h>
#include <sys/time.h>
#include <ctype.h>


#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Xresource.h>

char *malloc();
#define NEW(t) ((t *)malloc(sizeof(t)))
#define OLD(v) free((char *)(v))

#include "blockade.h"
#include "blockade-pix.h"
#include "blockade-lev.h"
#include "blockade-info.h"
#include "blockade-snd.h"

char **argvec;

char *getenv();

XrmDatabase db;
char *defaults = "\
*Background: black\n\
*Foreground: white\n\
*BorderColor: white\n\
*BorderWidth: 1\n\
*Name: xblockade\n\
*IconName: blockade\n\
";

char *displayname;
char *geometryspec;
char *background;
char *foreground;
char *bordercstr;
char *borderwstr;
char *name;
char *iconname;
char *fontname;
char *selkey;
int bw_flag;
int no_deflev;
int new_flag;
int syncX;
char *levelstr;

int argc;
char **argv;

Display *disp;
Screen *scr;
int width;
int height;
int depth;
Window rootwin;
Colormap defcmap;
Visual *visual;
GC defgc;

int bw_mode;

XColor fgcolor;
XColor bgcolor;
XColor bdcolor;

XColor blackcolor;
XColor whitecolor;
XColor colors[B_NCOLORS];

GC wingc;
XGCValues winshadow;
Window topwin;
Window gamewin;
Window line1win;
Window line2win;
Window textwin;
Window textpanelwin;
Window creditwin;
Window helpwin;
Window helppanelwin;
Window helpwins[N_HELP];
Window levprompt_win;
Window msgwin;
Window editwin;
Window g_buttonwin;
Window e_buttonwin;
Window g_buttonwin_s;
Window g_buttonwin_no_s;
Colormap wincmap;
Pixmap gray50;
int topw;
int toph;
int gamew;
int gameh;
int gamey;
int textw;
int texth;
int borderwidth;
XFontStruct *font;
XCharStruct maxdigitsize;
int deffont;
Font fontid;
char *levprompt_buf = 0;
int levprompt_fill;
int levprompt_len;
int levprompt_w;
int levprompt_h;
int levprompt_xoff;
int levprompt_xclr;
char *levprompt_prompt = "Level: ";
char msg_buf[256];
int msg_len;
int msg_w;
int msg_h;
int editw;
int edith;

int button_h;

int buttoncb_help();
int buttoncb_credits();
int buttoncb_edit();
int buttoncb_quit();
int buttoncb_save();

BUTTON buttons_no_s[] = { { "Help", buttoncb_help },
			  { "Credits", buttoncb_credits },
			  { "Edit", buttoncb_edit },
			  { "Quit", buttoncb_quit } };
#define NBUTTONS_NO_S (sizeof(buttons_no_s)/sizeof(buttons_no_s[0]))

BUTTON buttons_s[] = { { "Help", buttoncb_help },
		       { "Credits", buttoncb_credits },
		       { "Edit", buttoncb_edit },
		       { "Save", buttoncb_save },
		       { "Quit", buttoncb_quit } };
#define NBUTTONS_S (sizeof(buttons_s)/sizeof(buttons_s[0]))

int buttoncb_e_junk();
int buttoncb_e_revert();
int buttoncb_e_abort();
int buttoncb_e_done();
int buttoncb_e_name();
int buttoncb_e_clone();

BUTTON edit_buttons[] = { { "Junk level", buttoncb_e_junk },
			  { "Revert", buttoncb_e_revert },
			  { "Abort", buttoncb_e_abort },
			  { "Done", buttoncb_e_done },
			  { "Name", buttoncb_e_name },
			  { "Clone", buttoncb_e_clone } };
#define NEDITBUTTONS (sizeof(edit_buttons)/sizeof(edit_buttons[0]))

extern int help_button_h;

int buttoncb_help1();
int buttoncb_help2();
int buttoncb_help3();
int buttoncb_help4();
int buttoncb_help5();
int buttoncb_helpret();

BUTTON help_buttons[] = { { (char *)&help_button_bits[1], buttoncb_help1 },
			  { (char *)&help_button_bits[2], buttoncb_help2 },
			  { (char *)&help_button_bits[3], buttoncb_help3 },
			  { (char *)&help_button_bits[4], buttoncb_help4 },
			  { (char *)&help_button_bits[5], buttoncb_help5 },
			  { (char *)&help_button_bits[0], buttoncb_helpret } };
#define NHELPBUTTONS (sizeof(help_buttons)/sizeof(help_buttons[0]))

struct argfile_ {
  char *filename;
  struct argfile_ *link;
  } ;
typedef struct argfile_ ARGFILE;

ARGFILE *argfiles;
ARGFILE **argfile_tail = &argfiles;

LEVEL_SEG *level_segs;
LEVEL_SEG **level_segs_tail = &level_segs;
int maxlevelno;

Pixmap pic_blank;
Pixmap pic_s_b;
Pixmap pic_s_y;
Pixmap pic_r_b;
Pixmap pic_r_y;
Pixmap pic_d_b;
Pixmap pic_d_y;
Pixmap pic_p_b;
Pixmap pic_p_y;
Pixmap pic_color_b;
Pixmap pic_color_y;
Pixmap pic_color_flip;
Pixmap pic_wall;
Pixmap pic_mwall;
Pixmap pic_teleport;
Pixmap pic_mutate;
Pixmap pic_player;
Pixmap pic_stars[PIC_NSTARS];

Pixmap *pic_ptrs[B_NPIX];

char squaretype[BOARD_X][BOARD_Y];
#define SQ_BLANK    1
#define SQ_COLOR_B  2
#define SQ_COLOR_Y  3
#define SQ_COLOR_F  4
#define SQ_WALL     5
#define SQ_TELEPORT 6
#define SQ_MUTATE   7
#define SQ_STARS    8
char startype[BOARD_X][BOARD_Y]; /* used only when squaretype[][] == SQ_STARS */
char blocktype[BOARD_X][BOARD_Y];
#define BLK_COLOR 0x01
#define BLK_COLOR_B 0x00
#define BLK_COLOR_Y 0x01
#define BLK_SHAPE 0x0e
#define BLK_SHAPE_S 0x02
#define BLK_SHAPE_R 0x04
#define BLK_SHAPE_P 0x06
#define BLK_SHAPE_D 0x08
#define BLK_SHAPE_W 0x0a /* BLK_COLOR bit ignored */
#define BLK_NONE 0
int player_x;
int player_y;
char save_blocktype[BOARD_X][BOARD_Y];
int save_player_x;
int save_player_y;
char sqdamaged[BOARD_X][BOARD_Y];
char init_blocktype[BOARD_X][BOARD_Y];
int init_player_x;
int init_player_y;
int curlevelno;
LEVEL *curlevel;
char lnbuf[MAXLNLEN+1];
int lnfill;
char *levelname = "";
int levelname_len = 0;
int dispstate;
#define DS_GAME     0
#define DS_CREDITS  1
#define DS_HELP     2
#define DS_ASKLEVEL 3
#define DS_MSG      4
#define DS_EDIT     5
#define DS_LEVNAME  6
int helpscreen = 1;
int edit_damaged[B_NPIX];
int editcurs_x;
int editcurs_y;
int edit_curpic = 0;

#define EDIT_MX (EDIT_BORDER + PIC_W + EDIT_BORDER + EDIT_SPACING)
#define EDIT_MY (EDIT_BORDER + PIC_H + EDIT_BORDER + EDIT_SPACING)
#define EDIT_NCOLS (((PIC_W * BOARD_X) - EDIT_SPACING) / EDIT_MX)
#define EDIT_NROWS ((B_NPIX + EDIT_NCOLS - 1) / EDIT_NCOLS)

int junk_direction;
int junk_ascent;
int junk_descent;
#define XTE_JUNK &junk_direction,&junk_ascent,&junk_descent


/********************************************************************/
/********************************************************************/
/********************************************************************/

void bugchk(va_alist)
va_dcl
{
 va_list ap;
 char *fmt;

 fprintf(stderr,"INTERNAL ERROR: ");
 va_start(ap);
 fmt = va_arg(ap,char *);
 vfprintf(stderr,fmt,ap);
 va_end(ap);
 fprintf(stderr,"\n");
 abort();
}

void saveargv(int ac,char **av)
{
 int i;
 int nc;
 char *abuf;

 argc = ac;
 argv = (char **) malloc((ac+1)*sizeof(char *));
 nc = 1;
 for (i=0;i<ac;i++)
  { nc += strlen(av[i]) + 1;
  }
 abuf = (char *) malloc(nc*sizeof(char));
 for (i=0;i<ac;i++)
  { argv[i] = abuf;
    strcpy(abuf,av[i]);
    while (*abuf) abuf ++;
    abuf ++;
  }
 *abuf = '\0';
 argv[ac] = 0;
}

void addargfile(char *fn)
{
 ARGFILE *af;

 af = NEW(ARGFILE);
 *argfile_tail = af;
 argfile_tail = &af->link;
 af->filename = fn;
}

void handleargs(int ac,char **av)
{
 int skip;
 int errs;

 skip = 0;
 errs = 0;
 for (ac--,av++;ac;ac--,av++)
  { if (skip > 0)
     { skip --;
       continue;
     }
    if (**av != '-')
     { addargfile(*av);
       continue;
     }
    if (0)
     {
needarg:;
       fprintf(stderr,"%s: %s needs a following argument\n",argvec[0],*av);
       errs ++;
       continue;
     }
#define WANTARG() do { if (++skip >= ac) goto needarg; } while (0)
    if (!strcmp(*av,"-display"))
     { WANTARG();
       displayname = av[skip];
       continue;
     }
    if (!strcmp(*av,"-geometry"))
     { WANTARG();
       geometryspec = av[skip];
       continue;
     }
    if (!strcmp(*av,"-background") || !strcmp(*av,"-bg"))
     { WANTARG();
       background = av[skip];
       continue;
     }
    if (!strcmp(*av,"-foreground") || !strcmp(*av,"-fg"))
     { WANTARG();
       foreground = av[skip];
       continue;
     }
    if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bd"))
     { WANTARG();
       bordercstr = av[skip];
       continue;
     }
    if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw"))
     { WANTARG();
       borderwstr = av[skip];
       continue;
     }
    if (!strcmp(*av,"-name"))
     { WANTARG();
       name = av[skip];
       continue;
     }
    if (!strcmp(*av,"-iconname"))
     { WANTARG();
       iconname = av[skip];
       continue;
     }
    if (!strcmp(*av,"-selkey"))
     { WANTARG();
       selkey = av[skip];
       continue;
     }
    if (!strcmp(*av,"-font") || !strcmp(*av,"-fn"))
     { WANTARG();
       fontname = av[skip];
       continue;
     }
    if (!strcmp(*av,"-level"))
     { WANTARG();
       levelstr = av[skip];
       continue;
     }
    if (!strcmp(*av,"-file"))
     { WANTARG();
       addargfile(av[skip]);
       continue;
     }
    if (!strcmp(*av,"-bwmode"))
     { bw_flag = 1;
       continue;
     }
    if (!strcmp(*av,"-sync"))
     { syncX = 1;
       continue;
     }
    if (!strcmp(*av,"-nodeflev") || !strcmp(*av,"-nostdlev"))
     { no_deflev = 1;
       continue;
     }
    if (!strcmp(*av,"-new"))
     { new_flag = 1;
       continue;
     }
#undef WANTARG
    fprintf(stderr,"%s: unrecognized option `%s'\n",argvec[0],*av);
    errs ++;
  }
 if (errs)
  { exit(1);
  }
}

#define MAXDIST 1000

int strdist(char *s1,char *s2)
{
 int n1;
 int n2;
 char c1;
 char c2;
 int d;

 d = 0;
 while (1)
  { n1 = 0;
    for (;*s1&&!isalpha(*s1);s1++) n1 ++;
    n2 = 0;
    for (;*s2&&!isalpha(*s2);s2++) n2 ++;
    d += abs(n1-n2);
    c1 = *s1;
    c2 = *s2;
    if (!c1 && !c2) return(d);
    if (c1 != c2) d ++;
    if (c1) s1 ++;
    if (c2) s2 ++;
  }
}

void setlevel(char *s)
{
 int alldigits;
 char *cp;
 int bestdist;
 int d;
 LEVEL_SEG *ls;
 int i;

 if (*s == '\0')
  { curlevelno = 0;
    return;
  }
 alldigits = 1;
 for (cp=s;*cp;cp++)
  { if (! isdigit(*cp))
     { alldigits = 0;
       break;
     }
  }
 if (alldigits)
  { curlevelno = atoi(s);
  }
 else
  { curlevelno = 0;
    bestdist = MAXDIST + 1;
    for (ls=level_segs;ls;ls=ls->link)
     { for (i=0;i<ls->num;i++)
	{ d = strdist(s,ls->levels[i]->name);
	  if (d < bestdist)
	   { bestdist = d;
	     curlevelno = ls->first + i;
	   }
	}
     }
  }
}

void maybeset(char **strp,char *str)
{
 if (str && !*strp) *strp = str;
}

LEVEL *fread_level(f)
FILE *f;
{
 static LEVEL *l = 0;
 int namelen;
 int i;
 int c;
 int x;
 int y;

 if (0)
  {
fail:;
    return(0);
  }
 if (l == 0)
  { l = NEW(LEVEL);
    l->name = 0;
  }
 if (l->name)
  { free(l->name);
    l->name = 0;
  }
 if (fscanf(f,"%d",&namelen) != 1) goto fail;
 l->name = malloc(namelen);
 do
  { c = getc(f);
    if (c == EOF) goto fail;
  } while (isspace(c));
 /* ignore errors in this next loop; eof/error gets noticed shortly */
 for (i=0;i<namelen;i++) l->name[i] = getc(f);
 l->namelen = namelen;
 for (y=0;y<BOARD_Y;y++)
  { for (x=0;x<BOARD_X;x++)
     { do
	{ c = getc(f);
	  if (c == EOF) goto fail;
	} while (isspace(c));
       i = getc(f);
#define TWOCHAR(c1,c2) ((c==c1)&&(i==c2))
	    if (TWOCHAR('_','_')) c = PIC_BLANK;
       else if (TWOCHAR('Q','B')) c = PIC_S_B;
       else if (TWOCHAR('Q','Y')) c = PIC_S_Y;
       else if (TWOCHAR('R','B')) c = PIC_R_B;
       else if (TWOCHAR('R','Y')) c = PIC_R_Y;
       else if (TWOCHAR('P','B')) c = PIC_P_B;
       else if (TWOCHAR('P','Y')) c = PIC_P_Y;
       else if (TWOCHAR('D','B')) c = PIC_D_B;
       else if (TWOCHAR('D','Y')) c = PIC_D_Y;
       else if (TWOCHAR('C','B')) c = PIC_COLOR_B;
       else if (TWOCHAR('C','Y')) c = PIC_COLOR_Y;
       else if (TWOCHAR('C','F')) c = PIC_COLOR_FLIP;
       else if (TWOCHAR('W','L')) c = PIC_WALL;
       else if (TWOCHAR('M','W')) c = PIC_MWALL;
       else if (TWOCHAR('T','L')) c = PIC_TELEPORT;
       else if (TWOCHAR('M','U')) c = PIC_MUTATE;
       else if (TWOCHAR('P','L')) c = PIC_PLAYER;
       else if (TWOCHAR('S','A')) c = PIC_STARS_A;
       else if (TWOCHAR('S','B')) c = PIC_STARS_B;
       else if (TWOCHAR('S','C')) c = PIC_STARS_C;
       else if (TWOCHAR('S','D')) c = PIC_STARS_D;
       else if (TWOCHAR('S','E')) c = PIC_STARS_E;
       else if (TWOCHAR('S','F')) c = PIC_STARS_F;
       else if (TWOCHAR('S','G')) c = PIC_STARS_G;
       else if (TWOCHAR('S','H')) c = PIC_STARS_H;
       else if (TWOCHAR('S','I')) c = PIC_STARS_I;
       else if (TWOCHAR('S','J')) c = PIC_STARS_J;
       else goto fail;
#undef TWOCHAR
       l->layout[y][x] = c;
     }
  }
  { LEVEL *rv;
    rv = l;
    l = 0;
    return(rv);
  }
}

int read_level_file(fn,levvp)
char *fn;
LEVEL ***levvp;
{
 struct level_list_ {
   struct level_list_ *link;
   LEVEL *level;
   } ;
 typedef struct level_list_ LEVEL_LIST;
 LEVEL_LIST *levs;
 LEVEL_LIST **levtail;
 LEVEL_LIST *ll;
 int nlevs;
 LEVEL *lev;
 FILE *f;
 int i;

 f = fopen(fn,"r");
 if (f == 0)
  { if (new_flag)
     { int x;
       int y;
newlevelfile:;
       nlevs = 1;
       lev = NEW(LEVEL);
       *levvp = (LEVEL **) malloc(sizeof(LEVEL *));
       **levvp = lev;
       lev->namelen = 8;
       lev->name = malloc(8);
       bcopy("Untitled",lev->name,8);
       for (y=0;y<BOARD_Y;y++)
	{ for (x=0;x<BOARD_X;x++)
	   { lev->layout[y][x] = PIC_BLANK;
	   }
	}
       lev->layout[0][0] = PIC_PLAYER;
       lev->layout[0][1] = PIC_S_B;
       lev->layout[0][2] = PIC_S_Y;
       return(-1);
     }
    else
     { fprintf(stderr,"%s: can't read %s\n",argvec[0],fn);
       return(0);
     }
  }
 levtail = &levs;
 nlevs = 0;
 while ((lev=fread_level(f))!=0)
  { ll = NEW(LEVEL_LIST);
    *levtail = ll;
    levtail = &ll->link;
    ll->level = lev;
    nlevs ++;
  }
 fclose(f);
 *levtail = 0;
 *levvp = (LEVEL **) malloc(nlevs*sizeof(LEVEL *));
 for (i=0,ll=levs;i<nlevs;i++,ll=ll->link) levvp[0][i] = ll->level;
 while (levs)
  { ll = levs;
    levs = ll->link;
    OLD(ll);
  }
 if ((nlevs == 0) && new_flag) goto newlevelfile;
 return(nlevs);
}

void setup_levels(void)
{
 LEVEL_SEG *ls;
 ARGFILE *af;
 int lastlevel;
 int i;

 if (! no_deflev)
  { ls = NEW(LEVEL_SEG);
    *level_segs_tail = ls;
    level_segs_tail = &ls->link;
    ls->first = 1;
    ls->num = N_DEF_LEVELS;
    ls->last = N_DEF_LEVELS;
    ls->source = 0;
    ls->levels = (LEVEL **) malloc(N_DEF_LEVELS*sizeof(LEVEL *));
    ls->levels[0] = (LEVEL *) malloc(N_DEF_LEVELS*sizeof(LEVEL));
    ls->dirty = 0;
    for (i=1;i<N_DEF_LEVELS;i++) ls->levels[i] = ls->levels[0] + i;
    for (i=0;i<N_DEF_LEVELS;i++)
     { LEVEL *l;
       l = ls->levels[i];
       l->levelno = i + 1;
       l->seg = ls;
       l->name = b_l_builtin[i].name;
       bcopy((char *)/*&*/b_l_builtin[i].layout,(char *)/*&*/l->layout,sizeof(l->layout));
       l->namelen = strlen(l->name);
     }
    lastlevel = N_DEF_LEVELS;
  }
 else
  { lastlevel = 0;
  }
 *argfile_tail = 0;
 ls = 0;
 for (af=argfiles;af;af=af->link)
  { if (! ls)
     { ls = NEW(LEVEL_SEG);
     }
    ls->first = lastlevel + 1;
    ls->num = read_level_file(af->filename,&ls->levels);
    ls->dirty = 0;
    if (ls->num < 0)
     { ls->num = - ls->num;
       ls->dirty = 1;
     }
    if (ls->num > 0)
     { lastlevel = ls->first + ls->num - 1;
       ls->last = lastlevel;
       ls->source = af->filename;
       for (i=0;i<ls->num;i++)
	{ LEVEL *l;
	  l = ls->levels[i];
	  l->levelno = ls->first + i;
	  l->seg = ls;
	}
       *level_segs_tail = ls;
       level_segs_tail = &ls->link;
       ls = 0;
     }
  }
 if (ls) OLD(ls);
 *level_segs_tail = 0;
 if (level_segs == 0)
  { fprintf(stderr,"%s: need to get some levels from somewhere!\n",argvec[0]);
    exit(1);
  }
 maxlevelno = lastlevel;
}

void setup_db(void)
{
 char *str;
 char *home;
 XrmDatabase db2;

 db = XrmGetStringDatabase(defaults);
 str = XResourceManagerString(disp);
 if (str)
  { db2 = XrmGetStringDatabase(str);
    XrmMergeDatabases(db2,&db);
  }
 else
  { home = getenv("HOME");
    if (home)
     { str = malloc(strlen(home)+1+10+1);
       sprintf(str,"%s/.Xdefaults",home);
       db2 = XrmGetFileDatabase(str);
       if (db2)
	{ XrmMergeDatabases(db2,&db);
	}
       free(str);
     }
  }
}

char *get_default_value(name,class)
char *name;
char *class;
{
 char *type;
 XrmValue value;

 if (XrmGetResource(db,name,class,&type,&value) == False) return(0);
 return(value.addr);
}

void getcolor(XColor *col)
{
 while (1)
  { if (XAllocColor(disp,wincmap,col) == 0)
     { if (wincmap != defcmap)
	{ fprintf(stderr,"%s: can't allocate colormap cell\n",argvec[0]);
	  exit(1);
	}
       wincmap = XCopyColormapAndFree(disp,wincmap);
       continue;
     }
    break;
  }
}

void setup_color(char *str,XColor *col)
{
 if (XParseColor(disp,wincmap,str,col) == 0)
  { fprintf(stderr,"%s: bad color `%s'\n",argvec[0],str);
    exit(1);
  }
 getcolor(col);
}

void setup_visual(void)
{
 int i;
 XVisualInfo *vinf;
 int nvinf;
 XVisualInfo *v;
 XVisualInfo template;

 if (0)
  {
bwmode:;
    bw_mode = 1;
    visual = XDefaultVisualOfScreen(scr);
    defcmap = XDefaultColormapOfScreen(scr);
    defgc = XDefaultGCOfScreen(scr);
    depth = XDefaultDepthOfScreen(scr);
    return;
  }
 if (bw_flag) goto bwmode;
 bw_mode = 0;
 template.screen = XScreenNumberOfScreen(scr);
 template.visualid = XVisualIDFromVisual(XDefaultVisualOfScreen(scr));
 vinf = XGetVisualInfo(disp,VisualIDMask|VisualScreenMask,&template,&nvinf);
 if (nvinf != 1) goto bwmode; /* can this happen? */
 if ( ((vinf->class == PseudoColor) && (vinf->colormap_size > B_NCOLORS+3)) ||
      ((vinf->class == DirectColor) && (vinf->colormap_size > B_NCOLORS/3)) ||
      ((vinf->class == StaticColor) && (vinf->bits_per_rgb >= 4)) ||
      ((vinf->class == TrueColor) && (vinf->bits_per_rgb >= 4)) )
  { visual = vinf->visual;
    defcmap = XDefaultColormapOfScreen(scr);
    defgc = XDefaultGCOfScreen(scr);
    depth = XDefaultDepthOfScreen(scr);
    XFree((char *)vinf);
    return;
  }
 template.screen = XScreenNumberOfScreen(scr);
 template.class = PseudoColor;
 vinf = XGetVisualInfo(disp,VisualScreenMask|VisualClassMask,&template,&nvinf);
 if (nvinf < 1) goto bwmode;
 v = 0;
 for (i=0;i<nvinf;i++)
  { if (vinf[i].colormap_size >= B_NCOLORS+3)
     { if ((v == 0) || (vinf[i].colormap_size > v->colormap_size)) v = &vinf[i];
     }
  }
 if (v == 0) goto bwmode;
 visual = v->visual;
 defcmap = None;
  { Pixmap pm;
    /* Grrr - shouldn't have to bother creating the pixmap! */
    pm = XCreatePixmap(disp,rootwin,1,1,v->depth);
    defgc = XCreateGC(disp,pm,0L,(XGCValues *)0);
    XFreePixmap(disp,pm);
  }
 depth = v->depth;
 XFree((char *)vinf);
}

void setup_font(void)
{
 int c;
 XCharStruct d;

 font = 0;
 deffont = 0;
 if (fontname)
  { font = XLoadQueryFont(disp,fontname);
    if (! font)
     { fprintf(stderr,"%s: can't load font %s, using server default\n",argvec[0],fontname);
     }
    else
     { fontid = font->fid;
     }
  }
 if (! font)
  { font = XQueryFont(disp,XGContextFromGC(defgc));
    deffont = 1;
    fontid = None;
  }
 for (c=0;c<10;c++)
  { XTextExtents(font,"0123456789"+c,1,XTE_JUNK,&d);
    if (c == 0)
     { maxdigitsize = d;
     }
    else
     {
#define TST(field,cmp) if (d.field cmp maxdigitsize.field) maxdigitsize.field = d.field
       TST(lbearing,<);
       TST(rbearing,>);
       TST(ascent,>);
       TST(descent,>);
       TST(width,>);
#undef TST
     }
  }
}

void setup_colors(void)
{
 wincmap = (defcmap != None) ? defcmap : XCreateColormap(disp,rootwin,visual,AllocNone);
 if (bw_mode)
  { blackcolor.red = 0;
    blackcolor.green = 0;
    blackcolor.blue = 0;
    getcolor(&blackcolor);
    whitecolor.red = 65535;
    whitecolor.green = 65535;
    whitecolor.blue = 65535;
    getcolor(&whitecolor);
  }
 else
  { int i;
    for (i=0;i<B_NCOLORS;i++)
     { colors[i].red = b_p_colors[i][0];
       colors[i].green = b_p_colors[i][1];
       colors[i].blue = b_p_colors[i][2];
       getcolor(&colors[i]);
     }
  }
 setup_color(background,&bgcolor);
 setup_color(foreground,&fgcolor);
 setup_color(bordercstr,&bdcolor);
}

void setup_numbers(void)
{
 if (borderwstr) borderwidth = atoi(borderwstr);
}

Pixmap pmsetup(inx)
int inx;
{
 Pixmap rv;

 if (bw_mode)
  { static XImage *i = 0;
    static unsigned char buf[((PIC_W+7)>>3)*PIC_H];
    static GC gc;
    int x;
    int y;
    int n;
    int acc;
    unsigned char *bp;
    if (i == 0)
     { i = XCreateImage(disp,visual,1,XYBitmap,0,(char *)&buf[0],PIC_W,PIC_H,8,(PIC_W+7)>>3);
       i->bitmap_unit = 8;
       i->bitmap_bit_order = MSBFirst;
       rv = XCreatePixmap(disp,rootwin,1,1,1);
       gc = XCreateGC(disp,rv,0L,(XGCValues *)0);
       XFreePixmap(disp,rv);
       XSetBackground(disp,gc,0L);
       XSetForeground(disp,gc,1L);
     }
    acc = 0;
    bp = &buf[0];
    for (y=0;y<PIC_H;y++)
     { n = 0;
       for (x=0;x<PIC_W;x++)
	{ if (n > 7)
	   { *bp++ = acc;
	     n = 0;
	   }
	  acc = (acc << 1) | b_p_pix_bw[inx][y][x];
	  n ++;
	}
       if (n > 0)
	{ *bp++ = acc << (8 - n);
	}
     }
    rv = XCreatePixmap(disp,rootwin,PIC_W,PIC_H,1);
    XPutImage(disp,rv,gc,i,0,0,0,0,PIC_W,PIC_H);
  }
 else if (depth == 8)
  { static XImage *i = 0;
    static unsigned char buf[PIC_W*PIC_H];
    static GC gc;
    int x;
    int y;
    unsigned char *bp;
    if (i == 0)
     { i = XCreateImage(disp,visual,8,ZPixmap,0,(char *)&buf[0],PIC_W,PIC_H,8,PIC_W);
       i->bitmap_unit = 8;
       rv = XCreatePixmap(disp,rootwin,1,1,8);
       gc = XCreateGC(disp,rv,0L,(XGCValues *)0);
       XFreePixmap(disp,rv);
     }
    bp = &buf[0];
    for (y=0;y<PIC_H;y++)
     { for (x=0;x<PIC_W;x++)
	{ *bp++ = colors[ (int)b_p_pix_color[inx][y][x]].pixel;
	}
     }
    rv = XCreatePixmap(disp,rootwin,PIC_W,PIC_H,8);
    XPutImage(disp,rv,gc,i,0,0,0,0,PIC_W,PIC_H);
  }
 else
  { static XImage *i = 0;
    static unsigned char buf[((PIC_W+7)>>3)*PIC_H];
    static GC gc1;
    static GC gcn;
    static Pixmap tmp;
    int x;
    int y;
    int n;
    int acc;
    int m;
    int c;
    unsigned char *bp;
    if (i == 0)
     { i = XCreateImage(disp,visual,1,XYBitmap,0,(char *)&buf[0],PIC_W,PIC_H,8,(PIC_W+7)>>3);
       i->bitmap_unit = 8;
       i->bitmap_bit_order = MSBFirst;
       tmp = XCreatePixmap(disp,rootwin,PIC_W,PIC_H,1);
       gc1 = XCreateGC(disp,tmp,0L,(XGCValues *)0);
       XSetBackground(disp,gc1,0L);
       XSetForeground(disp,gc1,1L);
       rv = XCreatePixmap(disp,rootwin,1,1,depth);
       gcn = XCreateGC(disp,rv,0L,(XGCValues *)0);
       XSetBackground(disp,gcn,0L);
       XFreePixmap(disp,rv);
     }
    rv = XCreatePixmap(disp,rootwin,PIC_W,PIC_H,depth);
    XSetForeground(disp,gcn,0L);
    XSetFunction(disp,gcn,GXcopy);
    XFillRectangle(disp,rv,gcn,0,0,PIC_W,PIC_H);
    XSetFunction(disp,gcn,GXor);
    for (c=0;c<B_NCOLORS;c++)
     { m = 0;
       acc = 0;
       bp = &buf[0];
       for (y=0;y<PIC_H;y++)
	{ n = 0;
	  for (x=0;x<PIC_W;x++)
	   { if (n > 7)
	      { *bp++ = acc;
		n = 0;
	      }
	     acc <<= 1;
	     if (b_p_pix_color[inx][y][x] == c)
	      { acc |= 1;
		m ++;
	      }
	     n ++;
	   }
	  if (n > 0)
	   { *bp++ = acc << (8 - n);
	   }
	}
       if (m > 0)
	{ XPutImage(disp,tmp,gc1,i,0,0,0,0,PIC_W,PIC_H);
	  XSetForeground(disp,gcn,colors[c].pixel);
	  XCopyPlane(disp,tmp,rv,gcn,0,0,PIC_W,PIC_H,0,0,1L);
	}
     }
  }
 return(rv);
}

void setup_wingc(void)
{
 Pixmap pm;

 pm = XCreatePixmap(disp,rootwin,1,1,depth);
 wingc = XCreateGC(disp,pm,0L,(XGCValues *)0);
 XFreePixmap(disp,pm);
 XGetGCValues(disp,wingc,GCFunction|GCForeground|GCBackground|GCLineWidth|GCFillStyle|GCTileStipXOrigin|GCTileStipYOrigin|GCClipXOrigin|GCClipYOrigin|GCPlaneMask,&winshadow);
 winshadow.tile = None; /* force change when first set */
 winshadow.stipple = None; /* force change when first set */
 winshadow.clip_mask = None; /* force change when first set */
 winshadow.font = None; /* ie, the default font */
}

void setup_gc(va_alist)
va_dcl
{
 va_list ap;
 long int bit;
 int setdeffont;
 unsigned long int gcmask;
 GC gc;
 XGCValues *shadow;
 XGCValues gcval;

 va_start(ap);
 gc = wingc;
 shadow = &winshadow;
 setdeffont = 0;
 gcmask = 0;
 while (1)
  { bit = va_arg(ap,long int);
    switch (bit)
     { default:
	  fprintf(stderr,"Bad bit 0x%lx to setup_gc\n",bit);
	  abort();
	  break;
       case 0:
	  va_end(ap);
	  if (gcmask != 0) XChangeGC(disp,gc,gcmask,&gcval);
	  if (setdeffont) XCopyGC(disp,defgc,GCFont,gc);
	  return;
	  break;
#define CASE(mask,type,field)				\
       case mask:					\
	  gcval.field = va_arg(ap,type);		\
	  if (gcval.field != shadow->field)		\
	   { gcmask |= mask;				\
	     shadow->field = gcval.field;		\
	   }						\
	  break;
       CASE(GCFunction,int,function)
       CASE(GCForeground,unsigned long int,foreground)
       CASE(GCBackground,unsigned long int,background)
       CASE(GCLineWidth,int,line_width)
       CASE(GCTile,Pixmap,tile)
       CASE(GCStipple,Pixmap,stipple)
       CASE(GCFillStyle,int,fill_style)
       CASE(GCTileStipXOrigin,int,ts_x_origin)
       CASE(GCTileStipYOrigin,int,ts_y_origin)
       CASE(GCClipMask,Pixmap,clip_mask)
       CASE(GCClipXOrigin,int,clip_x_origin)
       CASE(GCClipYOrigin,int,clip_y_origin)
       CASE(GCPlaneMask,unsigned long int,plane_mask)
       case GCFont:
	  gcval.font = va_arg(ap,Font);
	  if (gcval.font != shadow->font)
	   { if (gcval.font == None)
	      { setdeffont = 1;
		gcmask &= ~GCFont;
	      }
	     else 
	      { gcmask |= GCFont;
		setdeffont = 0;
	      }
	     shadow->font = gcval.font;
	   }
	  break;
     }
  }
}

void setup_pixmaps(void)
{
 int i;

#define FOO(var,inx) var = pmsetup(inx); pic_ptrs[inx] = &var;
 FOO(pic_blank,PIC_BLANK);
 FOO(pic_s_b,PIC_S_B);
 FOO(pic_s_y,PIC_S_Y);
 FOO(pic_r_b,PIC_R_B);
 FOO(pic_r_y,PIC_R_Y);
 FOO(pic_d_b,PIC_D_B);
 FOO(pic_d_y,PIC_D_Y);
 FOO(pic_p_b,PIC_P_B);
 FOO(pic_p_y,PIC_P_Y);
 FOO(pic_color_b,PIC_COLOR_B);
 FOO(pic_color_y,PIC_COLOR_Y);
 FOO(pic_color_flip,PIC_COLOR_FLIP);
 FOO(pic_wall,PIC_WALL);
 FOO(pic_mwall,PIC_MWALL);
 FOO(pic_teleport,PIC_TELEPORT);
 FOO(pic_mutate,PIC_MUTATE);
 FOO(pic_player,PIC_PLAYER);
 for (i=0;i<PIC_NSTARS;i++)
  { FOO(pic_stars[i],PIC_STARS_A+i);
  }
#undef FOO
 gray50 = XCreatePixmap(disp,rootwin,2,2,depth);
 setup_gc(GCForeground,whitecolor.pixel,GCFunction,GXcopy,0L);
 XDrawPoint(disp,gray50,wingc,0,0);
 XDrawPoint(disp,gray50,wingc,1,1);
 setup_gc(GCForeground,blackcolor.pixel,0L);
 XDrawPoint(disp,gray50,wingc,0,1);
 XDrawPoint(disp,gray50,wingc,1,0);
}

void setup_sizes(void)
{
 LEVEL_SEG *ls;
 LEVEL *l;
 int i;
 int n;
 XCharStruct maxlns;
 XCharStruct d;

 n = 0;
 for (ls=level_segs;ls;ls=ls->link)
  { for (i=0;i<ls->num;i++)
     { l = ls->levels[i];
       XTextExtents(font,l->name,l->namelen,XTE_JUNK,&l->namesize);
       if (l->namelen > n) n = l->namelen;
       if ((i == 0) && (ls == level_segs))
	{ maxlns = l->namesize;
	}
       else
	{
#define TST(field,cmp) if (l->namesize.field cmp maxlns.field) maxlns.field = l->namesize.field
	  TST(lbearing,<);
	  TST(rbearing,>);
	  TST(ascent,>);
	  TST(descent,>);
	  TST(width,>);
#undef TST
	}
     }
  }
 levprompt_len = n;
 for (n=maxlevelno,i=1;n>=10;n/=10,i++) ;
 if (i > levprompt_len) levprompt_len = i;
 d = maxdigitsize;
 d.width *= i;
#define TST(field,cmp) if (d.field cmp maxlns.field) maxlns.field = d.field
 TST(lbearing,<);
 TST(rbearing,>);
 TST(ascent,>);
 TST(descent,>);
 TST(width,>);
#undef TST
 XTextExtents(font,levprompt_prompt,strlen(levprompt_prompt),XTE_JUNK,&d);
 levprompt_w = DIALOG_BORDER + DIALOG_MARGIN + d.width + maxlns.width + DIALOG_MARGIN + DIALOG_BORDER;
 levprompt_h = DIALOG_BORDER + DIALOG_MARGIN + font->ascent + font->descent + DIALOG_MARGIN + DIALOG_BORDER;
 levprompt_xoff = DIALOG_BORDER + DIALOG_MARGIN + d.width;
 levprompt_xclr = DIALOG_BORDER + DIALOG_MARGIN + d.width + maxlns.lbearing;
}

void setup_buttons(void)
{
 int i;
 BUTTON *b;
 int a;
 int d;

 button_h = 0;
 for (i=0;i<NBUTTONS_NO_S;i++)
  { b = &buttons_no_s[i];
    XTextExtents(font,b->text,strlen(b->text),XTE_JUNK,&b->textbound);
    d = (b->textbound.descent > font->descent) ? b->textbound.descent : font->descent;
    a = (b->textbound.ascent > font->ascent) ? b->textbound.ascent : font->ascent;
    b->w = BUTTON_BORDER + BUTTON_MARGIN + b->textbound.width + BUTTON_MARGIN + BUTTON_BORDER;
    b->h = BUTTON_BORDER + BUTTON_MARGIN + a + d + BUTTON_MARGIN + BUTTON_BORDER;
    b->textx = BUTTON_BORDER + BUTTON_MARGIN;
    b->texty = BUTTON_BORDER + BUTTON_MARGIN + a;
    if (b->h > button_h) button_h = b->h;
  }
 for (i=0;i<NBUTTONS_S;i++)
  { b = &buttons_s[i];
    XTextExtents(font,b->text,strlen(b->text),XTE_JUNK,&b->textbound);
    d = (b->textbound.descent > font->descent) ? b->textbound.descent : font->descent;
    a = (b->textbound.ascent > font->ascent) ? b->textbound.ascent : font->ascent;
    b->w = BUTTON_BORDER + BUTTON_MARGIN + b->textbound.width + BUTTON_MARGIN + BUTTON_BORDER;
    b->h = BUTTON_BORDER + BUTTON_MARGIN + a + d + BUTTON_MARGIN + BUTTON_BORDER;
    b->textx = BUTTON_BORDER + BUTTON_MARGIN;
    b->texty = BUTTON_BORDER + BUTTON_MARGIN + a;
    if (b->h > button_h) button_h = b->h;
  }
 for (i=0;i<NHELPBUTTONS;i++)
  { unsigned char *ucp;
    b = &help_buttons[i];
    ucp = (unsigned char *) * (char **) b->text;
    b->w = ucp[0];
    b->h = help_button_h;
    b->x = (ucp[1] << 8) | ucp[2];
    b->y = (ucp[3] << 8) | ucp[4];
  }
 for (i=0;i<NEDITBUTTONS;i++)
  { b = &edit_buttons[i];
    XTextExtents(font,b->text,strlen(b->text),XTE_JUNK,&b->textbound);
    d = (b->textbound.descent > font->descent) ? b->textbound.descent : font->descent;
    a = (b->textbound.ascent > font->ascent) ? b->textbound.ascent : font->ascent;
    b->w = BUTTON_BORDER + BUTTON_MARGIN + b->textbound.width + BUTTON_MARGIN + BUTTON_BORDER;
    b->h = BUTTON_BORDER + BUTTON_MARGIN + a + d + BUTTON_MARGIN + BUTTON_BORDER;
    b->textx = BUTTON_BORDER + BUTTON_MARGIN;
    b->texty = BUTTON_BORDER + BUTTON_MARGIN + a;
    if (b->h > button_h) button_h = b->h;
  }
}

void setup_windows(void)
{
 int x;
 int y;
 int w;
 int h;
 int bits;
 int i;
 BUTTON *b;
 unsigned long int attrmask;
 XSetWindowAttributes attr;
 XTextProperty wn_prop;
 XTextProperty in_prop;
 XSizeHints normal_hints;
 XWMHints wm_hints;
 XClassHint class_hints;

 w = borderwidth + SPACING + (BOARD_X * PIC_W) + SPACING + borderwidth;
 h = borderwidth + SPACING + (BOARD_Y * PIC_H) + SPACING + borderwidth + SPACING + (2 * (font->ascent + font->descent)) + SPACING + button_h + SPACING + borderwidth;
 x = (width - w) / 2;
 y = (height - h) / 2;
 normal_hints.min_width = w - (2 * borderwidth);
 normal_hints.min_height = h - (2 * borderwidth);
 bits = XParseGeometry(geometryspec,&x,&y,&w,&h);
 if (bits & XNegative) x = width + x - w;
 if (bits & YNegative) y = height + y - h;
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.border_pixel = bdcolor.pixel;
 attrmask |= CWBorderPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = StructureNotifyMask | KeyPressMask;
 attrmask |= CWEventMask;
 attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | PointerMotionMask;
 attrmask |= CWDontPropagate;
 w -= 2 * borderwidth;
 h -= 2 * borderwidth;
 topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visual,attrmask,&attr);
 wn_prop.value = (unsigned char *) name;
 wn_prop.encoding = XA_STRING;
 wn_prop.format = 8;
 wn_prop.nitems = strlen((char *)wn_prop.value);
 in_prop.value = (unsigned char *) iconname;
 in_prop.encoding = XA_STRING;
 in_prop.format = 8;
 in_prop.nitems = strlen((char *)in_prop.value);
 normal_hints.flags = PMinSize | PResizeInc;
 normal_hints.x = x;
 normal_hints.y = y;
 normal_hints.flags |= (bits & (XValue|YValue)) ? USPosition : PPosition;
 normal_hints.width = w;
 normal_hints.height = h;
 normal_hints.flags |= (bits & (WidthValue|HeightValue)) ? USSize : PSize;
 normal_hints.width_inc = 1;
 normal_hints.height_inc = 1;
 wm_hints.flags = InputHint;
 wm_hints.input = True;
 class_hints.res_name = "xblockade";
 class_hints.res_class = "Game";
 XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,&normal_hints,&wm_hints,&class_hints);
 if (wincmap != defcmap) XSetWindowColormap(disp,topwin,wincmap);
 if (selkey) XChangeProperty(disp,topwin,XInternAtom(disp,"wm_selkey",False),XA_STRING,8,PropModeReplace,(unsigned char *)selkey,strlen(selkey));
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask | ButtonMotionMask;
 attrmask |= CWEventMask;
 gamewin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,gamewin);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask;
 attrmask |= CWEventMask;
 levprompt_win = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask;
 attrmask |= CWEventMask;
 msgwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 attrmask = 0;
 attr.background_pixmap = gray50;
 attrmask |= CWBackPixmap;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask;
 attrmask |= CWEventMask;
 editwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask;
 attrmask |= CWEventMask;
 textpanelwin = XCreateWindow(disp,gamewin,0,0,PIC_W*BOARD_X,PIC_H*BOARD_Y,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask;
 attrmask |= CWEventMask;
 creditwin = XCreateWindow(disp,textpanelwin,0,0,PIC_W*BOARD_X,PIC_H*BOARD_Y,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,creditwin);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask;
 attrmask |= CWEventMask;
 helpwin = XCreateWindow(disp,textpanelwin,0,0,PIC_W*BOARD_X,PIC_H*BOARD_Y,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,helpwin);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask | ButtonPressMask;
 attrmask |= CWEventMask;
 helppanelwin = XCreateWindow(disp,helpwin,0,0,PIC_W*BOARD_X,PIC_H*BOARD_Y,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,helppanelwin);
 for (i=0;i<N_HELP;i++)
  { attrmask = 0;
    attr.background_pixel = bgcolor.pixel;
    attrmask |= CWBackPixel;
    attr.backing_store = NotUseful;
    attrmask |= CWBackingStore;
    attr.event_mask = ExposureMask | ButtonPressMask;
    attrmask |= CWEventMask;
    helpwins[i] = XCreateWindow(disp,helppanelwin,0,0,PIC_W*BOARD_X,PIC_H*BOARD_Y,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
    XMapWindow(disp,helpwins[i]);
  }
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = ExposureMask;
 attrmask |= CWEventMask;
 textwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,textwin);
 attrmask = 0;
 attr.background_pixel = bdcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 line1win = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 attrmask = 0;
 attr.background_pixel = bdcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 line2win = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,line2win);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 g_buttonwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,g_buttonwin);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 g_buttonwin_s = XCreateWindow(disp,g_buttonwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,g_buttonwin_s);
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 g_buttonwin_no_s = XCreateWindow(disp,g_buttonwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,g_buttonwin_no_s);
 for (i=0;i<NBUTTONS_NO_S;i++)
  { b = &buttons_no_s[i];
    attrmask = 0;
    attr.background_pixel = bgcolor.pixel;
    attrmask |= CWBackPixel;
    attr.backing_store = NotUseful;
    attrmask |= CWBackingStore;
    attr.event_mask = ExposureMask | ButtonPressMask;
    attrmask |= CWEventMask;
    b->win = XCreateWindow(disp,g_buttonwin_no_s,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
    XMapWindow(disp,b->win);
  }
 for (i=0;i<NBUTTONS_S;i++)
  { b = &buttons_s[i];
    attrmask = 0;
    attr.background_pixel = bgcolor.pixel;
    attrmask |= CWBackPixel;
    attr.backing_store = NotUseful;
    attrmask |= CWBackingStore;
    attr.event_mask = ExposureMask | ButtonPressMask;
    attrmask |= CWEventMask;
    b->win = XCreateWindow(disp,g_buttonwin_s,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
    XMapWindow(disp,b->win);
  }
 for (i=0;i<NHELPBUTTONS;i++)
  { b = &help_buttons[i];
    attrmask = 0;
    attr.background_pixel = bgcolor.pixel;
    attrmask |= CWBackPixel;
    attr.backing_store = NotUseful;
    attrmask |= CWBackingStore;
    attr.event_mask = ExposureMask | ButtonPressMask;
    attrmask |= CWEventMask;
    b->win = XCreateWindow(disp,helpwin,b->x,b->y,b->w,b->h,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
    XMapWindow(disp,b->win);
  }
 attrmask = 0;
 attr.background_pixel = bgcolor.pixel;
 attrmask |= CWBackPixel;
 attr.backing_store = NotUseful;
 attrmask |= CWBackingStore;
 attr.event_mask = NoEventMask;
 attrmask |= CWEventMask;
 e_buttonwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
 XMapWindow(disp,e_buttonwin);
 for (i=0;i<NEDITBUTTONS;i++)
  { b = &edit_buttons[i];
    attrmask = 0;
    attr.background_pixel = bgcolor.pixel;
    attrmask |= CWBackPixel;
    attr.backing_store = NotUseful;
    attrmask |= CWBackingStore;
    attr.event_mask = ExposureMask | ButtonPressMask;
    attrmask |= CWEventMask;
    b->win = XCreateWindow(disp,e_buttonwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr);
    XMapWindow(disp,b->win);
  }
 XLowerWindow(disp,helppanelwin);
 XLowerWindow(disp,e_buttonwin);
 XRaiseWindow(disp,gamewin);
 XMapRaised(disp,topwin);
 resize(w,h);
}

void setup_game(void)
{
 curlevelno = 0;
 if (levelstr) setlevel(levelstr);
 if (curlevelno < 1) curlevelno = 1;
 initlevel();
 dispstate = DS_GAME;
}

int set_sq_bl(int bi,int *sqp,int *blp,int *stp)
{
 *sqp = SQ_BLANK;
 *blp = BLK_NONE;
 switch (bi)
  { case PIC_BLANK:
       break;
    case PIC_S_B:
       *blp = BLK_SHAPE_S | BLK_COLOR_B;
       break;
    case PIC_S_Y:
       *blp = BLK_SHAPE_S | BLK_COLOR_Y;
       break;
    case PIC_R_B:
       *blp = BLK_SHAPE_R | BLK_COLOR_B;
       break;
    case PIC_R_Y:
       *blp = BLK_SHAPE_R | BLK_COLOR_Y;
       break;
    case PIC_D_B:
       *blp = BLK_SHAPE_D | BLK_COLOR_B;
       break;
    case PIC_D_Y:
       *blp = BLK_SHAPE_D | BLK_COLOR_Y;
       break;
    case PIC_P_B:
       *blp = BLK_SHAPE_P | BLK_COLOR_B;
       break;
    case PIC_P_Y:
       *blp = BLK_SHAPE_P | BLK_COLOR_Y;
       break;
    case PIC_COLOR_B:
       *sqp = SQ_COLOR_B;
       break;
    case PIC_COLOR_Y:
       *sqp = SQ_COLOR_Y;
       break;
    case PIC_COLOR_FLIP:
       *sqp = SQ_COLOR_F;
       break;
    case PIC_WALL:
       *sqp = SQ_WALL;
       break;
    case PIC_MWALL:
       *blp = BLK_SHAPE_W;
       break;
    case PIC_TELEPORT:
       *sqp = SQ_TELEPORT;
       break;
    case PIC_MUTATE:
       *sqp = SQ_MUTATE;
       break;
    case PIC_PLAYER:
       return(1);
       break;
    default:
       *stp = bi - PIC_STARS_A;
       if ((*stp < 0) || (*stp >= PIC_NSTARS)) *stp = 0;
       *sqp = SQ_STARS;
       break;
  }
 return(0);
}

void initlevel(void)
{
 int x;
 int y;
 int sq;
 int st;
 int bl;
 LEVEL_SEG *ls;
 LEVEL *l;

 if (curlevelno < 1) curlevelno = 1;
 for (ls=level_segs;ls&&(curlevelno>ls->last);ls=ls->link) ;
 if (! ls)
  { curlevelno = 1;
    ls = level_segs;
  }
 l = ls->levels[curlevelno-ls->first];
 st = 0;
 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { if (set_sq_bl(l->layout[y][x],&sq,&bl,&st))
	{ player_x = x;
	  player_y = y;
	}
       squaretype[x][y] = sq;
       startype[x][y] = st;
       blocktype[x][y] = bl;
       init_blocktype[x][y] = bl;
     }
  }
 init_player_x = player_x;
 init_player_y = player_y;
 pushsave();
 levelname = l->name;
 levelname_len = l->namelen;
 curlevel = l;
 XRaiseWindow(disp,curlevel->seg->dirty?g_buttonwin_s:g_buttonwin_no_s);
}

void run(void)
{
 XEvent e;

 while (1)
  { XNextEvent(disp,&e);
    handle_event(&e);
  }
}

void handle_event(XEvent *e)
{
 switch (e->type)
  { default:
       break;
    case ConfigureNotify:
       /* XConfigureEvent - xconfigure */
       resize(e->xconfigure.width,e->xconfigure.height);
       break;
    case Expose:
       /* XExposeEvent - xexpose */
       redisplay(e->xexpose.window,e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height,e->xexpose.count);
       break;
    case GraphicsExpose:
       /* XGraphicsExposeEvent - xgraphicsexpose */
       redisplay(e->xgraphicsexpose.drawable,e->xgraphicsexpose.x,e->xgraphicsexpose.y,e->xgraphicsexpose.width,e->xgraphicsexpose.height,e->xgraphicsexpose.count);
       break;
    case ButtonPress:
       /* XButtonPressEvent - XButtonEvent - xbutton */
       buttondown(e->xbutton.window,e->xbutton.x,e->xbutton.y,e->xbutton.state,e->xbutton.button);
       break;
    case MotionNotify:
       /* XMotionEvent - xmotion */
       motion(e->xmotion.window,e->xmotion.x,e->xmotion.y,e->xmotion.state);
       break;
    case KeyPress:
       /* XKeyPressedEvent - XKeyEvent - xkey */
	{ static XComposeStatus compose_status;
	  KeySym ks;
	  char string[256];
	  int nc;
	  nc = XLookupString(&e->xkey,&string[0],256,&ks,&compose_status);
	  docmd(ks,e->xkey.state,nc,&string[0]);
	}
       break;
    case MappingNotify:
       /* XMappingEvent - xmapping */
       XRefreshKeyboardMapping(&e->xmapping);
       break;
  }
}

void docmd(KeySym ks,unsigned int state,int nc,char *str)
{
 switch (dispstate)
  { case DS_GAME:
       if (state & ControlMask)
	{ switch (ks)
	   { case XK_H: case XK_h: buttoncb_help(); break;
	     case XK_C: case XK_c: buttoncb_credits(); break;
	     case XK_E: case XK_e: buttoncb_edit(); break;
	     case XK_S: case XK_s: buttoncb_save(); break;
	     case XK_Q: case XK_q: buttoncb_quit(); break;
	     case XK_R: case XK_r: resetlevel(); break;
	     case XK_Z: case XK_z: undopush(); break;
	     case XK_P: case XK_p: asklevel(); break;
	   }
	}
       else
	{ switch (ks)
	   { case XK_H: case XK_h: case XK_Left:  cmd_move(-1, 0); break;
	     case XK_J: case XK_j: case XK_Down:  cmd_move( 0, 1); break;
	     case XK_K: case XK_k: case XK_Up:    cmd_move( 0,-1); break;
	     case XK_L: case XK_l: case XK_Right: cmd_move( 1, 0); break;
	   }
	}
       break;
    case DS_CREDITS:
       setdispstate(DS_GAME);
       break;
    case DS_HELP:
       switch (ks)
	{ case XK_1: sethelp(1); break;
	  case XK_2: sethelp(2); break;
	  case XK_3: sethelp(3); break;
	  case XK_4: sethelp(4); break;
	  case XK_5: sethelp(5); break;
	  case XK_6: sethelp(6); break;
	  case XK_7: sethelp(7); break;
	  case XK_8: sethelp(8); break;
	  case XK_9: sethelp(9); break;
	  case XK_space:
	     if (helpscreen < N_HELP) sethelp(helpscreen+1); else endhelp();
	     break;
	  case XK_Escape: case XK_R: case XK_r: case XK_Return:
	     endhelp();
	     break;
	}
       break;
    case DS_ASKLEVEL:
       if (nc > 0)
	{ int i;
	  for (i=0;i<nc;i++)
	   { switch (str[i])
	      { case '\r': case '\n':
		   asklevel_done();
		   i = nc; /* pseudo-break from for loop */
		   break;
		case '\b': case '\177':
		   if (levprompt_fill > 0)
		    { levprompt_fill --;
		      XClearArea(disp,levprompt_win,levprompt_xclr,DIALOG_BORDER,levprompt_w-levprompt_xclr-DIALOG_BORDER,levprompt_h-(2*DIALOG_BORDER),True);
		    }
		   break;
		default:
		   if (levprompt_fill >= levprompt_len)
		    { bcopy(levprompt_buf+1,levprompt_buf,levprompt_len-1);
		      levprompt_fill --;
		    }
		   levprompt_buf[levprompt_fill++] = str[i];
	      }
	   }
	  XClearArea(disp,levprompt_win,levprompt_xclr,DIALOG_BORDER,levprompt_w-levprompt_xclr-DIALOG_BORDER,levprompt_h-(2*DIALOG_BORDER),False);
	  redisplay(levprompt_win,levprompt_xclr,DIALOG_BORDER,levprompt_w-levprompt_xclr-DIALOG_BORDER,levprompt_h-(2*DIALOG_BORDER),0);
	}
       break;
    case DS_MSG:
       setdispstate(DS_GAME);
       break;
    case DS_EDIT:
       if (state & ControlMask)
	{ switch (ks)
	   { case XK_J: case XK_j: buttoncb_e_junk(); break;
	     case XK_R: case XK_r: buttoncb_e_revert(); break;
	     case XK_A: case XK_a: buttoncb_e_abort(); break;
	     case XK_D: case XK_d: buttoncb_e_done(); break;
	     case XK_N: case XK_n: buttoncb_e_name(); break;
	     case XK_C: case XK_c: buttoncb_e_clone(); break;
	   }
	}
       else
	{ switch (ks)
	   { case XK_H: case XK_h: case XK_Left:  edit_move(-1, 0); break;
	     case XK_J: case XK_j: case XK_Down:  edit_move( 0, 1); break;
	     case XK_K: case XK_k: case XK_Up:    edit_move( 0,-1); break;
	     case XK_L: case XK_l: case XK_Right: edit_move( 1, 0); break;
	     case XK_plus:  change_curpic( 1); break;
	     case XK_minus: change_curpic(-1); break;
	     case XK_space: edit_set(editcurs_x,editcurs_y); break;
	   }
	}
       break;
    case DS_LEVNAME:
       if (nc > 0)
	{ int i;
	  for (i=0;i<nc;i++)
	   { switch (str[i])
	      { case '\r': case '\n':
		   levname_done(1);
		   i = nc; /* pseudo-break from for loop */
		   break;
		case '\7':
		   levname_done(0);
		   i = nc; /* pseudo-break from for loop */
		   break;
		case '\b': case '\177':
		   if (lnfill > 0)
		    { lnfill --;
		    }
		   break;
		default:
		   if (lnfill < MAXLNLEN)
		    { lnbuf[lnfill++] = str[i];
		    }
		   else
		    { XBell(disp,0);
		    }
		   break;
	      }
	   }
	  levelname_len = lnfill;
	  XClearArea(disp,textwin,0,0,0,0,False);
	  redraw_text();
	}
       break;
  }
}

void change_curpic(int d)
{
 edit_damaged[edit_curpic] = 1;
 edit_curpic += d;
 if (edit_curpic < 0) edit_curpic += B_NPIX; else if (edit_curpic >= B_NPIX) edit_curpic -= B_NPIX;
 edit_damaged[edit_curpic] = 1;
 fix_edit_damage();
}

void edit_set(int x,int y)
{
 int sq;
 int bl;
 int st;
 int draw;

 if ((x < 0) || (x >= BOARD_X) || (y < 0) || (y >= BOARD_Y)) return;
 draw = 0;
 if (set_sq_bl(edit_curpic,&sq,&bl,&st))
  { int ox;
    int oy;
    ox = player_x;
    oy = player_y;
    player_x = x;
    player_y = y;
    drawsq(ox,oy);
    draw = 1;
  }
 if ( (squaretype[x][y] != sq) ||
      (startype[x][y] != st) ||
      (blocktype[x][y] != bl) )
  { squaretype[x][y] = sq;
    startype[x][y] = st;
    blocktype[x][y] = bl;
    draw = 1;
  }
 if (draw) drawsq(x,y);
 if ( (blocktype[player_x][player_y] != BLK_NONE) ||
      (squaretype[player_x][player_y] != SQ_BLANK) )
  { int off;
    int i;
    int ox;
    int oy;
    ox = player_x;
    oy = player_y;
    off = (player_y * BOARD_X) + player_x;
    for (i=0;i<BOARD_X*BOARD_Y;i++)
     { x = (i+off) % BOARD_X;
       y = (i+off) / BOARD_X;
       if ((blocktype[x][y] == BLK_NONE) && (squaretype[x][y] == SQ_BLANK)) break;
     }
    if (i < BOARD_X*BOARD_Y)
     { player_x = x;
       player_y = y;
     }
    else
     { /* eek! have to put it *somewhere*.... */
       blocktype[0][0] = BLK_NONE;
       squaretype[0][0] = SQ_BLANK;
       player_x = 0;
       player_y = 0;
     }
    drawsq(ox,oy);
    drawsq(player_x,player_y);
  }
}

void edit_move(int dx,int dy)
{
 int ox;
 int oy;

 ox = editcurs_x;
 oy = editcurs_y;
 editcurs_x += dx;
 editcurs_y += dy;
 if (editcurs_x < 0) editcurs_x = 0; else if (editcurs_x >= BOARD_X) editcurs_x = BOARD_X - 1;
 if (editcurs_y < 0) editcurs_y = 0; else if (editcurs_y >= BOARD_Y) editcurs_y = BOARD_Y - 1;
 if ((editcurs_x != ox) || (editcurs_y != oy))
  { drawsq(ox,oy);
    drawsq(editcurs_x,editcurs_y);
  }
}

void asklevel(void)
{
 setdispstate(DS_ASKLEVEL);
 XMapRaised(disp,levprompt_win);
 if (! levprompt_buf)
  { levprompt_buf = malloc(levprompt_len+1);
  }
 sprintf(levprompt_buf,"%d",curlevelno);
 levprompt_fill = strlen(levprompt_buf);
}

void asklevel_done(void)
{
 levprompt_buf[levprompt_fill] = '\0';
 setdispstate(DS_GAME);
 setlevel(levprompt_buf);
 initlevel();
 redraw();
}

void beginhelp(void)
{
 setdispstate(DS_HELP);
 sethelp(helpscreen);
 XRaiseWindow(disp,helpwin);
 XMapWindow(disp,textpanelwin);
}

void sethelp(int n)
{
 if ((n > 0) && (n <= N_HELP))
  { XRaiseWindow(disp,helpwins[n-1]);
    helpscreen = n;
  }
}

void endhelp(void)
{
 setdispstate(DS_GAME);
}

void undopush(void)
{
 level_juggle(save_blocktype,&save_player_x,&save_player_y,1);
}

void resetlevel(void)
{
 level_juggle(init_blocktype,&init_player_x,&init_player_y,0);
 pushsave();
}

void level_juggle(char (*bl)[BOARD_Y],int *pxp,int *pyp,int swap)
/*
char (*bl)[BOARD_Y];
int *pxp;
int *pyp;
int swap;
*/
{
 int tmp;
 int x;
 int y;

 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { if (blocktype[x][y] != bl[x][y])
	{ if (swap)
	   { tmp = blocktype[x][y];
	     blocktype[x][y] = bl[x][y];
	     bl[x][y] = tmp;
	   }
	  else
	   { blocktype[x][y] = bl[x][y];
	   }
	  sqdamaged[x][y] = 1;
	}
     }
  }
 if ((player_x != *pxp) || (player_y != *pyp))
  { sqdamaged[player_x][player_y] = 1;
    if (swap)
     { tmp = player_x;
       player_x = *pxp;
       *pxp = tmp;
       tmp = player_y;
       player_y = *pyp;
       *pyp = tmp;
     }
    else
     { player_x = *pxp;
       player_y = *pyp;
     }
    sqdamaged[player_x][player_y] = 1;
  }
 fix_damage();
}

int boardisclear()
{
 int x;
 int y;
 int bt;

 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { bt = blocktype[x][y];
       if (bt != BLK_NONE)
	{ switch (blocktype[x][y] & BLK_SHAPE)
	   { case BLK_SHAPE_W:
		break;
	     default:
		return(0);
		break;
	   }
	}
     }
  }
 return(1);
}

void nextlevel(void)
{
 curlevelno ++;
 initlevel();
 redraw();
}

void redraw(void)
{
 redisplay(gamewin,0,0,gamew,gameh,0);
 XClearArea(disp,textwin,0,0,textw,texth,True);
}

void pushsave(void)
{
 int x;
 int y;

 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { save_blocktype[x][y] = blocktype[x][y];
     }
  }
 save_player_x = player_x;
 save_player_y = player_y;
}

int do_move(int dx,int dy)
{
 int nx;
 int ny;
 int nbt;
 int bx;
 int by;
 int bbt;

 nx = player_x + dx;
 ny = player_y + dy;
 if ((nx < 0) || (ny < 0) || (nx >= BOARD_X) || (ny >= BOARD_Y)) return(1);
 nbt = blocktype[nx][ny];
 if (nbt == BLK_NONE)
  { switch (squaretype[nx][ny])
     { case SQ_WALL: case SQ_STARS: return(1); break;
     }
    player_x = nx;
    player_y = ny;
    drawsq(nx-dx,ny-dy);
    drawsq(nx,ny);
  }
 else
  { bx = nx + dx;
    by = ny + dy;
    if ((bx < 0) || (by < 0) || (bx >= BOARD_X) || (by >= BOARD_Y)) return(1);
    bbt = blocktype[bx][by];
    if (bbt == (nbt^BLK_COLOR_B^BLK_COLOR_Y))
     { pushsave();
       player_x = nx;
       player_y = ny;
       blocktype[nx][ny] = BLK_NONE;
       switch (bbt & BLK_SHAPE)
	{ case BLK_SHAPE_S:
	     blocktype[bx][by] = BLK_NONE;
	     if (boardisclear())
	      { nextlevel();
		return(0);
	      }
	     break;
	  case BLK_SHAPE_R:
	     blocktype[bx][by] = (bbt & ~BLK_SHAPE) | BLK_SHAPE_S;
	     break;
	  case BLK_SHAPE_P:
	     blocktype[bx][by] = (bbt & ~BLK_SHAPE) | BLK_SHAPE_R;
	     break;
	  case BLK_SHAPE_D:
	     blocktype[bx][by] = (bbt & ~BLK_SHAPE) | BLK_SHAPE_P;
	     break;
	  default:
	     bugchk("bad block %d in block meeting",bbt);
	     break;
	}
       drawsq(nx-dx,ny-dy);
       drawsq(nx,ny);
       drawsq(bx,by);
     }
    else if (bbt == BLK_NONE)
     { switch (squaretype[bx][by])
	{ case SQ_WALL: case SQ_STARS: return(1); break;
	}
       pushsave();
       player_x = nx;
       player_y = ny;
       blocktype[bx][by] = nbt;
       blocktype[nx][ny] = BLK_NONE;
       if ((nbt & BLK_SHAPE) != BLK_SHAPE_W)
	{ switch (squaretype[bx][by])
	   { case SQ_BLANK:
		break;
	     case SQ_COLOR_B:
		blocktype[bx][by] = (nbt & ~BLK_COLOR) | BLK_COLOR_B;
		break;
	     case SQ_COLOR_Y:
		blocktype[bx][by] = (nbt & ~BLK_COLOR) | BLK_COLOR_Y;
		break;
	     case SQ_COLOR_F:
		blocktype[bx][by] = nbt ^ BLK_COLOR_B ^ BLK_COLOR_Y;
		break;
	     case SQ_TELEPORT:
		do_teleport(bx,by);
		break;
	     case SQ_MUTATE:
		switch (nbt & BLK_SHAPE)
		 { case BLK_SHAPE_S:
		      blocktype[bx][by] = (nbt & ~BLK_SHAPE) | BLK_SHAPE_R;
		      break;
		   case BLK_SHAPE_R:
		      blocktype[bx][by] = (nbt & ~BLK_SHAPE) | BLK_SHAPE_P;
		      break;
		   case BLK_SHAPE_P:
		      blocktype[bx][by] = (nbt & ~BLK_SHAPE) | BLK_SHAPE_D;
		      break;
		 }
		break;
	     default:
		bugchk("bad square type %d in block slide",squaretype[nx][ny]);
		break;
	   }
	}
       drawsq(nx-dx,ny-dy);
       drawsq(nx,ny);
       drawsq(bx,by);
     }
  }
  return 0;
}

void cmd_move(int dx,int dy)
{
 if (do_move(dx,dy)) playsound(SND_CANTMOVE);
}

void do_teleport(int fx,int fy)
{
 int bestx;
 int besty;
 int bestdist;
 int x;
 int y;
 int dist;

 bestx = fx;
 besty = fy;
 bestdist = BOARD_X + BOARD_Y + 1;
 for (y=0;y<BOARD_Y;y++)
  { for (x=0;x<BOARD_X;x++)
     { if (squaretype[x][y] != SQ_TELEPORT) continue;
       if (blocktype[x][y] != BLK_NONE) continue;
       if ((x == player_x) && (y == player_y)) continue;
       dist = abs(x-fx) + abs(y-fy);
       if (dist < bestdist)
	{ bestx = x;
	  besty = y;
	  bestdist = dist;
	}
     }
  }
 if ((fx != bestx) || (fy != besty))
  { blocktype[bestx][besty] = blocktype[fx][fy];
    blocktype[fx][fy] = BLK_NONE;
    drawsq(bestx,besty);
    /* drawsq(fx,fy); done by caller */
  }
}

void edit_ask_resize(void)
{
 int w;
 int h;
 XWindowChanges val;

 resize(topw,toph);
 w = SPACING + (BOARD_X * PIC_W) + SPACING;
 switch (dispstate)
  { case DS_EDIT:
    case DS_LEVNAME:
       h = SPACING+edith+SPACING+borderwidth+SPACING+gameh+SPACING+borderwidth+SPACING+texth+SPACING+button_h+SPACING;
       break;
    default:
       h = SPACING+gameh+SPACING+borderwidth+SPACING+texth+SPACING+button_h+SPACING;
       break;
  }
 val.width = w;
 val.height = h;
 XReconfigureWMWindow(disp,topwin,XScreenNumberOfScreen(scr),CWWidth|CWHeight,&val);
}

void resize(int tw,int th)
{
 int y;
 int dy;
 int yinc;
 int ny;
 int x;
 int dx;
 int xinc;
 int nx;
#define BUMPY(inc) ((y += (inc) + (yinc = dy / ny)), (dy -= yinc), (ny --))
#define BUMPX(inc) ((x += (inc) + (xinc = dx / nx)), (dx -= xinc), (nx --))
 int i;
 BUTTON *b;

 topw = tw;
 toph = th;
 gamew = BOARD_X * PIC_W;
 gameh = BOARD_Y * PIC_H;
 editw = EDIT_SPACING + (EDIT_NCOLS * (EDIT_BORDER + PIC_W + EDIT_BORDER + EDIT_SPACING));
 edith = EDIT_SPACING + (EDIT_NROWS * (EDIT_BORDER + PIC_H + EDIT_BORDER + EDIT_SPACING));
 textw = topw;
 texth = 2 * (font->ascent + font->descent);
 switch (dispstate)
  { case DS_EDIT:
    case DS_LEVNAME:
       dy = toph - (edith + borderwidth + gameh + borderwidth + texth + button_h);
       ny = 7;
       y = 0;
       BUMPY(0);
       XMoveResizeWindow(disp,editwin,(topw-editw)/2,y,editw,edith);
       BUMPY(edith);
       XMoveResizeWindow(disp,line1win,0,y,topw,borderwidth);
       BUMPY(borderwidth);
       break;
    default:
       dy = toph - (gameh + borderwidth + texth + button_h);
       ny = 5;
       y = 0;
       BUMPY(0);
       break;
  }
 gamey = y;
 XMoveResizeWindow(disp,gamewin,(topw-gamew)/2,y,gamew,gameh);
 XMoveResizeWindow(disp,levprompt_win,(topw-levprompt_w)/2,y+(gameh-levprompt_h)/2,levprompt_w,levprompt_h);
 if (dispstate == DS_MSG) place_msgwin();
 BUMPY(gameh);
 XMoveResizeWindow(disp,line2win,0,y,topw,borderwidth);
 BUMPY(borderwidth);
 XMoveResizeWindow(disp,textwin,(topw-textw)/2,y,textw,texth);
 BUMPY(texth);
 XMoveResizeWindow(disp,g_buttonwin,0,y,topw,button_h);
 XMoveResizeWindow(disp,e_buttonwin,0,y,topw,button_h);
 XMoveResizeWindow(disp,g_buttonwin_s,0,0,topw,button_h);
 XMoveResizeWindow(disp,g_buttonwin_no_s,0,0,topw,button_h);
 dx = topw;
 for (i=0;i<NBUTTONS_NO_S;i++) dx -= buttons_no_s[i].w;
 nx = NBUTTONS_NO_S + 1;
 x = 0;
 BUMPX(0);
 for (i=0;i<NBUTTONS_NO_S;i++)
  { b = &buttons_no_s[i];
    b->x = x;
    b->y = (button_h - b->h) / 2;
    XMoveResizeWindow(disp,b->win,b->x,b->y,b->w,b->h);
    BUMPX(b->w);
  }
 dx = topw;
 for (i=0;i<NBUTTONS_S;i++) dx -= buttons_s[i].w;
 nx = NBUTTONS_S + 1;
 x = 0;
 BUMPX(0);
 for (i=0;i<NBUTTONS_S;i++)
  { b = &buttons_s[i];
    b->x = x;
    b->y = (button_h - b->h) / 2;
    XMoveResizeWindow(disp,b->win,b->x,b->y,b->w,b->h);
    BUMPX(b->w);
  }
 dx = topw;
 for (i=0;i<NEDITBUTTONS;i++) dx -= edit_buttons[i].w;
 nx = NEDITBUTTONS + 1;
 x = 0;
 BUMPX(0);
 for (i=0;i<NEDITBUTTONS;i++)
  { b = &edit_buttons[i];
    b->x = x;
    b->y = (button_h - b->h) / 2;
    XMoveResizeWindow(disp,b->win,b->x,b->y,b->w,b->h);
    BUMPX(b->w);
  }
 BUMPY(button_h);
}
#undef BUMPY
#undef BUMPX

void redisplay(Window win,int x,int y,int w,int h,int count)
{
 int i;

 if (win == gamewin)
  { damage_gamewin(x,y,w,h);
    if (count == 0) fix_damage();
  }
 else if (win == textwin)
  { if (count == 0) redraw_text();
  }
 else if (win == creditwin)
  { update_textpanel(creditwin,&credits_img_bits[0],x,y,w,h);
    if (count == 0) update_textpanel_pix(creditwin,&credits_pix[0]);
  }
 else if (win == levprompt_win)
  { if (count == 0) draw_levprompt();
  }
 else if (win == msgwin)
  { if (count == 0) draw_msg();
  }
 else if (win == editwin)
  { damage_editwin(x,y,w,h);
    if (count == 0) fix_edit_damage();
  }
 else
  { for (i=0;i<N_HELP;i++)
     { if (win == helpwins[i])
	{ update_textpanel(win,help_img_bits[i],x,y,w,h);
	  if (count == 0) update_textpanel_pix(win,help_pix[i]);
	}
     }
    for (i=0;i<NBUTTONS_NO_S;i++) if (win == buttons_no_s[i].win) redraw_button(&buttons_no_s[i]);
    for (i=0;i<NBUTTONS_S;i++) if (win == buttons_s[i].win) redraw_button(&buttons_s[i]);
    for (i=0;i<NHELPBUTTONS;i++) if (win == help_buttons[i].win) redraw_help_button(&help_buttons[i]);
    for (i=0;i<NEDITBUTTONS;i++) if (win == edit_buttons[i].win) redraw_button(&edit_buttons[i]);
  }
}

void damage_editwin(int x,int y,int w,int h)
{
 int x1;
 int y1;
 int x0;
 int i;

 x1 = x + w - 1;
 y1 = y + h - 1;
 x /= EDIT_MX;
 y /= EDIT_MY;
 x1 /= EDIT_MX;
 y1 /= EDIT_MY;
 if (x1 >= EDIT_NCOLS) x1 = EDIT_NCOLS - 1;
 if (y1 >= EDIT_NCOLS) y1 = EDIT_NCOLS - 1;
 x0 = x;
 for (;y<=y1;y++)
  { i = (y * EDIT_NCOLS) + x0;
    for (x=x0;x<=x1;x++)
     { if (i < B_NPIX) edit_damaged[i] = 1;
       i ++;
     }
  }
}

void fix_edit_damage(void)
{
 int i;

 setup_gc(GCFunction,GXcopy,0L);
 for (i=0;i<B_NPIX;i++)
  { if (edit_damaged[i])
     { setup_gc(GCForeground,(i==edit_curpic)?fgcolor.pixel:bgcolor.pixel,0L);
       XFillRectangle(disp,editwin,wingc,EDIT_SPACING+((i%EDIT_NCOLS)*EDIT_MX),EDIT_SPACING+((i/EDIT_NCOLS)*EDIT_MY),EDIT_BORDER+PIC_W+EDIT_BORDER,EDIT_BORDER+PIC_H+EDIT_BORDER);
     }
  }
 if (bw_mode) setup_gc(GCForeground,whitecolor.pixel,GCBackground,blackcolor.pixel,0L);
 for (i=0;i<B_NPIX;i++)
  { if (edit_damaged[i])
     { if (bw_mode)
	{ XCopyPlane(disp,*pic_ptrs[i],editwin,wingc,0,0,PIC_W,PIC_H,EDIT_SPACING+EDIT_BORDER+((i%EDIT_NCOLS)*EDIT_MX),EDIT_SPACING+EDIT_BORDER+((i/EDIT_NCOLS)*EDIT_MY),1L);
	}
       else
	{ XCopyArea(disp,*pic_ptrs[i],editwin,wingc,0,0,PIC_W,PIC_H,EDIT_SPACING+EDIT_BORDER+((i%EDIT_NCOLS)*EDIT_MX),EDIT_BORDER+EDIT_SPACING+((i/EDIT_NCOLS)*EDIT_MY));
	}
       edit_damaged[i] = 0;
     }
  }
}

void draw_levprompt(void)
{
 setup_gc(GCFont,fontid,GCForeground,fgcolor.pixel,GCLineWidth,0,GCFillStyle,FillSolid,GCFunction,GXcopy,0L);
 XDrawRectangle(disp,levprompt_win,wingc,0,0,levprompt_w-1,levprompt_h-1);
 XDrawString(disp,levprompt_win,wingc,DIALOG_BORDER+DIALOG_MARGIN,DIALOG_BORDER+DIALOG_MARGIN+font->ascent,levprompt_prompt,strlen(levprompt_prompt));
 XDrawString(disp,levprompt_win,wingc,levprompt_xoff,DIALOG_BORDER+DIALOG_MARGIN+font->ascent,levprompt_buf,levprompt_fill);
}

void draw_msg(void)
{
 setup_gc(GCFont,fontid,GCForeground,fgcolor.pixel,GCLineWidth,0,GCFillStyle,FillSolid,GCFunction,GXcopy,0L);
 XDrawRectangle(disp,msgwin,wingc,0,0,msg_w-1,msg_h-1);
 XDrawString(disp,msgwin,wingc,DIALOG_BORDER+DIALOG_MARGIN,DIALOG_BORDER+DIALOG_MARGIN+font->ascent,&msg_buf[0],msg_len);
}

void update_textpanel(Window win,unsigned char *bits,int x,int y,int w,int h)
{
 static XImage *xi = 0;

 if (xi == 0)
  { xi = XCreateImage(disp,visual,1,XYBitmap,0,bits,PIC_W*BOARD_X,PIC_H*BOARD_Y,8,((PIC_W*BOARD_X)+7)>>3);
    xi->byte_order = LSBFirst;
    xi->bitmap_unit = 8;
    xi->bitmap_bit_order = LSBFirst;
  }
 xi->data = (char *)bits;
 setup_gc(GCForeground,fgcolor.pixel,GCBackground,bgcolor.pixel,GCFunction,GXcopy,0L);
 XPutImage(disp,win,wingc,xi,x,y,x,y,w,h);
}

void update_textpanel_pix(Window win,int *pv)
{
 if (bw_mode)
  { setup_gc(GCForeground,whitecolor.pixel,GCBackground,blackcolor.pixel,GCFunction,GXcopy,0L);
    while (pv[0] >= 0)
     { XCopyPlane(disp,*pic_ptrs[pv[0]],win,wingc,0,0,PIC_W,PIC_H,pv[1],pv[2],1L);
       pv += 3;
     }
  }
 else
  { setup_gc(GCFunction,GXcopy,0L);
    while (pv[0] >= 0)
     { XCopyArea(disp,*pic_ptrs[pv[0]],win,wingc,0,0,PIC_W,PIC_H,pv[1],pv[2]);
       pv += 3;
     }
  }
}

void pickpic(int x,int y)
{
 int qx;
 int qy;
 int rx;
 int ry;
 int i;

 qx = x / EDIT_MX;
 rx = x % EDIT_MX;
 qy = y / EDIT_MY;
 ry = y % EDIT_MY;
 if ((rx >= EDIT_SPACING) && (ry >= EDIT_SPACING))
  { i = (qy * EDIT_NCOLS) + qx;
    if (i < B_NPIX)
     { edit_damaged[edit_curpic] = 1;
       edit_curpic = i;
       edit_damaged[edit_curpic] = 1;
       fix_edit_damage();
     }
  }
}

int buttondown(Window win,int x,int y,unsigned int state,unsigned int button)
{
 int i;
 BUTTON *b;

 if (win == gamewin)
  { switch (dispstate)
     { default:
	  setdispstate(DS_GAME);
	  /* fall through */
       case DS_GAME:
	  mousegame(x,y);
	  break;
       case DS_ASKLEVEL:
	  break;
       case DS_MSG:
	  setdispstate(DS_GAME);
	  break;
       case DS_EDIT:
	  mouseedit(x,y);
	  break;
     }
  }
 else if (win == creditwin)
  { setdispstate(DS_GAME);
  }
 else if (win == levprompt_win)
  { if (dispstate == DS_ASKLEVEL) asklevel_done(); else setdispstate(DS_GAME);
  }
 else if (win == msgwin)
  { setdispstate(DS_GAME);
  }
 else if (win == editwin)
  { pickpic(x,y);
  }
 else
  { for (i=0;i<N_HELP-1;i++)
     { if (win == helpwins[i])
	{ sethelp(i+2);
	}
     }
    if (win == helpwins[N_HELP-1]) endhelp();
    for (i=0;i<NBUTTONS_NO_S;i++)
     { b = &buttons_no_s[i];
       if (win == b->win)
	{ (*b->callback)();
	}
     }
    for (i=0;i<NBUTTONS_S;i++)
     { b = &buttons_s[i];
       if (win == b->win)
	{ (*b->callback)();
	}
     }
    for (i=0;i<NHELPBUTTONS;i++)
     { b = &help_buttons[i];
       if (win == b->win)
	{ (*b->callback)();
	}
     }
    for (i=0;i<NEDITBUTTONS;i++)
     { b = &edit_buttons[i];
       if (win == b->win)
	{ (*b->callback)();
	}
     }
  }
  return 0;
}

void motion(Window win,int x,int y,unsigned int state)
{
 if (win == gamewin)
  { switch (dispstate)
     { case DS_GAME:
	  mousegame(x,y);
	  break;
       case DS_EDIT:
	  mouseedit(x,y);
	  break;
     }
  }
}

void mousegame(int x,int y)
{
 int dx;
 int dy;

 x = ((x + PIC_W) / PIC_W) - 1;
 y = ((y + PIC_H) / PIC_H) - 1;
 dx = x - player_x;
 dy = y - player_y;
 if ((abs(dx)+abs(dy) > 1) || ((dx || dy) && do_move(dx,dy)))
  { static struct timeval lastfail = { 0, 0 };
    struct timeval now;
    struct timeval dtv;
    gettimeofday(&now,(struct timezone *)0);
    dtv.tv_sec = now.tv_sec - lastfail.tv_sec;
    dtv.tv_usec = now.tv_usec - lastfail.tv_usec;
    if (dtv.tv_usec < 0)
     { dtv.tv_usec += 1000000;
       dtv.tv_sec --;
     }
    if ((dtv.tv_sec > 0) || (dtv.tv_usec > 250000))
     { lastfail = now;
       playsound(SND_CANTMOVE);
     }
  }
}

void mouseedit(int x,int y)
{
 edit_set(((x+PIC_W)/PIC_W)-1,((y+PIC_H)/PIC_H)-1);
}

void damage_gamewin(int x,int y,int w,int h)
{
 int x0;
 int x1;
 int y1;

 x1 = x + w - 1;
 y1 = y + h - 1;
 x0 = x / PIC_W;
 y /= PIC_H;
 x1 /= PIC_W;
 y1 /= PIC_H;
 if (x0 < 0) x0 = 0;
 if (y < 0) y = 0;
 if (x1 >= BOARD_X) x1 = BOARD_X - 1;
 if (y1 >= BOARD_Y) y1 = BOARD_Y - 1;
 if ((x0 <= x1) && (y <= y1))
  { for (;y<=y1;y++)
     { for (x=x0;x<=x1;x++)
	{ sqdamaged[x][y] = 1;
	}
     }
  }
}

void fix_damage(void)
{
 int x;
 int y;

 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { if (sqdamaged[x][y])
	{ sqdamaged[x][y] = 0;
	  drawsq(x,y);
	}
     }
  }
}

void redraw_text(void)
{
 char linebuf[64];

 sprintf(&linebuf[0],"Scene %d",curlevelno);
 text_center(&linebuf[0],strlen(&linebuf[0]),font->ascent);
 text_center(levelname,levelname_len,font->ascent+font->descent+font->ascent);
}

void redraw_button(BUTTON *b)
{
 setup_gc(GCFont,fontid,GCForeground,fgcolor.pixel,GCLineWidth,0,GCFillStyle,FillSolid,0L);
 XDrawRectangle(disp,b->win,wingc,0,0,b->w-1,b->h-1);
 XDrawString(disp,b->win,wingc,b->textx,b->texty,b->text,strlen(b->text));
}

void redraw_help_button(BUTTON *b)
{
 static XImage *xi = 0;

 if (xi == 0)
  { xi = XCreateImage(disp,visual,1,XYBitmap,0,b->text,1,help_button_h,8,1);
    xi->byte_order = LSBFirst;
    xi->bitmap_unit = 8;
    xi->bitmap_bit_order = LSBFirst;
  }
 /* 5 = # of overhead bytes before bitmap begins - see setup_buttons */
 xi->data = 5 + *(char **)b->text;
 xi->width = b->w;
 xi->bytes_per_line = (b->w + 7) >> 3;
 setup_gc(GCForeground,fgcolor.pixel,GCBackground,bgcolor.pixel,GCFunction,GXcopy,0L);
 XPutImage(disp,b->win,wingc,xi,0,0,0,0,b->w,b->h);
}

void text_center(char *str,int len,int y)
{
 XCharStruct s;

 XTextExtents(font,str,len,XTE_JUNK,&s);
 setup_gc(GCFont,fontid,GCForeground,fgcolor.pixel,GCFillStyle,FillSolid,0L);
 XDrawString(disp,textwin,wingc,(textw-s.width)/2,y,str,len);
}

void drawsq(int x,int y)
{
 Pixmap pm;

 if ((x == player_x) && (y == player_y))
  { pm = pic_player;
  }
 else
  { int bt;
    bt = blocktype[x][y];
    if (bt == BLK_NONE)
     { switch (squaretype[x][y])
	{ case SQ_BLANK:
	     pm = pic_blank;
	     break;
	  case SQ_COLOR_B:
	     pm = pic_color_b;
	     break;
	  case SQ_COLOR_Y:
	     pm = pic_color_y;
	     break;
	  case SQ_COLOR_F:
	     pm = pic_color_flip;
	     break;
	  case SQ_WALL:
	     pm = pic_wall;
	     break;
	  case SQ_TELEPORT:
	     pm = pic_teleport;
	     break;
	  case SQ_MUTATE:
	     pm = pic_mutate;
	     break;
	  case SQ_STARS:
	      { int st;
		st = startype[x][y];
		if ((st < 0) || (st >= PIC_NSTARS))
		 { bugchk("invalid star number %d at (%d,%d)",st,x,y);
		 }
		pm = pic_stars[st];
	      }
	     break;
	  default:
	     bugchk("invalid square type %d at (%d,%d)\n",squaretype[x][y],x,y);
	     break;
	}
     }
    else
     { switch (bt & BLK_SHAPE)
	{ case BLK_SHAPE_W:
	     pm = pic_mwall;
	     break;
	  case BLK_SHAPE_S:
	     pm = ((bt & BLK_COLOR) == BLK_COLOR_B) ? pic_s_b : pic_s_y;
	     break;
	  case BLK_SHAPE_R:
	     pm = ((bt & BLK_COLOR) == BLK_COLOR_B) ? pic_r_b : pic_r_y;
	     break;
	  case BLK_SHAPE_P:
	     pm = ((bt & BLK_COLOR) == BLK_COLOR_B) ? pic_p_b : pic_p_y;
	     break;
	  case BLK_SHAPE_D:
	     pm = ((bt & BLK_COLOR) == BLK_COLOR_B) ? pic_d_b : pic_d_y;
	     break;
	  default:
	     bugchk("invalid block %d at (%d,%d)",bt,x,y);
	     break;
	}
     }
  }
 if (bw_mode)
  { setup_gc(GCForeground,whitecolor.pixel,GCBackground,blackcolor.pixel,GCFunction,GXcopy,0L);
    XCopyPlane(disp,pm,gamewin,wingc,0,0,PIC_W,PIC_H,x*PIC_W,y*PIC_H,1L);
  }
 else
  { setup_gc(GCFunction,GXcopy,0L);
    XCopyArea(disp,pm,gamewin,wingc,0,0,PIC_W,PIC_H,x*PIC_W,y*PIC_H);
  }
 if ((dispstate == DS_EDIT) && (x == editcurs_x) && (y == editcurs_y))
  { int xx;
    int yy;
    setup_gc(GCForeground,whitecolor.pixel,0L);
    xx = x * PIC_W;
    yy = y * PIC_H;
    XFillRectangle(disp,gamewin,wingc,xx,yy,PIC_W,3);
    XFillRectangle(disp,gamewin,wingc,xx,yy+PIC_H-3,PIC_W,3);
    XFillRectangle(disp,gamewin,wingc,xx,yy+3,3,PIC_H-6);
    XFillRectangle(disp,gamewin,wingc,xx+PIC_W-3,yy+3,3,PIC_H-6);
  }
}

void setdispstate(int new)
{
 int old;

 if (new == dispstate) return;
 old = dispstate;
 dispstate = new;
 switch (old)
  { case DS_GAME:
       break;
    case DS_CREDITS:
       XUnmapWindow(disp,textpanelwin);
       break;
    case DS_HELP:
       XUnmapWindow(disp,textpanelwin);
       break;
    case DS_ASKLEVEL:
       XUnmapWindow(disp,levprompt_win);
       break;
    case DS_MSG:
       XUnmapWindow(disp,msgwin);
       break;
    case DS_EDIT:
       if (new != DS_LEVNAME)
	{ XUnmapWindow(disp,editwin);
	  XLowerWindow(disp,e_buttonwin);
	  edit_ask_resize();
	  drawsq(editcurs_x,editcurs_y);
	}
       break;
    case DS_LEVNAME:
       if (new != DS_EDIT)
	{ XUnmapWindow(disp,editwin);
	  XLowerWindow(disp,e_buttonwin);
	  edit_ask_resize();
	  drawsq(editcurs_x,editcurs_y);
	}
       break;
  }
 if (new == DS_GAME) /* paranoia */
  { XUnmapWindow(disp,textpanelwin);
    XUnmapWindow(disp,levprompt_win);
    XUnmapWindow(disp,msgwin);
    XUnmapWindow(disp,editwin);
  }
}

void set_msg_size(void)
{
 XCharStruct d;

 XTextExtents(font,&msg_buf[0],msg_len,XTE_JUNK,&d);
 msg_w = DIALOG_BORDER + DIALOG_MARGIN + d.width + DIALOG_MARGIN + DIALOG_BORDER;
 msg_h = DIALOG_BORDER + DIALOG_MARGIN + font->ascent + font->descent + DIALOG_MARGIN + DIALOG_BORDER;
}

void place_msgwin(void)
{
 XMoveResizeWindow(disp,msgwin,(topw-msg_w)/2,gamey+(gameh-msg_h)/2,msg_w,msg_h);
}

void popmsg(va_alist)
va_dcl
{
 va_list ap;
 char *fmt;

 va_start(ap);
 fmt = va_arg(ap,char *);
 vsprintf(&msg_buf[0],fmt,ap);
 msg_len = strlen(&msg_buf[0]);
 va_end(ap);
 set_msg_size();
 place_msgwin();
 setdispstate(DS_MSG);
 XMapRaised(disp,msgwin);
}

void begin_edit(void)
{
 if (curlevel->seg->source == 0)
  { popmsg("Can't edit built-in levels");
  }
 else
  { setdispstate(DS_EDIT);
    XMapWindow(disp,editwin);
    XMapWindow(disp,line1win);
    XLowerWindow(disp,g_buttonwin);
    edit_ask_resize();
    editcurs_x = init_player_x;
    editcurs_y = init_player_y;
    sqdamaged[editcurs_x][editcurs_y] = 1;
    resetlevel();
  }
}

void finish_edit(void)
{
 int x;
 int y;

 setdispstate(DS_GAME);
 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { init_blocktype[x][y] = blocktype[x][y];
     }
  }
 init_player_x = player_x;
 init_player_y = player_y;
 curlevel->seg->dirty = 1;
 for (x=0;x<BOARD_X;x++)
  { for (y=0;y<BOARD_Y;y++)
     { int pic;
       switch (init_blocktype[x][y])
	{ case BLK_NONE:
	     switch (squaretype[x][y])
	      { case SQ_BLANK:    pic = PIC_BLANK;      break;
		case SQ_COLOR_B:  pic = PIC_COLOR_B;    break;
		case SQ_COLOR_Y:  pic = PIC_COLOR_Y;    break;
		case SQ_COLOR_F:  pic = PIC_COLOR_FLIP; break;
		case SQ_WALL:     pic = PIC_WALL;       break;
		case SQ_TELEPORT: pic = PIC_TELEPORT;   break;
		case SQ_MUTATE:   pic = PIC_MUTATE;     break;
		case SQ_STARS:
		   pic = PIC_STARS_A + startype[x][y];
		   break;
		default:
		   bugchk("Bad squaretype %d at [%d][%d] in finish_edit",squaretype[x][y],x,y);
		   break;
	      }
	     break;
	  case BLK_SHAPE_S|BLK_COLOR_B: pic = PIC_S_B; break;
	  case BLK_SHAPE_S|BLK_COLOR_Y: pic = PIC_S_Y; break;
	  case BLK_SHAPE_R|BLK_COLOR_B: pic = PIC_R_B; break;
	  case BLK_SHAPE_R|BLK_COLOR_Y: pic = PIC_R_Y; break;
	  case BLK_SHAPE_P|BLK_COLOR_B: pic = PIC_P_B; break;
	  case BLK_SHAPE_P|BLK_COLOR_Y: pic = PIC_P_Y; break;
	  case BLK_SHAPE_D|BLK_COLOR_B: pic = PIC_D_B; break;
	  case BLK_SHAPE_D|BLK_COLOR_Y: pic = PIC_D_Y; break;
	  case BLK_SHAPE_W: pic = PIC_MWALL; break;
	  default:
	     bugchk("Bad blocktype %d at [%d][%d] in finish_edit",init_blocktype[x][y],x,y);
	     break;
	}
       curlevel->layout[y][x] = pic;
     }
  }
 curlevel->layout[player_y][player_x] = PIC_PLAYER;
 initlevel();
}

void fwrite_level(FILE *f,LEVEL *l)
{
 int x;
 int y;
 char *s;

 fprintf(f,"%d:",l->namelen);
 fwrite(l->name,1,l->namelen,f);
 for (y=0;y<BOARD_Y;y++)
  { for (x=0;x<BOARD_X;x++)
     { switch (l->layout[y][x])
	{ case PIC_BLANK:      s = "__"; break;
	  case PIC_S_B:        s = "QB"; break;
	  case PIC_S_Y:        s = "QY"; break;
	  case PIC_R_B:        s = "RB"; break;
	  case PIC_R_Y:        s = "RY"; break;
	  case PIC_P_B:        s = "PB"; break;
	  case PIC_P_Y:        s = "PY"; break;
	  case PIC_D_B:        s = "DB"; break;
	  case PIC_D_Y:        s = "DY"; break;
	  case PIC_COLOR_B:    s = "CB"; break;
	  case PIC_COLOR_Y:    s = "CY"; break;
	  case PIC_COLOR_FLIP: s = "CF"; break;
	  case PIC_WALL:       s = "WL"; break;
	  case PIC_MWALL:      s = "MW"; break;
	  case PIC_TELEPORT:   s = "TL"; break;
	  case PIC_MUTATE:     s = "MU"; break;
	  case PIC_PLAYER:     s = "PL"; break;
	  case PIC_STARS_A:    s = "SA"; break;
	  case PIC_STARS_B:    s = "SB"; break;
	  case PIC_STARS_C:    s = "SC"; break;
	  case PIC_STARS_D:    s = "SD"; break;
	  case PIC_STARS_E:    s = "SE"; break;
	  case PIC_STARS_F:    s = "SF"; break;
	  case PIC_STARS_G:    s = "SG"; break;
	  case PIC_STARS_H:    s = "SH"; break;
	  case PIC_STARS_I:    s = "SI"; break;
	  case PIC_STARS_J:    s = "SJ"; break;
	  default:
	     bugchk("Bad value %d in layout[%d][%d] for level %d (from %s) in fwrite_level",l->layout[y][x],y,x,l->levelno,l->seg->source);
	     break;
	}
       fprintf(f,"%c%s",x?' ':'\n',s);
     }
  }
 fprintf(f,"\n");
}

void saveseg(LEVEL_SEG *seg)
{
 FILE *f;
 int i;

 if (! seg->dirty) return;
 if (! seg->source) bugchk("Called saveseg on dirty built-in segment");
 f = fopen(seg->source,"w");
 if (f == 0)
  { popmsg("Can't open %s",seg->source);
    return;
  }
 for (i=0;i<seg->num;i++)
  { if (i) fprintf(f,"\n");
    fwrite_level(f,seg->levels[i]);
  }
 fclose(f);
 seg->dirty = 0;
 XRaiseWindow(disp,g_buttonwin_no_s);
 popmsg("%s written",seg->source);
}

void showcredits(void)
{
 setdispstate(DS_CREDITS);
 XRaiseWindow(disp,creditwin);
 XMapWindow(disp,textpanelwin);
}

void levname_begin(void)
{
 setdispstate(DS_LEVNAME);
 lnfill = levelname_len;
 if (lnfill > MAXLNLEN) lnfill = MAXLNLEN;
 bcopy(levelname,&lnbuf[0],lnfill);
 XClearArea(disp,textwin,0,0,0,0,False);
 levelname = &lnbuf[0];
 levelname_len = lnfill;
 redraw_text();
}

void levname_done(int saveit)
{
 setdispstate(DS_EDIT);
 if (saveit)
  { free(curlevel->name);
    curlevel->namelen = lnfill;
    curlevel->name = malloc(lnfill);
    bcopy(&lnbuf[0],curlevel->name,lnfill);
    curlevel->seg->dirty = 1;
  }
 levelname = curlevel->name;
 levelname_len = curlevel->namelen;
 reset_sizes();
 XClearArea(disp,textwin,0,0,0,0,False);
}

void reset_sizes(void)
{
 setup_sizes();
 resize(topw,toph);
}

void clonelevel(void)
{
 LEVEL_SEG *ls;
 LEVEL *l;
 int i;
 LEVEL **newlevels;
 int o;

 for (ls=curlevel->seg->link;ls;ls=ls->link)
  { ls->first ++;
    ls->last ++;
    for (i=0;i<ls->num;i++) ls->levels[i]->levelno = ls->first + i;
  }
 ls = curlevel->seg;
 newlevels = (LEVEL **) malloc((ls->num+1)*sizeof(LEVEL *));
 o = curlevel->levelno - ls->first;
 for (i=0;i<=o;i++) newlevels[i] = ls->levels[i];
 for (i=ls->num-1;i>o;i--)
  { l = ls->levels[i];
    newlevels[i+1] = l;
    l->levelno ++;
  }
 l = NEW(LEVEL);
 newlevels[o+1] = l;
 *l = *newlevels[o];
 l->name = malloc(l->namelen+7);
 bcopy(newlevels[o]->name,l->name,l->namelen);
 l->name[l->namelen++] = ' ';
 l->name[l->namelen++] = '(';
 l->name[l->namelen++] = 'c';
 l->name[l->namelen++] = 'o';
 l->name[l->namelen++] = 'p';
 l->name[l->namelen++] = 'y';
 l->name[l->namelen++] = ')';
 l->levelno ++;
 curlevel = l;
 curlevelno = l->levelno;
 maxlevelno ++;
 ls->dirty = 1;
 ls->num ++;
 ls->last ++;
 free((char *)ls->levels);
 ls->levels = newlevels;
 reset_sizes();
 initlevel();
 XClearArea(disp,textwin,0,0,0,0,False);
 redraw_text();
}

void junklevel(void)
{
 LEVEL_SEG *ls;
 int i;

 ls = curlevel->seg;
 if (ls->num < 2)
  { XBell(disp,0);
    return;
  }
 for (ls=ls->link;ls;ls=ls->link)
  { ls->first --;
    ls->last --;
    for (i=0;i<ls->num;i++) ls->levels[i]->levelno = ls->first + i;
  }
 ls = curlevel->seg;
 free(curlevel->name);
 OLD(curlevel);
 if (curlevelno == ls->last)
  { curlevelno --;
  }
 else
  { for (i=curlevelno+1-ls->first;i<ls->num;i++)
     { LEVEL *l;
       l = ls->levels[i];
       ls->levels[i-1] = l;
       l->levelno --;
     }
  }
 ls->dirty = 1;
 ls->num --;
 ls->last --;
 reset_sizes();
 initlevel();
 redraw();
}

int buttoncb_e_junk()
{
 junklevel();
 return 0;
}

int buttoncb_e_revert()
{
 level_juggle(init_blocktype,&init_player_x,&init_player_y,0);
 return 0;
}

int buttoncb_e_abort()
{
 resetlevel();
 setdispstate(DS_GAME);
 return 0;
}

int buttoncb_e_done()
{
 finish_edit();
 return 0;
}

int buttoncb_e_name()
{
 levname_begin();
 return 0;
}

int buttoncb_e_clone()
{
 clonelevel();
 return 0;
}

int buttoncb_help1()
{
 sethelp(1);
 return 0;
}

int buttoncb_help2()
{
 sethelp(2);
 return 0;
}

int buttoncb_help3()
{
 sethelp(3);
 return 0;
}

int buttoncb_help4()
{
 sethelp(4);
 return 0;
}

int buttoncb_help5()
{
 sethelp(5);
 return 0;
}

int buttoncb_helpret()
{
 setdispstate(DS_GAME);
 return 0;
}

int buttoncb_help()
{
 beginhelp();
 return 0;
}

int buttoncb_credits()
{
 showcredits();
 return 0;
}

int buttoncb_edit()
{
 begin_edit();
 return 0;
}

int buttoncb_save()
{
 if (! curlevel->seg->dirty) return 0;
 saveseg(curlevel->seg);
 return 0;
}

int buttoncb_quit()
{
 exit(0);
 return 0;
}

Display *open_display(disp)
char *disp;
{
 Display *rv;

 rv = XOpenDisplay(disp);
 if (rv == 0)
  { fprintf(stderr,"%s: can't open display %s\n",argvec[0],XDisplayName(disp));
    exit(1);
  }
 return(rv);
}

int main(int ac,char **av)
{
 argvec = av;
 saveargv(ac,av);
 handleargs(ac,av);
 setup_levels();
 disp = open_display(displayname);
 if (syncX) XSynchronize(disp,True);
 scr = XDefaultScreenOfDisplay(disp);
 width = XWidthOfScreen(scr);
 height = XHeightOfScreen(scr);
 rootwin = XRootWindowOfScreen(scr);
 setup_db();
 maybeset(&geometryspec,get_default_value("xblockade.geometry","Game.Geometry"));
 maybeset(&fontname,get_default_value("xblockade.font","Game.Font"));
 maybeset(&background,get_default_value("xblockade.background","Game.Background"));
 maybeset(&foreground,get_default_value("xblockade.foreground","Game.Foreground"));
 maybeset(&bordercstr,get_default_value("xblockade.borderColor","Game.BorderColor"));
 maybeset(&borderwstr,get_default_value("xblockade.borderWidth","Game.BorderWidth"));
 maybeset(&name,get_default_value("xblockade.name","Game.Name"));
 maybeset(&iconname,get_default_value("xblockade.iconName","Game.IconName"));
 maybeset(&selkey,get_default_value("xblockade.selkey","Game.Selkey"));
 setup_visual();
 setup_font();
 setup_colors();
 setup_numbers();
 setup_wingc();
 setup_pixmaps();
 setup_sizes();
 setup_buttons();
 setup_windows();
 setup_game();
 run();
 return(0);
}
