 /*  Xsand, a sandpile simulator for X windows.
           
      Michael Creutz   
   creutz@wind.phy.bnl.gov 

 compiling:  cc -O -o xsand xsand.c -lX11 

 version 1.0 
 13 May 1994 

*/

# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xos.h>
# include <X11/Xatom.h> 
# include <stdio.h>
# include <stdlib.h>
# include <malloc.h>

 /* lattice dimensions (plus two for boundaries): */
 /* NCOLS should be a multiple of 4 */
# define NROWS 200
# define NCOLS 200
# define VOLUME (NROWS*NCOLS)

int block=1; /* default to small blocks */

char field[2][VOLUME];      /* to store the system */
int old=0,new=1;
char * picture; /* for the image structure */
char translate[256]; /* for converting colors */

int paused=0,boundary=0,pencolor=4,active=1,trace=0,autodouble=0;
int mask,nshift;
long mask0x13,mask1,maxi;
long mrand48(),lrand48(); /* generates a random word, lrand is non-negative */
double drand48(); /* generates a random double */
static char *progname;
char stringbuffer[100];

/* dimensions for placing things */
#define WINDOWWIDTH (block*NCOLS+PLAYLEFT+BOUNDWIDTH+20)
#define WINDOWHEIGHT (PLAYTOP+block*NROWS+10)
#define PLAYTOP 90
#define PLAYLEFT 10
#define BOUNDHEIGHT 80
#define TRACEHEIGHT 60
#define BOUNDWIDTH  110
#define BARHEIGHT 15
#define BARWIDTH  120
#define BARTOP 50
#define BARLEFT 5
#define XHEIGHT 40

/* various window stuff */
Display *display;
int screen;
Window window,quitbutton,pausebutton,playground,srbutton,
    boundwindow,colorbar,fillbutton,tracewindow,doublebutton,adbutton;
GC gc, gcr, gcxor, gccolor, gcpen;
XImage *spinimage;
XFontStruct *font=NULL;
int font_height;
XSizeHints size_hints;
XColor xcolor,colorcell;
Colormap cmap;
long event_mask;
int depth,darkcolor,lightcolor,black,white;

main(argc,argv)
int argc;
char **argv;
{unsigned int width, height;
 int i,x,y;
 XEvent report;
 progname=argv[0];
 if (argc>1) /* show image with bigger blocks */
   block=atoi(argv[1]);
 if (block>4) block=4; 
 if (block<1) block=1;
 picture=malloc(block*block*VOLUME); 
 openwindow(argc,argv);
  /* mask used for random site selection */
 mask=1;
 while (mask<VOLUME) mask=1|(mask<<1);
  /* masks for parallel updating */
 mask0x13=0;
 mask1=0;
 for (i=0;i<sizeof(long);i++) 
  {mask0x13=0x13|(mask0x13<<8);
   mask1=1|(mask1<<8);
  }
 maxi=VOLUME/sizeof(long);

   /* set initial state */
 for (i=0;i<VOLUME;i++)
  field[old][i]=field[new][i]=4;
 for (i=0;i<block*block*VOLUME;i++)
  picture[i]=(-1);

/* loop forever, looking for events */
 while(1)
  {if (0==paused)
     update();
   if (paused|XPending(display)|((active|autodouble)==0))
    {XNextEvent(display,&report); 
     switch (report.type)
      {case Expose:
        if (report.xexpose.count!=0) break; /* more in queue, wait for them */
        repaint();  
        break;
       case ConfigureNotify:
        width=report.xconfigure.width;
        height=report.xconfigure.height;
        if ((width<size_hints.min_width)||(height<size_hints.min_height))
            {fprintf(stderr,"%s: window too small to proceed.\n",progname);
             XUnloadFont(display,font->fid);
             XFreeGC(display,gc); 
             XCloseDisplay(display);
             exit(1);
            } 
        break; 
       case ButtonPress:
        if (report.xbutton.window==quitbutton)
            {XUnloadFont(display,font->fid);
             XFreeGC(display,gc); 
             XCloseDisplay(display);
             free(picture);
             exit(1);
            } 
        else if (report.xbutton.window==pausebutton)
            {paused=1-paused;
             mypause();
            }
        else if (report.xbutton.window==adbutton)
            {autodouble=1-autodouble;
             XFillRectangle(display,adbutton,gcxor,
                0,0,60,20 -1+BOUNDHEIGHT/4);
            }
        else if (report.xbutton.window==doublebutton)
            {for (i=0;i<VOLUME;i++)
              field[old][i]<<=1;
             fixboundary();
             showpic();
            }
        else if (report.xbutton.window==fillbutton)
            {for (i=0;i<VOLUME;i++)
              field[old][i]=pencolor;
             fixboundary();
             showpic();
            }
        else if (report.xbutton.window==boundwindow)
            {setboundary((report.xbutton.y*4)/BOUNDHEIGHT);
             showpic();
            }
        else if (report.xbutton.window==tracewindow)
            {settrace((report.xbutton.y*3)/TRACEHEIGHT);
             showpic();
            }
        else if (report.xbutton.window==colorbar)
            {setpen((report.xbutton.x*8)/BARWIDTH);
            }
        else if (report.xbutton.window==playground) /* dump some sand */
            {x=report.xbutton.x/block;
             y=report.xbutton.y/block;
             sketch(x,y);
            }
        else if (report.xbutton.window==srbutton)
          {if (2*report.xbutton.y<XHEIGHT)
              savepic();
           else
              loadpic();
          }
        else
         update();
        break;
       default:
        break;
      } /* end of switch */
    } /* end of if XPending */
  } /* end of while(1) */
} /* end of main */

