/* automalab for windoze
   Michael Creutz
   creutz@bnl.gov
   13 December 1999  */

/* This windoze xx program is based on my xtoys package at
      "http://thy.phy.bnl.gov/www/xtoys/xtoys.html"
   The current source and some documentation is at
      "http://thy.phy.bnl.gov/www/xtoys/windoze/" 
   For more features and documentation see the X versions. 
   I am using the cygwin developing tools for compilation,
   but the source should work with other compilers.
   Please let me know if it doesn't. */

#include <windows.h>
#include <string.h>  // for memset()
#include <stdio.h>   // for sprintf()
#include <stdlib.h>  // for malloc(), free(), srand(), rand()
#include <math.h>    // for log(), used to find beta for the Ising case

// fake errno with nocygwin (there must be a better way to do this)
int __errno;

int width,height,volume; /* for the "playground" */
int border=8;  /* padding around window */
int setupflag=0; /* set after initial setup */
int age=0,newage=1; /* label old and new lattices */
int paused=0;  /* set when updating paused */
int sketching=0;  /* set when in drawing mode */
int sketchpen;  /* color for sketching */
int mousex,mousey,oldmousex,oldmousey; 
int delay=0;  /* delay and countdown used to slow updating */
int countdown=0;
int demon=0; /* for the Ising simulation */
int showbeta=0;  /* flag for calculating temperature in Ising model */

/* stuff for customized buttons */
#define NBUTTONS 29
typedef struct {
    int x0;
    int y0;
    int x1;
    int y1;
    char * text;
    int state;
 } mybutton;

mybutton mybuttons[NBUTTONS];

int nbuttons; /* number of currently active buttons */
int drawbuttons(HDC);
int checkbuttons(HWND, int x, int y);
int fontheight, fontwidth;

/* tags for menu items */
enum {s1=1,s2=2,s4=4,s8=8,sand,totalistic,ising,fire,quit,
        fast,medium,slow,pause,
	clear,fill,empty,full,flow,periodic,
	save,restore,add};

int rule;
int boundary;
int scale;
int speed;

/* for saving files */
FILE * mypicture;
char * filename="aulab.bmp";
int savepic();
int restorepic(HWND);
int addpic(HWND);

int update();
int setup(HWND);
int cleanup(HWND);
void showit(HWND);
int initialize(int rule);
int fixboundary();
unsigned char * mydata[2];
char stringbuffer[1024];
int ruletable[512];

/* stuff for windows and bitmaps */
HMENU scalepopup, rulemenu, bmenu, speedpopup, mymenu, filemenu;
BITMAPINFO *pmybmi;
RGBQUAD *mycolors;
COLORREF myrgbcolor[8];
 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
  /* set the initial rule */
  rule=totalistic;
  boundary=empty;
  speed=fast;
  scale=2;
  srand(1234);

  /* make the menus */

  filemenu=CreateMenu();
  AppendMenu(filemenu,MF_STRING,save,"save");
  AppendMenu(filemenu,MF_STRING,restore,"restore");
  AppendMenu(filemenu,MF_STRING,add,"add");
  AppendMenu(filemenu,MF_SEPARATOR,0,NULL);
  AppendMenu(filemenu,MF_STRING,quit,"&quit");

  scalepopup=CreateMenu();
  AppendMenu(scalepopup,MF_STRING,s1,"1");
  AppendMenu(scalepopup,MF_STRING|MF_CHECKED,s2,"2");
  AppendMenu(scalepopup,MF_STRING,s4,"4");
  AppendMenu(scalepopup,MF_STRING,s8,"8");

  rulemenu=CreateMenu();
  AppendMenu(rulemenu,MF_STRING|MF_CHECKED,totalistic,"totalistic");
  AppendMenu(rulemenu,MF_STRING,sand,"sand");
  AppendMenu(rulemenu,MF_STRING,ising,"Ising");
  AppendMenu(rulemenu,MF_STRING,fire,"fire");
 
  bmenu=CreateMenu();
  AppendMenu(bmenu,MF_STRING|MF_CHECKED,empty,"empty");
  AppendMenu(bmenu,MF_STRING,full,"full");
  AppendMenu(bmenu,MF_STRING,flow,"flow");
  AppendMenu(bmenu,MF_STRING,periodic,"periodic");

  speedpopup = CreateMenu();
  AppendMenu(speedpopup,MF_STRING|MF_CHECKED,fast,"fast");
  AppendMenu(speedpopup,MF_STRING,medium,"medium");
  AppendMenu(speedpopup,MF_STRING,slow,"slow");
  AppendMenu(speedpopup,MF_STRING,pause,"pause");

  mymenu = CreateMenu();
  AppendMenu(mymenu,MF_POPUP, (UINT) filemenu,"&File");
  AppendMenu(mymenu,MF_POPUP, (UINT) rulemenu,"&Model");
  AppendMenu(mymenu,MF_POPUP, (UINT) bmenu,"&Boundary");
  AppendMenu(mymenu,MF_POPUP, (UINT) scalepopup,"sca&Le");
  AppendMenu(mymenu,MF_POPUP, (UINT) speedpopup,"&Speed");
  AppendMenu(mymenu,MF_STRING,clear,"&Clear"); 
  AppendMenu(mymenu,MF_STRING,fill,"&Fill"); 

  /* the following is pretty much copied from Petzold */
  static char szAppName[] = "Automalab" ;
  HWND        hwnd ;
  MSG         msg ;
  WNDCLASSEX  wndclass ;

  wndclass.cbSize        = sizeof (wndclass) ;
  wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc   = WndProc ;
  wndclass.cbClsExtra    = 0 ;
  wndclass.cbWndExtra    = 0 ;
  wndclass.hInstance     = hInstance ;
  wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (GRAY_BRUSH) ;
  wndclass.lpszMenuName  = "mymenu";
  wndclass.lpszClassName = szAppName ;
  wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;

  RegisterClassEx (&wndclass) ;

  hwnd = CreateWindow (szAppName,         // window class name
		       "Automalab",     // window caption
		       WS_OVERLAPPEDWINDOW,     // window style
		       CW_USEDEFAULT,           // initial x position
		       CW_USEDEFAULT,           // initial y position
		       CW_USEDEFAULT,           // initial x size
		       CW_USEDEFAULT,           // initial y size
		       NULL,                    // parent window handle
		       mymenu,                    // window menu handle
		       hInstance,               // program instance handle
		       NULL) ;		             // creation parameters

  ShowWindow (hwnd, iCmdShow) ;
  UpdateWindow (hwnd) ;

  SetTimer(hwnd,1,100,NULL);

  while(1) {
    if (paused||sketching||(countdown>0))
      {
	GetMessage (&msg, NULL, 0, 0);
	if (msg.message == WM_QUIT)
	  break;
	TranslateMessage (&msg) ;
	DispatchMessage (&msg) ;
      }
    else if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) 
      {
	if (msg.message == WM_QUIT)
	  break;
	TranslateMessage (&msg) ;
	DispatchMessage (&msg) ;
      }
    else if (setupflag){
      update();
      showit(hwnd);
      countdown=delay;
    }
  }
  return msg.wParam ;
}

