/* 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 #include // for memset() #include // for sprintf() #include // for malloc(), free(), srand(), rand() #include // 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=0)&&(oldmousey0) while (nh[7] 3 ); mydata[newage][n]=((3|8)&(mydata[age][n]))+sum; n++; } if (mybuttons[0].state<0) for (n=width;nTREE) { 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=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>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;imybuttons[n].x0)&&(xmybuttons[n].y0)&&(y2) { /* 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;i2)) { 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