sketch(x,y)
/* sketch starting at point x,y */
int x,y;
{int sketching,i,oldx,oldy,deltax,deltay,newx,newy;
 Window root,child;
 XEvent sketchreport;
 unsigned int keys_buttons;
 int window_x,window_y;
 newx=x;
 newy=y;
 field[old][x+NCOLS*y]=pencolor;
 showpic();
 sketching=1;
 while (sketching)
  {XNextEvent(display,&sketchreport); 
   switch (sketchreport.type)
    {case MotionNotify: 
      oldx=newx;
      oldy=newy;
      if (!XQueryPointer(display,playground,
        &root,&child,&window_x,&window_y,
        &newx,&newy,&keys_buttons))
        {sketching=0;
         break;
        }
      newx/=block;
      newy/=block;
      if ((newx>NCOLS)|(newy>NROWS)|(newx<0)|(newy<0))
        {sketching=0;
         break;
        }
      deltax=abs(newx-oldx);
      deltay=abs(newy-oldy);
      if (deltax|deltay)
       {if (deltax>deltay)
         for (i=0;i<=deltax;i++)
          field [old][oldx+i*(newx-oldx)/deltax
                  +NCOLS*(oldy+i*(newy-oldy)/deltax)]=pencolor;
        else
         for (i=0;i<=deltay;i++)
          field [old][oldx+i*(newx-oldx)/deltay
                  +NCOLS*(oldy+i*(newy-oldy)/deltay)]=pencolor;
        showpic();
       }
      break;
     case ButtonRelease:
     default:
      sketching=0;
      break;
    } 
  }
 fixboundary();
 active=1;
 return;
}


update()
{int i;
 parallelupdate(field[old],field[new]);
 old=new;
 new=1-new;
 fixboundary();
 showpic(); 
 if ((0==active)&&autodouble)           
  {for (i=0;i<VOLUME;i++)
    field[old][i]<<=1;
   fixboundary();
   showpic();
  }
 return;
}

parallelupdate(source,destination)
/* do several spins at a time */
long * source;
long * destination;
{int i;
 active=0;
 for (i=0;i<maxi;i++)
  {destination[i]=source[i]&mask0x13;
   active|=(source[i]=(source[i]>>2)&mask1);
  }
#define BPW (sizeof(long))
for (i=NCOLS/BPW;i<(VOLUME-NCOLS)/BPW;i++)
  destination[i]+=(source[i  ]>>8)
                 +(source[i-nshift]<<(8*(BPW-1))) 
                 +(source[i  ]<<8)
                 +(source[i+nshift]>>(8*(BPW-1)))
                 + source[i+NCOLS/BPW]
                 + source[i-NCOLS/BPW]; 
if (trace)
 for (i=0;i<maxi;i++)
  destination[i]|=source[i]<<4;
return;
}


showpic()
{int row,col,i1,i2,color,j,j1;
 if (block>1) /* I wish I know how to do this faster */
  for (row=0;row<NROWS;row++)
   for (col=0;col<NCOLS;col++)
    {color=translate[field[old][row*NCOLS+col]];
     j=block*(col+block*NCOLS*row);
     if (color!=picture[j])
      for (i1=0;i1<block;i1++)
        {j1=i1*block*NCOLS+j;
         for (i2=0;i2<block;i2++)
           picture[j1+i2]=color;
        }
    }
 else
   for (j=0;j<VOLUME;j++)
    picture[j]=translate[field[old][j]];
 XPutImage(display,playground,gc,spinimage,0,0,0,0,block*NCOLS,block*NROWS); 
 return;
}