/* two routines used while sketching */

int point(HWND hwnd,int x,int y) {
  y=height-y-1; /* bitmaps upside down */
  if (rule==totalistic) /* allows one to erase drawn pixels */
    mydata[age][x+y*width]^=sketchpen;
  else
    mydata[age][x+y*width]=sketchpen;
  showit(hwnd);
  return 0;
}  

int line(HWND hwnd,int x1,int y1,int x2,int y2) {
  /* correction since bitmaps upside down */
  y1=height-y1-1;
  y2=height-y2-1;
  int x,y;
  if ((x2<0)||(x2>width)
          ||(y2<0)||(y2>height)){
 	sketching=0;
	return 0;
	}
  int dx= (x2>x1) ? 1:-1;
  int dy= (y2>y1) ? 1:-1;
  if (abs(x2-x1)>abs(y2-y1)) {
    for (x=x1;x!=x2;x+=dx) {
      y=y1+((y2-y1)*(x-x1))/(x2-x1);
      mydata[age][x+y*width]=sketchpen;
    }
  }
  else if (abs(y2-y1))     
    for (y=y1;y!=y2;y+=dy) {
      x=x1+((x2-x1)*(y-y1))/(y2-y1);
      mydata[age][x+y*width]=sketchpen;
    }
  showit(hwnd);
  return 0;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
  HDC         hdc ;
  PAINTSTRUCT ps ;
  RECT        rect ;
  TEXTMETRIC  tm ;
  int newwidth,newheight;

  switch (iMsg)
    {
    case WM_TIMER :
      countdown--;
      return 0;
    case WM_COMMAND :  // a menu request
      switch (LOWORD(wParam)) {
        case quit:
          PostQuitMessage(0);
          return 0;
        case sand:
        case totalistic:
        case ising:
	case fire:
          CheckMenuItem(rulemenu,rule,MF_UNCHECKED);
	  rule=LOWORD(wParam);
	  CheckMenuItem(rulemenu,rule,MF_CHECKED);
	  initialize(rule);
	  InvalidateRect(hwnd,NULL,TRUE);
	  return 0;
        case s8:
        case s4:
        case s2:
        case s1:
	  CheckMenuItem(scalepopup,scale,MF_UNCHECKED);
	  scale=LOWORD(wParam);
	  CheckMenuItem(scalepopup,scale,MF_CHECKED);
	  cleanup(hwnd);
	  setup(hwnd);
	  InvalidateRect(hwnd,NULL,TRUE);
	  return 0;
        case empty:
        case full:
        case flow:
        case periodic:
	  CheckMenuItem(bmenu,boundary,MF_UNCHECKED);
	  boundary=LOWORD(wParam);
	  CheckMenuItem(bmenu,boundary,MF_CHECKED);
	  return 0;
        case fast:
        case medium:
        case slow:
        case pause:
          CheckMenuItem(speedpopup,speed,MF_UNCHECKED);
          speed=LOWORD(wParam); 
          CheckMenuItem(speedpopup,speed,MF_CHECKED);
	  paused=0;
          if (speed==fast)
	    delay=0;
	  else if (speed==medium)
	    delay=2;
	  else if (speed==slow)
            delay=10;
          else
            paused=1;
	  return 0;
        case clear:
          memset(mydata[age],0,volume);
          memset(mydata[newage],0,volume);
          showit(hwnd);
	  return 0;
        case fill:
          memset(mydata[age],sketchpen,volume);
          showit(hwnd);
	  return 0;
	case save:
	  savepic();
	  return 0;
	case restore:
	  restorepic(hwnd);
	  return 0;
	case add:
          addpic(hwnd);
	  return 0;
        }
        return 0;

    case WM_LBUTTONDOWN :
      oldmousex=(LOWORD(lParam)-border)/scale;
      oldmousey=(HIWORD(lParam)-border)/scale;
      if ((oldmousex>=0)&&(oldmousex<width)
          &&(oldmousey>=0)&&(oldmousey<height)){
        point(hwnd,oldmousex,oldmousey);
 	sketching=1;
	}
      else if (!checkbuttons(hwnd,LOWORD(lParam),HIWORD(lParam))) {
	update();
	showit(hwnd);
      }
      return 0;
    case WM_LBUTTONUP :
      sketching=0;
      return 0;
    case WM_MOUSEMOVE :
      if (sketching) {
        mousex=(LOWORD(lParam)-border)/scale;
        mousey=(HIWORD(lParam)-border)/scale;
        line(hwnd,oldmousex,oldmousey,mousex,mousey);
	oldmousex=mousex;
	oldmousey=mousey;
      }
      return 0;
    case WM_CREATE :
      hdc = GetDC(hwnd);
      GetTextMetrics(hdc,&tm);
      ReleaseDC(hwnd,hdc);
      fontheight=tm.tmHeight;
      fontwidth=tm.tmAveCharWidth;
      return 0 ;

    case WM_PAINT :
      InvalidateRect(hwnd,NULL,TRUE);
      hdc = BeginPaint (hwnd, &ps) ;
      GetClientRect (hwnd, &rect) ;
      // reset the width and height if changed
      newwidth=(rect.right-2*border)/scale;
      newheight=(rect.bottom-2*border-50)/scale;
      newwidth &= (~3); // round down to long
      newheight &= (~1); // round to even
      if ((newwidth!=width)||(newheight!=height)) {
	cleanup(hwnd);
	width=newwidth;
	height=newheight;
	volume=width*height;
	if (volume<=0) volume=width=height=1;
	setup(hwnd);
	initialize(rule);
      }
      drawbuttons(hdc);
      showit(hwnd); 
	sprintf(stringbuffer,"lattice: %d by %d   "
            ,width,height);
	TextOut(hdc,5,rect.bottom-20,stringbuffer,strlen(stringbuffer));
      EndPaint (hwnd, &ps) ;
      return 0 ;

    case WM_DESTROY :
      cleanup(hwnd);
      PostQuitMessage (0) ;
      return 0 ;
    }

  return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}

int history(){
  /* combines what is in newage with current state */
  /* looks nice for single bit automata */
  int n;
  for (n=0;n<volume;n++)
    mydata[age][n]=(1&mydata[age][n])|((1&mydata[newage][n])<<1);
  return 1;
}

int totalisticupdate(){
  int n,nh[8];
  nh[0]=0;
  nh[1]=nh[0]+1;
  nh[2]=nh[0]+2;
  nh[3]=nh[0]+width;
  nh[4]=nh[2]+width;
  nh[5]=nh[0]+2*width;
  nh[6]=nh[1]+2*width;
  nh[7]=nh[2]+2*width;
  n=nh[3]+1;
  // an inversion is done below since DIB's are upside down
  if (mybuttons[27].state>0)
   while (nh[7]<volume){
    int index = (1&mydata[age][n])
             |((1&mydata[age][nh[6]++])<<1)
             |((1&mydata[age][nh[3]++])<<2)
             |((1&mydata[age][nh[4]++])<<3)
             |((1&mydata[age][nh[1]++])<<4)
             |((1&mydata[age][nh[5]++])<<5)
             |((1&mydata[age][nh[7]++])<<6)
             |((1&mydata[age][nh[0]++])<<7)
             |((1&mydata[age][nh[2]++])<<8);
    mydata[newage][n]=ruletable[index];
    n++;
   }
  else  /* only change is the xor below */
   while (nh[7]<volume){
    int index = (1&mydata[age][n])
             |((1&mydata[age][nh[6]++])<<1)
             |((1&mydata[age][nh[3]++])<<2)
             |((1&mydata[age][nh[4]++])<<3)
             |((1&mydata[age][nh[1]++])<<4)
             |((1&mydata[age][nh[5]++])<<5)
             |((1&mydata[age][nh[7]++])<<6)
             |((1&mydata[age][nh[0]++])<<7)
             |((1&mydata[age][nh[2]++])<<8);
    mydata[newage][n]^=ruletable[index];
    n++;
   }
  age=1-age;
  newage=1-age;
  history();
  fixboundary();
  return 0;
}