fixboundary()
{int i,bv;
 if (boundary<2)
  {bv=4*boundary;
   for (i=0;i<NCOLS;i++)
    {field[old][i]=bv;
     field[old][VOLUME-NCOLS+i]=bv;
    }
   for (i=0;i<VOLUME;i+=NCOLS)
    {field[old][i]=bv;
     field[old][i+NCOLS-1]=bv;
    }
  }
 else if (2==boundary)
  {for (i=0;i<NCOLS;i++)
    {field[old][i]=field[old][VOLUME-2*NCOLS+i];
     field[old][VOLUME-NCOLS+i]=field[old][NCOLS+i];
    }
   for (i=0;i<VOLUME;i+=NCOLS)
    {field[old][i]=field[old][i+NCOLS-2];
     field[old][i+NCOLS-1]=field[old][i+1];
    }
  }
 else
  {for (i=0;i<NCOLS;i++)
    {field[old][i]=4;
     field[old][VOLUME-NCOLS+i]=0;
    }
   for (i=0;i<VOLUME;i+=NCOLS)
    {field[old][i]=0;
     field[old][i+NCOLS-1]=0;
    }
  }
 return;
} 

repaint()
/* this fixes the window up whenever it is uncovered */
{int i;
 XDrawString(display,quitbutton,gcr,
        0,font_height,"quit",4);
 XDrawString(display,fillbutton,gcr,
        4,font_height,"fill",4);
 mypause();
 XDrawString(display,doublebutton,gcr,
        0,font_height,"double",6);
 XFillRectangle(display,adbutton,gccolor,
                0,0,60,20 -1+BOUNDHEIGHT/4);
 XDrawString(display,adbutton,gcr,
        0,font_height,"auto-d",6);
 if (0==autodouble) 
            {XFillRectangle(display,adbutton,gcxor,
                0,0,60,20 -1+BOUNDHEIGHT/4);
            }
 XDrawString(display,srbutton,gcr,8,font_height,  " save  ",7);
 XDrawString(display,srbutton,gcr,0,XHEIGHT/2+font_height,"restore",7);
 XDrawLine(display,srbutton,gc,
       0,XHEIGHT/2,68,XHEIGHT/2);
/* draw the boundary condition buttons */
 XFillRectangle(display,boundwindow,gcr,0,
        0,BOUNDWIDTH,BOUNDHEIGHT);
 XDrawString(display,boundwindow,gccolor,
        0,font_height,                "    open    ",12);
 XDrawString(display,boundwindow,gccolor,
        0,BOUNDHEIGHT/4+font_height,  "    sandy   ",12);
 XDrawString(display,boundwindow,gccolor,
        5,2*BOUNDHEIGHT/4+font_height,"  periodic ",11);
 XDrawString(display,boundwindow,gccolor,
        5,3*BOUNDHEIGHT/4+font_height,"    flow   ",11);
 for (i=1;i<4;i++)
   XDrawLine(display,boundwindow,gc,
       0,i*BOUNDHEIGHT/4-1,BOUNDWIDTH,i*BOUNDHEIGHT/4-1);
 XFillRectangle(display,boundwindow,gcxor,0,
    (BOUNDHEIGHT*boundary)/4,BOUNDWIDTH,-1+BOUNDHEIGHT/4);
/* draw the trace buttons */
 XFillRectangle(display,tracewindow,gcr,0,
        0,BOUNDWIDTH,TRACEHEIGHT);
 XDrawString(display,tracewindow,gccolor,
        0,font_height,                " tracer off ",12);
 XDrawString(display,tracewindow,gccolor,
        0,TRACEHEIGHT/3+font_height,  " tracer  on ",12);
 XDrawString(display,tracewindow,gccolor,
        5,2*TRACEHEIGHT/3+font_height,"clear trace",11);
 for (i=1;i<3;i++)
   XDrawLine(display,tracewindow,gc,
       0,i*TRACEHEIGHT/3-1,BOUNDWIDTH,i*TRACEHEIGHT/3-1);
 XFillRectangle(display,tracewindow,gcxor,0,
    (TRACEHEIGHT*trace)/3,BOUNDWIDTH,-1+TRACEHEIGHT/3);
 /* fix the colorbar */
 for (i=0;i<8;i++)
  {XSetForeground(display,gcpen,translate[i]);
   XFillRectangle(display,colorbar,gcpen,i*BARWIDTH/8,0,
           BARWIDTH/8,BARHEIGHT);
   sprintf(stringbuffer,"%d",i);
   XDrawString(display,window,gc,
          4+BARLEFT+BARWIDTH*i/8,BARTOP+32,stringbuffer,1);
  }
 XDrawString(display,window,gc,
        BARLEFT+BARWIDTH+16,BARTOP+font_height,"Pen:",4);
 setpen(pencolor);
 /* write various strings */
 sprintf(stringbuffer,"%d by %d lattice",NCOLS-2,NROWS-2);
 XDrawString(display,window,gc,5,40,stringbuffer,strlen(stringbuffer));
 XDrawString(display,window,gc,WINDOWWIDTH-50,WINDOWHEIGHT-5
       ,"MJC",3); 
 showpic();  
 return;
}
 