int sandupdate(){
  /* the BTW sandpile model */
  /* if a cell's value exceeds 3, subtract 4 and add one to each neighbor */
  int n,nh[4];
  nh[0]=0;
  nh[1]=nh[0]+width-1;
  nh[2]=nh[1]+2;
  nh[3]=nh[0]+2*width;
  n=nh[1]+1; 
  int sum;
  while (nh[3]<volume){
    sum=0;
    for (int i=0;i<4;i++)
      sum += ( (7&mydata[age][nh[i]++]) > 3 );
    mydata[newage][n]=((3|8)&(mydata[age][n]))+sum;
    n++;
  }
  if (mybuttons[0].state<0)
    for (n=width;n<volume;n++)
      mydata[newage][n]|=(8&(mydata[age][n] | (mydata[age][n]<<1)));
  age=1-age;
  newage=1-age;
  fixboundary();
  return 0;
}

int fireupdate(){
  int n,nh[4],active=0;
  nh[0]=0;
  nh[1]=nh[0]+width-1;
  nh[2]=nh[1]+2;
  nh[3]=nh[0]+2*width;
  n=nh[1]+1; 
  int sum;
#define TREE 3
#define FIRE 5
  while (nh[3]<volume){
    sum=0;
    for (int i=0;i<4;i++)
      sum |= mydata[age][nh[i]++];
    if (mydata[age][n]==TREE) {
      if (sum>TREE) {
        mydata[newage][n]=FIRE;
        active++;
      } 
     else
        mydata[newage][n]=TREE;
    } else {
      mydata[newage][n]=(rand()&31) ? 0 : TREE;
    }
    n++;
  }
  if (active<10)
    mydata[newage][(int) (volume * ((rand()&0xffff)/(1.*0xffff)))]=FIRE;
  age=1-age;
  newage=1-age;
  fixboundary();
  return 0;
}

int isingupdate(){
  /* microcanonical Ising simulation */
  int newdemon;
  int n,parity,i;
  int x,y;
  static int sweep=0;
  int c0=0,c1=0;
  for (parity=0;parity<2;parity++) {
    for (i=1;i<=29;i++) {
      for (x=1;x<width-1;x++){
	for (y=i;y<height-1;y+=29){
	  n=x+width*y;
	  if (1&(parity^x^y)){
	    newdemon=demon-2
	      +(mydata[age][n]^mydata[age][n-width])
	      +(mydata[age][n]^mydata[age][n-1])
	      +(mydata[age][n]^mydata[age][n+1])
	      +(mydata[age][n]^mydata[age][n+width]);
	    if (newdemon>=0){
	      mydata[age][n]^=1;
	      demon=newdemon;
	    }
	  }
	  c1+=(1&demon);
	  c0++;
	}
	if (mybuttons[0].state<0) demon |= (0==(rand()&127));
	else if (mybuttons[1].state<0) demon &= (~(0==(rand()&31)));
      }
    } 
    fixboundary();
  }
  sweep++;
  if (c1)
    if (sweep>=10) {
      sweep=0;
      showbeta=1;
      double beta=-0.25*log(c1/(1.*(c0-c1)));
      sprintf(stringbuffer,"beta =%6.3g      ",beta);
    }
  return 0;
}

int update(){
  if (rule==sand)
    sandupdate();
  else if (rule==totalistic)
    totalisticupdate();
  else if (rule==ising)
    isingupdate();
  else if (rule==fire)
    fireupdate();
  return 0;
}

int fixboundary(){
  int n,factor,add,topadd;
  factor=add=topadd=0;
  if (boundary==full) 
    if (rule==sand) add=4; 
    else if (rule==fire) add=FIRE;
    else add=1;
    topadd=add;
  if (boundary==flow)
    if (rule==sand) topadd=4;
    else if (rule==fire) topadd=FIRE;
    else topadd=1;
  if (boundary==periodic)
    factor=1;   
    /* fix top and bottom boundaries */
  for (n=0;n<width;n++){
    mydata[age][n]=factor*mydata[age][volume-2*width+n]+topadd;
    mydata[age][volume-width+n]=factor*mydata[age][n+width]+add;
  }
  /* fix left and right boundaries */
  for (n=0;n<height;n++) {
    mydata[age][n*width]=factor*mydata[age][(n+1)*width-2]+add;
    mydata[age][(n+1)*width-1]=factor*mydata[age][n*width+1]+add;
  }
  return 0;     
}