setboundary(value)
/* take action if boundary buttons hit */
int value;
{XFillRectangle(display,boundwindow,gcxor,0,
    (BOUNDHEIGHT*boundary)/4,BOUNDWIDTH,-1+BOUNDHEIGHT/4);
 boundary=value;
 XFillRectangle(display,boundwindow,gcxor,0,
    (BOUNDHEIGHT*boundary)/4,BOUNDWIDTH,-1+BOUNDHEIGHT/4);
 fixboundary();
 return;
}
 
settrace(value)
/* take action if trace buttons hit */
int value;
{int i;
 XFillRectangle(display,tracewindow,gcxor,0,
    (TRACEHEIGHT*trace)/3,BOUNDWIDTH,-1+TRACEHEIGHT/3);
 if (2==value)
  {XFillRectangle(display,tracewindow,gcxor,0,
    (TRACEHEIGHT*2)/3,BOUNDWIDTH,-1+TRACEHEIGHT/3);
   for (i=0;i<VOLUME;i++)
    field[old][i]&=0xf;
   XFillRectangle(display,tracewindow,gcxor,0,
    (TRACEHEIGHT*2)/3,BOUNDWIDTH,-1+TRACEHEIGHT/3);
  } 
 else trace=value;
 XFillRectangle(display,tracewindow,gcxor,0,
    (TRACEHEIGHT*trace)/3,BOUNDWIDTH,-1+TRACEHEIGHT/3);
 return;
}

setpen(value)
/* take action if pen buttons hit */
int value;
{pencolor=value;
 XSetForeground(display,gcpen,translate[pencolor]);
 XFillRectangle(display,window,gcpen,
     BARLEFT+BARWIDTH+60,BARTOP+2
      ,16,16);
 return;
}

/* fix up the pause button */
mypause()
{if (0==paused) 
   XDrawImageString(display,pausebutton,gcr,
        0,font_height,"pause",5);
 else
   XDrawImageString(display,pausebutton,gcr,
        0,font_height," run ",5);
 return;
}

openwindow(argc,argv)
/* a lot of this is taken from the basicwin program in the
Xlib Programming Manual */
int argc;
char **argv;
{char *window_name="Sand";
 char *icon_name="Sand";
 Pixmap icon_pixmap;
 char *display_name=NULL;
 long bitcheck;
 int i;

#define icon_bitmap_width 16
#define icon_bitmap_height 16
 static char icon_bitmap_bits[] = {
   0x1f, 0xf8, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0xf8,
   0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

/* open up the display */
 if ((display=XOpenDisplay(display_name))==NULL)
  {fprintf(stderr,"%s: cannot connect to X server %s\n",
    progname,XDisplayName(display_name));
   exit(-1);
  }
 screen=DefaultScreen(display);

 depth=DefaultDepth(display,screen);
 cmap=DefaultColormap(display,screen);
    /* color? This is not the right way to do it, but .... */
 if (1==depth) 
   {fprintf(stderr,"Sorry but this program needs a color monitor.\n");
    exit(-1);
   }


 darkcolor=black=BlackPixel(display,screen);
 lightcolor=white=WhitePixel(display,screen);
 translate[0]=white;
 translate[1]=black;
 translate[2]=lightcolor;
 translate[3]=darkcolor;
 if (XAllocNamedColor(display,cmap,"firebrick",&colorcell,&xcolor))
              darkcolor=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"wheat",&colorcell,&xcolor))
              lightcolor=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"black",&colorcell,&xcolor))
              translate[0]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"red",&colorcell,&xcolor))
              translate[1]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"blue",&colorcell,&xcolor))
              translate[2]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"green",&colorcell,&xcolor))
              translate[3]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"orange",&colorcell,&xcolor))
              translate[4]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"gold",&colorcell,&xcolor))
              translate[5]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"yellow",&colorcell,&xcolor))
              translate[6]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"white",&colorcell,&xcolor))
              translate[7]=colorcell.pixel;
 if (XAllocNamedColor(display,cmap,"cornflower blue",&colorcell,&xcolor))
              translate[8]=colorcell.pixel;

  /* fill out the color table for future uses */
 for(i=9;i<256;i++)
   translate[i]=translate[i%8]; 

 for (i=16;i<20;i++)
   translate[i]=translate[8]; /* for tracing */

/* make the main window */
 window=XCreateSimpleWindow(display,RootWindow(display,screen),
   0,0,WINDOWWIDTH,WINDOWHEIGHT,4,black,
   lightcolor);

/* make the icon */
 icon_pixmap=XCreateBitmapFromData(display,window,
   icon_bitmap_bits,icon_bitmap_width,
   icon_bitmap_height);

 size_hints.flags=PPosition | PSize | PMinSize;
 size_hints.min_width=WINDOWWIDTH;
 size_hints.min_height=WINDOWHEIGHT;