HDC myhdcmem;
HBITMAP myhbm,oldhbm;

void showit(HWND hwnd) {
  /* put the data on the screen */
  HDC hdc=GetDC(hwnd);
  oldhbm=(HBITMAP) GetCurrentObject(hdc, OBJ_BITMAP);
  SetDIBits(myhdcmem,myhbm,0,height,mydata[age],pmybmi,DIB_RGB_COLORS);
  SelectObject(myhdcmem,myhbm);
  if (scale==1)
    BitBlt(hdc,border,border,width,height,myhdcmem,0,0,SRCCOPY);
  else
    StretchBlt(hdc,border,border,width*scale,height*scale,
	       myhdcmem,0,0,width,height,SRCCOPY);
  SelectObject(myhdcmem,oldhbm);
  if (showbeta){
      showbeta=0;
      SetBkColor(hdc,RGB(127,127,127));
      TextOut(hdc,mybuttons[1].x1+4,mybuttons[1].y1,
	      stringbuffer,strlen(stringbuffer));
  }
  ReleaseDC(hwnd,hdc);
  return;
}

void pickcolors() {
    /* find some colors */
  int n=0;
  myrgbcolor[n]=RGB(0,0,0);
  mycolors[n].rgbRed=0;
  mycolors[n].rgbGreen=0;
  mycolors[n].rgbBlue=0;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(255,0,0);
  mycolors[n].rgbRed=255;
  mycolors[n].rgbGreen=0;
  mycolors[n].rgbBlue=0;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(0,0,255);
  mycolors[n].rgbRed=0;
  mycolors[n].rgbGreen=0;
  mycolors[n].rgbBlue=255;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(0,255,0);
  mycolors[n].rgbRed=0;
  mycolors[n].rgbGreen=255;
  mycolors[n].rgbBlue=0;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(255,0,255);
  mycolors[n].rgbRed=255;
  mycolors[n].rgbGreen=0;
  mycolors[n].rgbBlue=255;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(255,255,0);
  mycolors[n].rgbRed=255;
  mycolors[n].rgbGreen=255;
  mycolors[n].rgbBlue=0;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(0,255,255);
  mycolors[n].rgbRed=0;
  mycolors[n].rgbGreen=255;
  mycolors[n].rgbBlue=255;
  mycolors[n++].rgbReserved=0;
  myrgbcolor[n]=RGB(255,255,255);
  mycolors[n].rgbRed=255;
  mycolors[n].rgbGreen=255;
  mycolors[n].rgbBlue=255;
  mycolors[n++].rgbReserved=0;

  for (n=8;n<256;n++){
    mycolors[n].rgbRed=255&(32*(n-8));
    mycolors[n].rgbGreen=255&(32*(n-8));
    mycolors[n].rgbBlue=255&(32*(n-8));
    mycolors[n++].rgbReserved=0;
  } 

  return;
}
	
int setup(HWND hwnd) {
  int n;
  /* make the bitmap to draw into */
  HDC hdc=GetDC(hwnd);
  myhdcmem=CreateCompatibleDC(hdc);
  myhbm=CreateCompatibleBitmap(hdc,width,height);
  // SetMapMode(myhdcmem,GetMapMode(hdc)); (default should work?)
  ReleaseDC(hwnd,hdc);

  /* create working data arrays for my own DI bitmap */
  for (n=0;n<2;n++) {
    if (NULL==(mydata[n]=(unsigned char *) malloc(volume))) {
      PostQuitMessage(0);
      return 1;
    }
  }
  /* make the DI bitmap to work with */
  if (NULL== (pmybmi= (BITMAPINFO *) malloc(sizeof(BITMAPINFO)
          +255*sizeof(RGBQUAD)))) {
      PostQuitMessage(0);
      return 1;
    }
  (*pmybmi).bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
  (*pmybmi).bmiHeader.biWidth=width;
  (*pmybmi).bmiHeader.biHeight=height;
  (*pmybmi).bmiHeader.biPlanes=1;
  (*pmybmi).bmiHeader.biBitCount=8;
  (*pmybmi).bmiHeader.biCompression=0;
  (*pmybmi).bmiHeader.biSizeImage=0;
  (*pmybmi).bmiHeader.biXPelsPerMeter=0;
  (*pmybmi).bmiHeader.biYPelsPerMeter=0;
  (*pmybmi).bmiHeader.biClrUsed=0;
  (*pmybmi).bmiHeader.biClrImportant=0;
  
  mycolors=(*pmybmi).bmiColors;
  pickcolors();
  initialize(rule);
  setupflag=1;
  return 0;
}