#ifdef X11R3
 size_hints.x=x;
 size_hints.y=y;
 size_hints.width=WINDOWWIDTH;
 size_hints.height=WINDOWHEIGHT;
 XSetStandardProperties(display,window,window_name,icon_name,
    icon_pixmap,argv,argc,&size_hints);
#else
 {XWMHints wm_hints;
  XClassHint class_hints;
  XTextProperty windowName, iconName;
  if (XStringListToTextProperty(&window_name,1,&windowName)==0)
   {fprintf(stderr,"%s: structure allocation for windowName failed.\n"
      ,progname);
    exit(-1);
   }
  if (XStringListToTextProperty(&icon_name,1,&iconName)==0)
   {fprintf(stderr,"%s: structure allocation for iconName failed.\n"
       ,progname);
    exit(-1);
   }
  wm_hints.initial_state=NormalState;
  wm_hints.input=True;
  wm_hints.icon_pixmap=icon_pixmap;
  wm_hints.flags=StateHint|IconPixmapHint|InputHint;
  class_hints.res_name=progname;
  class_hints.res_class="Basicwin";
  XSetWMProperties(display,window,&windowName,&iconName,
       argv,argc,&size_hints,&wm_hints,&class_hints);
 }
#endif

/* make the buttons */
 quitbutton=XCreateSimpleWindow(display,window,
    6,0,40,20,2,black,darkcolor);
 pausebutton=XCreateSimpleWindow(display,window,
    56,0,48,20,2,black,darkcolor);
 doublebutton=XCreateSimpleWindow(display,window,
         WINDOWWIDTH-80,2*BOUNDHEIGHT,
         60,20,2,black,darkcolor);
 adbutton=XCreateSimpleWindow(display,window,
         WINDOWWIDTH-80,2*BOUNDHEIGHT+20,
         60,20,2,black,darkcolor);
 playground=XCreateSimpleWindow(display,window,
    PLAYLEFT,PLAYTOP,block*NCOLS,block*NROWS,2,black,white);
 boundwindow=XCreateSimpleWindow(display,window,
             WINDOWWIDTH-BOUNDWIDTH-4,0,BOUNDWIDTH,BOUNDHEIGHT-1,2,
             BlackPixel(display,screen),lightcolor);
 tracewindow=XCreateSimpleWindow(display,window,
             WINDOWWIDTH-BOUNDWIDTH-4,BOUNDHEIGHT+4,BOUNDWIDTH,TRACEHEIGHT-1,2,
             BlackPixel(display,screen),lightcolor);
 colorbar=XCreateSimpleWindow(display,window,
             BARLEFT,BARTOP,BARWIDTH,BARHEIGHT,2,
             BlackPixel(display,screen),lightcolor);
 fillbutton=XCreateSimpleWindow(display,window,
    114,0,48,20,2,black,darkcolor);
 srbutton=XCreateSimpleWindow(display,window,
    WINDOWWIDTH-84,3*BOUNDHEIGHT-20,
    68,XHEIGHT,2,black,darkcolor);

/* pick the events to look for */
 event_mask=ExposureMask|ButtonPressMask|StructureNotifyMask;
 XSelectInput(display,window,event_mask);
 event_mask=ButtonPressMask; 
/* note that with this simple mask if one just covers a button
it will not get redrawn.  I wonder if anyone will notice?  If I put
the exposuremask in here, things flash irritatingly on being uncovered. */
 XSelectInput(display,quitbutton,event_mask);
 XSelectInput(display,pausebutton,event_mask);
 XSelectInput(display,doublebutton,event_mask);
 XSelectInput(display,adbutton,event_mask);
 XSelectInput(display,boundwindow,event_mask);
 XSelectInput(display,tracewindow,event_mask);
 XSelectInput(display,colorbar,event_mask);
 XSelectInput(display,fillbutton,event_mask);
 XSelectInput(display,srbutton,event_mask);
 event_mask=ButtonReleaseMask|ButtonPressMask|PointerMotionHintMask
                   |ButtonMotionMask; 
 XSelectInput(display,playground,event_mask);

/* pick font: 9x15 is supposed to almost always be there */
 if ((font=XLoadQueryFont(display,"9x15"))==NULL)
  {fprintf(stderr,"%s: Cannot open 9x15 font\n",progname);
   exit(-1);
  }
 font_height=font->ascent+font->descent;

/* make graphics contexts: 
      gc for black on white
      gccolor for background and buttons 
      gcr for reverse video 
      gcxor for highlighting */
   
 gc=XCreateGC(display,window,0,NULL);
 XSetFont(display,gc,font->fid);
 XSetForeground(display,gc,black);
 XSetBackground(display,gc,white); 

 gcr=XCreateGC(display,window,0,NULL); 
 XSetFont(display,gcr,font->fid);
 XSetForeground(display,gcr,lightcolor);
 XSetBackground(display,gcr,darkcolor);

 gccolor=XCreateGC(display,window,0,NULL); 
 XSetFont(display,gccolor,font->fid);
 XSetForeground(display,gccolor,darkcolor);
 XSetBackground(display,gccolor,lightcolor); 

 gcpen=XCreateGC(display,window,0,NULL); 
 XSetFont(display,gcpen,font->fid);
 XSetForeground(display,gcpen,translate[pencolor]);
 XSetBackground(display,gcpen,lightcolor); 

 gcxor=XCreateGC(display,window,0,NULL);
 XSetFont(display,gcxor,font->fid);
 XSetForeground(display,gcxor,
             darkcolor^lightcolor);
 XSetFunction(display,gcxor,GXxor);

/* show the window and buttons */
 XMapWindow(display,window);
 XMapWindow(display,quitbutton);
 XMapWindow(display,pausebutton);
 XMapWindow(display,doublebutton);
 XMapWindow(display,adbutton);
 XMapWindow(display,boundwindow);
 XMapWindow(display,tracewindow);
 XMapWindow(display,colorbar);
 XMapWindow(display,fillbutton);
 XMapWindow(display,playground);
 XMapWindow(display,srbutton);

/* make image structure */
 spinimage=XCreateImage(display,(Visual *) &window,8,ZPixmap,0,
    (char*) picture,block*NCOLS,block*NROWS,8,0);

/* test for byte order on client */
 bitcheck=1;
 if (* (char*) (&bitcheck) )
   nshift=(-1);
 else 
   nshift=1;
 return;
}


/* from here on is the stuff for saving and restoring from a gif file *//* for info on how gif works, see ftp://network.ucsd.edu/graphics/GIF.shar.Z */

/* tables for storing codes and addresses to strings */
# define TABLELENGTH (1<<13)
# define TABLEMASK (TABLELENGTH-1)
unsigned char  * addresses[TABLELENGTH];  /* where to find the string */
int codes[TABLELENGTH],linktonext[TABLELENGTH],lengths[TABLELENGTH];
int nextcode; /* the next available code value */
int codeindex[4097]; /* where in the tables a given code is stored,
 need one extra place for overflow before resetting */

loadpic()
/* load in a gif image */
{int i,j,filesize,gwidth,gheight,gvolume,blocksize;
 unsigned char *ptr, *ptr1, *rawgif;
 int colorbits,codesize;
 FILE *infile;
 if (NULL==(infile=fopen("xsand.gif","r")))
   {fprintf(stderr,"couldn't open input file\n");
    return;
   }
    /* find the file size */
 fseek(infile, 0L, 2);
 filesize = ftell(infile);
 fseek(infile, 0L, 0);
  /* make a place in memory for the file */
 if (NULL==(rawgif= (unsigned char *) malloc(filesize) ))
    {fprintf(stderr, "not enough memory to read gif file\n");
     return;
    }
 ptr=ptr1=rawgif;
  /* read in the file */ 
 if (fread(ptr, filesize, 1, infile) != 1)
    {fprintf(stderr, "read failed\n");
     free(rawgif);
     return;
    }
 fclose(infile);
  /* check for GIF signature */
 if (strncmp((char *) ptr,"GIF87a", 6))
     {fprintf(stderr, "not a GIF file\n");
      free(rawgif);
      return;
     }
 ptr+=6;
  /* skip over screen size */
 ptr+=4;
  /* how many bits of color */
 colorbits=1+(*ptr)&0xf;
 ptr+=2; /* skip over background */ 
 if (*ptr)
   {fprintf(stderr, "corrupt GIF file\n");
    free(rawgif);
    return;
   }
 ptr++;
  /* skip over colormap */
 ptr+=(3*(1<<colorbits));
  /* image descriptor */
 if (','!=(*ptr))
   {fprintf(stderr, "corrupt GIF file\n");
    free(rawgif);
    return;
   }
 ptr+=5;
 gwidth=(*ptr)+0x100*(*(ptr+1));
 ptr+=2;
 gheight=(*ptr)+0x100*(*(ptr+1));
 ptr+=2;
 if (0x80&(*ptr)) /* skip over local color map */
   ptr+=(3*(1<<colorbits));
 ptr++;  
 /* should be at start of data */
 codesize=(*ptr++);
  /* deblock the data */
 blocksize=(*ptr++);
 while (blocksize)
  {while (blocksize--)
    *ptr1++=(*ptr++);
   blocksize=(*ptr++);
  }
  /* make a place for the decompressed file */
 gvolume=gwidth*gheight;
 ptr=(unsigned char *) malloc(gvolume);
 decompress(codesize,rawgif,ptr,gvolume);
 free(rawgif);
  /* map picture into field */ 
 for (j=0;j<NROWS;j++)
  {if (j>=gheight) break;
   for (i=0;i<NCOLS;i++)
    {if (i>=gwidth) break;
     field[old][i+j*NCOLS]=ptr[i+j*gwidth];
    }
  }
 free(ptr);
 fixboundary();
 showpic();
 return;
}