int cleanup(HWND hwnd) {
  int n;
  DeleteObject(myhbm);
  ReleaseDC(hwnd,myhdcmem);
  if (setupflag) {
    if (pmybmi) free (pmybmi);
    for (n=0;n<2;n++)
      if(mydata[n]) free(mydata[n]);
  }
  pmybmi=NULL;
  for (n=0;n<2;n++)
    mydata[n]=NULL;
  setupflag=0;
  return 1;
} 

int bitcount(int i){
  int result=0;
  while (i) {
    result++;
    i&=i-1;
  }
  return result;
} 

int makeruletable() {
  int n;
  int nbrhd;
/* high bit of ruletable index is current state; the other bits
   label neighbors as 
    1 2 3
    4 X 5 
    6 7 8  (except for vertical inversion handled elsewhere) */ 

  nbrhd= (mybuttons[1].state<0)
       |((mybuttons[3].state<0)<<1)
       |((mybuttons[5].state<0)<<2)
       |((mybuttons[7].state<0)<<3)
       |((mybuttons[0].state<0)<<4)
       |((mybuttons[2].state<0)<<5)
       |((mybuttons[6].state<0)<<6)
       |((mybuttons[8].state<0)<<7);

  for (n=0;n<512; n+=2) {
    ruletable[n]=ruletable[n+1]=0;
    int count=bitcount(nbrhd&(n>>1));
    if (mybuttons[9+count].state<0) ruletable[n]=1;
    if (mybuttons[18+count].state<0) ruletable[n+1]=1;
  }
  return 1;
}

// size for small buttons
#define BSIZE 18

int initialize(int r) {
  nbuttons=0;
  int i,n,m;
  switch (r) {
  case sand:
    sketchpen=4;
    memset(mydata[age],sketchpen,volume);
    mybuttons[0].x0=32;
    mybuttons[0].y0=scale*height+border+10;
    mybuttons[0].y1=mybuttons[0].y0+fontheight+4;
    mybuttons[0].text="trace";
    mybuttons[0].x1=mybuttons[0].x0+12+fontwidth*strlen(mybuttons[0].text);
    mybuttons[0].state=1;
    mybuttons[1].x0=mybuttons[0].x1+10;
    mybuttons[1].y0=scale*height+border+10;
    mybuttons[1].y1=mybuttons[1].y0+fontheight+4;
    mybuttons[1].text = "clear trace";
    mybuttons[1].x1=mybuttons[1].x0+12+fontwidth*strlen(mybuttons[1].text);
    mybuttons[1].state=1;
    mybuttons[2].x0=mybuttons[1].x1+10;
    mybuttons[2].y0=scale*height+border+10;
    mybuttons[2].y1=mybuttons[2].y0+fontheight+4;
    mybuttons[2].text="double";
    mybuttons[2].x1=mybuttons[2].x0+12+fontwidth*strlen(mybuttons[2].text);
    mybuttons[2].state=1;
    nbuttons=3+8;
    for (i=3;i<11;i++) {
       mybuttons[i].x0=mybuttons[2].x1+10+(i-3)*BSIZE;
       mybuttons[i].x1=mybuttons[i].x0+BSIZE;
       mybuttons[i].y0=mybuttons[i-1].y0;
       mybuttons[i].y1=mybuttons[i].y0+BSIZE;
       mybuttons[i].text="";
       mybuttons[i].state=1;
    }
    mybuttons[3+sketchpen].state=-1;
    break;
  case fire :
    sketchpen=FIRE;
    memset(mydata[age],0,volume);
    break;		
  case totalistic :
    sketchpen=1;
    memset(mydata[age],0,volume);
    memset(mydata[age]+width/2+width*(height/2)-width/4,1,width/2);
    nbuttons=29;
    mybuttons[27].x0=border;
    mybuttons[27].y0=scale*height+border+4;
    mybuttons[27].text="xor past";
    mybuttons[27].y1=mybuttons[27].y0+fontheight+4;
    mybuttons[27].x1=mybuttons[27].x0+12+fontwidth*strlen(mybuttons[27].text);
    mybuttons[27].state=1;
    mybuttons[28].x0=mybuttons[27].x1+4;
    mybuttons[28].y0=scale*height+border+4;
    mybuttons[28].text="reverse";
    mybuttons[28].y1=mybuttons[28].y0+fontheight+2;
    mybuttons[28].x1=mybuttons[28].x0+12+fontwidth*strlen(mybuttons[28].text);
    mybuttons[28].state=0;     /* the rule bars */
    for (n=9;n<18;n++) {
        mybuttons[n].x0=mybuttons[28].x1+10+(n-9)*(BSIZE+1);
        mybuttons[n].x1=mybuttons[n].x0+BSIZE;
        mybuttons[n].y0=scale*height+border+5;
        mybuttons[n].y1=mybuttons[n].y0+BSIZE;
        mybuttons[n].text="";
        mybuttons[n].state=1;
        mybuttons[n+9].x0=mybuttons[n].x0;
        mybuttons[n+9].x1=mybuttons[n+9].x0+BSIZE;
        mybuttons[n+9].y0=scale*height+border+BSIZE+7;
        mybuttons[n+9].y1=mybuttons[n+9].y0+BSIZE;
        mybuttons[n+9].text="";
        mybuttons[n+9].state=1;
    }
     /* start with life */ 
    mybuttons[9+3].state=-1;
    mybuttons[18+2].state=-1;
    mybuttons[18+3].state=-1;
     /* the neighborhood buttons */
    for (n=0;n<3;n++) 
      for (m=0;m<3;m++) {
       i=n+3*m;
       mybuttons[i].x0=mybuttons[17].x1+fontwidth*10+n*(BSIZE);
       mybuttons[i].x1=mybuttons[i].x0+BSIZE;
       mybuttons[i].y0=scale*height+border+2+(BSIZE)*m;
       mybuttons[i].y1=mybuttons[i].y0+BSIZE; 
       mybuttons[i].text="";
       mybuttons[i].state=(i!=4)?-1:1;
    }
    makeruletable();
    break;
  case ising :
    sketchpen=1;
    demon=0;
    for (int i=0;i<volume;i++)
      mydata[age][i]=(0==(rand()&15));
    nbuttons=2;
    mybuttons[0].x0=32;
    mybuttons[0].x1=mybuttons[0].x0+50;
    mybuttons[0].y0=scale*height+border+10;
    mybuttons[0].y1=mybuttons[0].y0+fontheight+2;
    mybuttons[0].text="heat";
    mybuttons[0].state=1;

    mybuttons[1].x0=mybuttons[0].x1+20;
    mybuttons[1].x1=mybuttons[1].x0+50;
    mybuttons[1].y0=scale*height+border+10;
    mybuttons[1].y1=mybuttons[1].y0+fontheight+2;
    mybuttons[1].text="cool";
    mybuttons[1].state=1;
    break;
  }
  return 1;
}