savepic()
/* save the field as a gif image */
{int i;
 int colorbits=5,codesize=5;
 FILE *outfile;
 if (NULL==(outfile=fopen("xsand.gif","w")))
   {fprintf(stderr,"couldn't open output file\n");
    return;
   }
  /* GIF signature */
 fwrite("GIF87a",6,1,outfile);
  /* screen descriptor */
 stringbuffer[0]=NCOLS&0xff;
 stringbuffer[1]=(NCOLS>>8)&0xff;
 stringbuffer[2]=NROWS&0xff;
 stringbuffer[3]=(NROWS>>8)&0xff;
 stringbuffer[4]=(0x80) /* M=1; global color map follows */
                |((colorbits-1)<<4) /* -1+ bits of color reslution */
                |(colorbits-1);   /* -1+bits per pixel in image */
 stringbuffer[5]=0; /* background color */
 stringbuffer[6]=0;
 fwrite(stringbuffer,7,1,outfile);
  /* global color map */
 for (i=0;i<(1<<colorbits);i++)
  {colorcell.pixel=translate[i];
   XQueryColor(display,cmap,&colorcell);
   fputc(colorcell.red,outfile);
   fputc(colorcell.green,outfile);
   fputc(colorcell.blue,outfile);
  }
  /* image descriptor */
 stringbuffer[0]=','; /* image descriptor separator */
 stringbuffer[1]=0;
 stringbuffer[2]=0;
 stringbuffer[3]=0;
 stringbuffer[4]=0;
 stringbuffer[5]=NCOLS&0xff;
 stringbuffer[6]=(NCOLS>>8)&0xff;
 stringbuffer[7]=NROWS&0xff;
 stringbuffer[8]=(NROWS>>8)&0xff;
 stringbuffer[9]=0; /* use global color map, no interlace  */ 
 fwrite(stringbuffer,10,1,outfile);
 /* start of image data */
 fputc(codesize,outfile); 
 compress(codesize,field[old],outfile); 
  /* gif terminator */
 fputc(';',outfile);
 fclose(outfile);
 return;
}

/* LZW compression */

compress(initcodesize,ptr,outfile)
int initcodesize; /* the initial compression bits */
char * ptr;       /* where the data comes from    */
FILE * outfile;   /* where the output goes        */
{int currentcode,prefixcode,codesize,maxbits=12,maxcode;
 int clearcode,eoicode,currentplace=0,length,blocksize=0,bitoffset;
 int findcode();
 unsigned long outputword;
 unsigned char blockbuffer[256]; /* to hold data blocks before writing */
   /* set up initial code table */
 inittable(initcodesize);
 clearcode=(1<<initcodesize);
 eoicode=clearcode+1;
 codesize=initcodesize+1;
 maxcode=1<<codesize;
 nextcode=eoicode+1;
  /* start with a clear code */
 outputword=clearcode;
 bitoffset=codesize; 
 while (currentplace<VOLUME)
  {   /* check if codesize needs increasing */
   if (nextcode>maxcode)
    {codesize++;
     maxcode=1<<codesize;
     /* if too big, then reset compressor */
     if (codesize>maxbits)
      {if (bitoffset) outputword|=(clearcode<<bitoffset);
       else outputword=clearcode;
       bitoffset+=maxbits;
       inittable(initcodesize);
       codesize=initcodesize+1;
       maxcode=1<<codesize;
       nextcode=eoicode+1;
      }
    }

    /* look for an unstored string */
   length=1;
   while (nextcode>
            (currentcode=findcode(length,(char *)(ptr+currentplace))))
    {prefixcode=currentcode;
     length++;
     if ((currentplace+length)>=VOLUME) break;
    }
   nextcode++;
   currentplace+=(length-1);
    /* output the prefix code */
   if (bitoffset)  outputword|=(prefixcode<<bitoffset);
   else outputword=prefixcode;
   bitoffset+=codesize;
    /* output finished bytes to blocks */
   while (bitoffset>=8)
     {blockbuffer[blocksize]=outputword&0xff;
      outputword>>=8;
      bitoffset-=8;
      blocksize++;
        /* output filled block */
      if (blocksize>=254)
        {fputc((char) blocksize, outfile);
         fwrite(blockbuffer,blocksize,1,outfile);
         blocksize=0;
        }
     }
  }
   /* output the end of information code */
 if (bitoffset) outputword|=(eoicode<<bitoffset);
 else outputword=eoicode;
 bitoffset+=codesize;
   /* finish outputting the data */
 while (bitoffset>=0) 
     {blockbuffer[blocksize]=(char) (outputword&0xff);
      outputword>>=8;
      bitoffset-=8;
      blocksize++;
      if (blocksize>=254)
        {fputc((char) blocksize, outfile);
         fwrite(blockbuffer,blocksize,1,outfile);
         blocksize=0;
        }
     }
   /* output the last block */
 if (blocksize) 
   {fputc((char) blocksize, outfile);
    fwrite(blockbuffer,blocksize,1,outfile);
   }
  /* a final zero block count */
 fputc(0, outfile);
 return;
}