int checkbuttons(HWND hwnd, int x, int y) {
  int n,i,nbrs;
  for (n=0;n<nbuttons;n++) {
    if   ((x>mybuttons[n].x0)&&(x<mybuttons[n].x1)
        &&(y>mybuttons[n].y0)&&(y<mybuttons[n].y1)){
      if (rule==sand) {
        mybuttons[n].state*=(-1); /* toggle state */
        if (n==2) { /* double things mod 8 */
	  for (i=0;i<volume;i++)
            mydata[age][i]=(mydata[age][i]<<1)&7;
          showit(hwnd);
          mybuttons[n].state = 1; 
	}
        else if (n==1) 	 { /* remove any trace information */
	  for (i=0;i<volume;i++)
            mydata[age][i]&=7;
          showit(hwnd);
          mybuttons[n].state = 1; 
	}
        else if (n>2) {  /* change the sketching pen */
          mybuttons[sketchpen+3].state=1;
          sketchpen=n-3;
          mybuttons[n].state=-1;
        }
      }
      else if (rule==ising) {
        if (n==0) {
          mybuttons[0].state*=(-1);
          mybuttons[1].state = 1;
        } else {
          mybuttons[1].state*=(-1);
          mybuttons[0].state = 1;
        }
      }        
      else if (rule==totalistic) {
         if ((n!=4)&&(n!=28)) mybuttons[n].state*=(-1);
         if (n<9) { /* turn off unneeded buttons */
          nbrs=0;
          for (i=0;i<9;i++)
            nbrs+=(mybuttons[i].state<0);
          for (i=0;i<=nbrs;i++){
            if (mybuttons[9+i].state==0) mybuttons[9+i].state=1;
            if (mybuttons[18+i].state==0) mybuttons[18+i].state=1;
          }
          for (i=nbrs+1;i<9;i++)
           mybuttons[9+i].state=mybuttons[18+i].state=0; 
         } 
         if (n<27) // the rule has changed
            makeruletable();
         if (n==27) 
           mybuttons[28].state=(mybuttons[27].state<0);
         if (n==28) { // the reverse button
	   mybuttons[28].state *= -1;  
           for (int i=0;i<volume;i++){
             int temp=1&mydata[age][i];
             mydata[age][i]=mydata[newage][i];
             mydata[newage][i]=temp;
           }
 	 }
      }
      HDC hdc=GetDC(hwnd);
      drawbuttons(hdc);
      ReleaseDC(hwnd,hdc);
      return 1;
    }
  }
  return 0;
}