decompress(initcodesize,ptr,ptr1,volume)
int initcodesize;
unsigned char * ptr, *ptr1; /* compressed data from ptr go to ptr1 */
int volume;
{int i,currentcode,codesize,maxbits=12;
 int clearcode,eoicode,codemask;
 int bitoffset=0,indx,oldindx;
 int currentplace=0,oldplace=0;
 int findcode();
 unsigned long inputword=0;
   /* set up initial code table */
 currentcode=clearcode=(1<<initcodesize);
 eoicode=clearcode+1;
 while (currentcode!=eoicode)
  {if (currentcode==clearcode)
      {inittable(initcodesize);
       codesize=initcodesize+1;
       nextcode=eoicode+1;
       codemask=(1<<codesize)-1;
       oldindx=(-1);
      }
   else /* code represents data */
    {indx=codeindex[currentcode]; /* where in table is currentcode */
     if (indx>=0) /* it is there */
      { /* put it into the output */
       for (i=0;i<lengths[indx];i++)
         ptr1[currentplace+i]=addresses[indx][i];
       if (oldindx>=0) /* first character treated differently */
        {findcode(lengths[oldindx]+1,ptr1+oldplace);
         nextcode++;
        }
       oldplace=currentplace;
       currentplace+=lengths[indx];
       oldindx=indx;
      }
     else /* not in table yet */
      {for (i=0;i<lengths[oldindx];i++)
         ptr1[currentplace+i]=addresses[oldindx][i];
       ptr1[currentplace+lengths[oldindx]]=addresses[oldindx][0];
       findcode(lengths[oldindx]+1,ptr1+currentplace);
       oldplace=currentplace;
       currentplace+=lengths[oldindx]+1;
       oldindx=codeindex[nextcode++];
      }
      /* crude error checking */
     if ((oldindx<0)||(currentplace>volume))
      {fprintf(stderr,"gif file appears to be corrupt\n");
       return;
      }
    } 
     /* check if codesize needs increasing */     
   if (nextcode>codemask)
      if (codesize<maxbits)
        {codesize++;
         codemask=(1<<codesize)-1;
        }
   while (bitoffset<codesize) /* read some more data */
    {if (bitoffset) inputword=inputword|(((int)(*ptr++))<<bitoffset);
     else inputword=(*ptr++);
     bitoffset+=8;
    } 
    /* strip off current code */
   currentcode=inputword&codemask;
   inputword>>=codesize;
   bitoffset-=codesize;
   if (currentcode>nextcode) 
      {fprintf(stderr,"gif file appears to be corrupt\n");
       return;
      }
  }
 return;
}

inittable(size)
int size;
{int i;
 for (i=0;i<TABLELENGTH;i++)
  {linktonext[i]=(-1);
   codes[i]=(-1);
   lengths[i]=(-1);
  }
 for (i=0;i<4096;i++)
   codeindex[i]=(-1);
  /* store initial codes for raw characters */
 nextcode=0;
 for (i=0;i<(1<<size);i++)
  {stringbuffer[i]=i;
   findcode(1,stringbuffer+i);
   nextcode++;
  }
 return;
}

/* hashit is supposed to give a unique fairly random number in the table for
each length a and string b; these integers have no deep significance */

# define hashit(a,b) (51*a+53*(57*b[0]+59*(61*b[a-1]+b[a>>1])))&TABLEMASK

findcode(length,string)
unsigned char * string;
int length;
 /* return code for string of given length;  
    store it and return nextcode if not found */
{int i,j,indx,previousindex;
 indx=hashit(length,string); 
  /* look for string in table */
 previousindex=indx;
 while (indx>0)
  {if (lengths[indx]==length) /* is the length right ? */
    {for (j=0;j<length;j++)
      if ((string[j]) != (addresses[indx][j])) break;
     if (j==length) return codes[indx]; 
    }
   previousindex=indx;
   indx=linktonext[indx];
  } 
  /* not found, so store it */
 indx=previousindex;
 i=indx;
 while (codes[i]>=0) /* find an unused slot in table */
  {i++;
   if (i>=TABLELENGTH) i-=TABLELENGTH;
  }
 if (i!=indx) 
     {linktonext[indx]=i;  /* link to it */
      indx=i;              /* move to it */
     }
 codes[indx]=nextcode; /* save the new code */
 lengths[indx]=length;
 addresses[indx]=string;
 codeindex[nextcode]=indx; 
 return nextcode;
}  