int drawbuttons(HDC hdc) {
  int n,length;
  HBRUSH mybrush=NULL;
  HPEN darkcolor=CreatePen(PS_SOLID,0,RGB(0,0,0));
  HPEN lightcolor=CreatePen(PS_SOLID,0,RGB(255,255,255));
  SetBkMode(hdc,TRANSPARENT);
  for (n=0;n<nbuttons;n++){
    if (mybuttons[n].state<0) {
      SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
    } else {
      SelectObject(hdc,GetStockObject(GRAY_BRUSH));
    }
    if ((rule==sand)&&(n>2)) {
      mybrush=CreateSolidBrush(myrgbcolor[n-3]);
      SelectObject(hdc,mybrush);
    }
    SelectObject(hdc,GetStockObject(NULL_PEN));
    Rectangle(hdc,mybuttons[n].x0,mybuttons[n].y0,
                  mybuttons[n].x1,mybuttons[n].y1);
    if (mybrush) {
       DeleteObject(mybrush);
       mybrush=NULL;
    }
    length=strlen(mybuttons[n].text);
    if (length && (mybuttons[n].state))
      TextOut(hdc,(mybuttons[n].x0+mybuttons[n].x1-length*fontwidth)/2, 
             mybuttons[n].y0,
             mybuttons[n].text,length);               
    /* decorate buttons */    
    if (mybuttons[n].state) {
          if (mybuttons[n].state<0)
        	SelectObject(hdc,darkcolor);
          else if (mybuttons[n].state>0)
                SelectObject(hdc,lightcolor);
          MoveToEx(hdc,mybuttons[n].x0,mybuttons[n].y1-2,NULL);
          LineTo(hdc,mybuttons[n].x0,mybuttons[n].y0);
          LineTo(hdc,mybuttons[n].x1-2,mybuttons[n].y0);
          if (mybuttons[n].state>0)
        	SelectObject(hdc,darkcolor);
          else if (mybuttons[n].state<0)
                SelectObject(hdc,lightcolor);
          LineTo(hdc,mybuttons[n].x1-2,mybuttons[n].y1-2);
          LineTo(hdc,mybuttons[n].x0,mybuttons[n].y1-2);
	}
  }
  /* draw miscellaneous text */
  if (rule==totalistic){
    sprintf(stringbuffer,"neighbors");
    TextOut(hdc,mybuttons[2].x1+4,mybuttons[2].y1,stringbuffer,strlen(stringbuffer));
    sprintf(stringbuffer,"births");
    TextOut(hdc,mybuttons[17].x1+4,mybuttons[17].y0,stringbuffer,strlen(stringbuffer));
    sprintf(stringbuffer,"survivors");
    TextOut(hdc,mybuttons[26].x1+4,mybuttons[26].y0,stringbuffer,strlen(stringbuffer));
    for (n=0;n<=8;n++) {
      sprintf(stringbuffer,"%d",n);
      TextOut(hdc,mybuttons[18+n].x0+2,mybuttons[18+n].y1,
          stringbuffer,strlen(stringbuffer));
    }
  }
  if (rule==sand) {
    for (n=0;n<8;n++) {
      sprintf(stringbuffer,"%d",n);
      TextOut(hdc,mybuttons[3+n].x0+2,mybuttons[3+n].y1,
          stringbuffer,strlen(stringbuffer));
    }
  }
  DeleteObject(lightcolor);
  DeleteObject(darkcolor);
  return 1;
}

int savepic(){
  int size,offset;
  size=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFO)+255*sizeof(RGBQUAD);
  offset=size;
  BITMAPFILEHEADER mybmfh;
  mybmfh.bfType='B'+256*'M'; /* fucking backwards byte order */
  mybmfh.bfSize=size+volume;
  mybmfh.bfReserved1=mybmfh.bfReserved2=0;
  mybmfh.bfOffBits=offset;
  if (NULL==(mypicture=fopen(filename,"w"))) return 0;
  fwrite(&mybmfh,1,sizeof(BITMAPFILEHEADER),mypicture);
  fwrite(pmybmi,1,size-sizeof(BITMAPFILEHEADER),mypicture);
  fwrite(mydata[age],1,volume,mypicture);
  fclose(mypicture);
  return 1;
}

int restorepic(HWND hwnd){
  BITMAPFILEHEADER mybmfh;
  if (NULL==(mypicture=fopen(filename,"r"))) return 0;
  fread(&mybmfh,1,sizeof(BITMAPFILEHEADER),mypicture);
  fseek(mypicture,sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFO)-sizeof(RGBQUAD),SEEK_SET);
  fread(mycolors,1,255*sizeof(RGBQUAD),mypicture);
  fseek(mypicture,mybmfh.bfOffBits,SEEK_SET);
  fread(mydata[age],1,volume,mypicture);
  fclose(mypicture);
  showit(hwnd);
  return 1;  
}

int addpic(HWND hwnd){
  BITMAPFILEHEADER mybmfh;
  if (NULL==(mypicture=fopen(filename,"r"))) return 0;;
  fread(&mybmfh,1,sizeof(BITMAPFILEHEADER),mypicture);
  fseek(mypicture,mybmfh.bfOffBits,SEEK_SET);
  fread(mydata[newage],1,volume,mypicture);
  fclose(mypicture);
  for (int n=0;n<volume;n++)
    mydata[age][n]=7&(mydata[age][n]+mydata[newage][n]);
  showit(hwnd);
  return 1;
}