/* Program: LIFE Author : Kim Moser Date : 10/24/88 System : IBM PC, Borland Turbo-C 2.0 Descrip: IBM PC implementation of John Conway's LIFE simulation. Usage : LIFE [path_to_gfx_dvr] Hardware required: IBM PC or compatible with EGA card. (Should work with CGA or monochrome card, but I haven't tested it.) Compiler: Borland's Turbo C 2.0 Compiler settings: o Nested comments: ON o Model: Large Linker settings: o Graphics library: ON */ #include #include #include /* Import strlen() */ #include /* Import delay() */ #include #include #define FALSE 0 #define TRUE 1 typedef unsigned short int boolean; #define KBDFLAGS 0x000000417L /* Address of keyboard status flags */ boolean shift(void) /* Returns TRUE if either [or both] SHIFT key(s) are down, else FALSE */ { return ( ( (*((char far *) KBDFLAGS) & 0x01)) || ( (*((char far *) KBDFLAGS) & 0x02)) ); } /************************************************************************** Screen I/O identifiers **************************************************************************/ #define MAXROW 25 /* Width and height of screen [in characters]; may be changed if video card supports more rows and/or columns */ /* Color definitions [assumed to be unsigned short integers] */ #define BLACK 0 #define BLUE 1 #define GREEN 2 #define CYAN 3 #define RED 4 #define MAGENTA 5 #define BROWN 6 #define LIGHTGREY 7 #define DARKGREY 8 #define LIGHTBLYE 9 #define LIGHTGREEN 10 #define LIGHTCYAN 11 #define LIGHTRED 12 #define LIGHTMAGENTA 13 #define YELLOW 14 #define BRIGHTWHITE 15 #define FLASHING 8 /* Added to a background that's [0..7] will cause foreground to flash */ /* Types for screen I/O: */ typedef unsigned char scrnbyte; /* Can be considered an unsigned short int */ typedef struct scrnchar { scrnbyte ch, at; /* Character and its attribute [on screen] */ }; typedef unsigned int scrncoord; typedef unsigned short int color; typedef struct scrnchar far *vid_addr; /* Screen I/O vars */ static scrnbyte fgd = BRIGHTWHITE; /* Default foreground color */ static scrnbyte bgd = BLACK; /* Default background color */ static scrnbyte att = BRIGHTWHITE; /* Default attribute byte */ /* @@@ NOT USED static scrncoord curx=1, cury=1; /* Cursor coordinates for write() procedures */ */ #define COLOR 0xB8000000L #define MONO 0xB0000000L unsigned char far *CRT_MODE = (unsigned char far *) 0x00400049L; /* BIOS video mode number */ unsigned int far *CRT_COL = (unsigned int far *) 0x0040004AL; /* Pointer to number of columns */ vid_addr SCREEN = (vid_addr) COLOR; /* Default */ /* PC ASCII characters: vertical and horizontal bars: */ #define HORIZ_LINE 196 #define VERT_LINE 179 /* This is what gets added to extended keystrokes to differentiate them from 'normal' keystrokes: */ #define EXTENDED 256 /* Ordinal keystroke values: */ #define ESCKEY 27 #define ENTERKEY 13 #define BACKSPACEKEY 8 #define ALT_G_KEY (34+EXTENDED) #define DELETEKEY (83+EXTENDED) #define UPKEY (72+EXTENDED) #define DOWNKEY (80+EXTENDED) #define LEFTKEY (75+EXTENDED) #define RIGHTKEY (77+EXTENDED) #define CTRL_LEFTKEY (115+EXTENDED) #define CTRL_RIGHTKEY (116+EXTENDED) #define HOMEKEY (71+EXTENDED) #define ENDKEY (79+EXTENDED) #define PGUPKEY (73+EXTENDED) #define PGDNKEY (81+EXTENDED) #define CTRL_PGUPKEY (132+EXTENDED) #define CTRL_PGDNKEY (118+EXTENDED) #define F1KEY (59+EXTENDED) #define F2KEY (60+EXTENDED) #define F3KEY (61+EXTENDED) #define F4KEY (62+EXTENDED) #define F5KEY (63+EXTENDED) #define F6KEY (64+EXTENDED) #define F7KEY (65+EXTENDED) #define F8KEY (66+EXTENDED) #define F9KEY (67+EXTENDED) #define F10KEY (68+EXTENDED) #define SH_F1KEY (84+EXTENDED) #define SH_F2KEY (85+EXTENDED) #define SH_F3KEY (86+EXTENDED) #define SH_F4KEY (87+EXTENDED) #define SH_F8KEY (91+EXTENDED) #define CTRL_F1KEY (94+EXTENDED) #define CTRL_F2KEY (95+EXTENDED) #define CTRL_F3KEY (96+EXTENDED) #define CTRL_F4KEY (97+EXTENDED) #define CTRL_F5KEY (98+EXTENDED) #define ALT_F1KEY (104+EXTENDED) #define ALT_F2KEY (105+EXTENDED) #define ALT_F3KEY (106+EXTENDED) #define ALT_F4KEY (107+EXTENDED) #define ALT_F5KEY (108+EXTENDED) /*************** LIFE identifiers ***************/ typedef unsigned int cellcoord; /* Definitions specific to LIFE: */ /* $00, $01, $10, $11: */ #define DEAD 0 #define LIVE 1 #define WALL 2 #define BORDER 3 /* Main status: */ /* [These could be enumerated] */ #define MAINMENU 0 #define GROWING 1 #define EDITING 2 #define LOADING 3 #define SAVING 4 #define CONFIGURING 5 /* You may change these defaults: */ unsigned int FIELDWIDTH = 100; unsigned int FIELDHEIGHT = 100; unsigned long int FREERAM; /* How much free core left */ /* Previous generation buffer [arbitrary size]: */ unsigned int MAXGEN = 10; boolean USE_WALLS = TRUE; boolean USE_COMMENTS = TRUE; /* Cell types: */ typedef unsigned short int cell; typedef struct cellblock { unsigned char cell0; /* Can't use these bitfields because a cellblock would take up 16 bits, the natural int size on a PC. Instead, use a 'char' to force it to 8 bits. cell0: BITSINCELL; cell1: BITSINCELL; cell2: BITSINCELL; cell3: BITSINCELL; */ }; unsigned short int BITSINCELL; unsigned short int CELLSINBLOCK; struct cellblock ANDMASK; /* Typical field of cells: */ typedef struct cellblock *field; /* mainfield -> field->cell[] | V field->cell[] | V field->cell[] | V field->cell[] ... {MAXGEN times} */ /* typedef struct cellblock field [FIELDHEIGHT][FIELDWIDTH/CELLSINBLOCK+(FIELDWIDTH%CELLSINBLOCK==0 ? 0 : 1)]; */ /* (If CELLSINBLOCK does not fit evenly into FIELDWIDTH then round up to the next even byte) Structure: 0 1 2 3 <-- Byte ==--==-- ==--==-- ==--==-- ==--==-- 0 1 2 3 4 5 6 7 8 91011 12131415 <-- Cell */ /* Typical comments: */ typedef char *comments; /* [FIELDHEIGHT][FIELDWIDTH] */ /* Other vars: */ static unsigned int status; static unsigned int lastgen = 0; /* Index of latest valid generation in array of fields */ static unsigned int showgen = 0; /* Index of generation shown to user [n=current, n-1=previous, etc.] */ static unsigned int gencount = 0; /* Displayed on status line: 0=current, 1=next oldest, etc. */ static scrncoord winx1=3, winy1=2, winx2=78, winy2=21; /* Screen coords of editor window */ static cellcoord editX=0, editY=0; /* Cell coordinates of editor cursor */ static cellcoord cellX=0, cellY=0; /* Cell coordinates of cell in top-left corner of editor window. */ static scrncoord crsrX=3, crsrY=2; /* Screen coordinates of cursor in editor window */ static cell pen = LIVE; /* Pen character [for editor] */ static boolean pendn; /* 0=up, 1=dn */ /* Status box coords [you may change these defaults]: */ static scrncoord statx1 = 1; static scrncoord staty1 = (MAXROW-2); static scrncoord statx2 = 80; /* Gets initialized later */ static scrncoord staty2 = MAXROW; /* You may change these defaults: */ static char livech = '\376'; /* Screen character to represent live cells */ static color livefg = BRIGHTWHITE; /* Live cell foreground color */ static color livebg = BLACK; /* Live dell background color */ static char deadch = '\372'; /* Screen character to represent dead cells */ static color deadfg = BLUE; /* Dead cell foreground color */ static color deadbg = BLACK; /* Dead cell background color */ static char wallch = '#'; /* Screen character to represent walls */ static color wallfg = GREEN; /* Wall foreground color */ static scrnbyte wallbg = BLUE; /* Wall background color */ static char borderch = '\260'; /* Screen character to represent out-of-field */ static color borderfg = LIGHTGREY; static color borderbg = BLACK; static char crsrch = 'X'; /* Default screen character to represent cursor */ static color crsrfg = BRIGHTWHITE; /* Cursor foreground color */ static color crsrbg = BLUE; /* Cursor background color */ static color commentfg = LIGHTCYAN; /* Comment foreground color */ static color commentbg = BLACK; /* Commend background color */ static boolean showcomments = TRUE; /* Whether to display comments */ static boolean linemode = FALSE; /* Whether arrow keys draw graphic lines */ static char fieldfilename[] = "LIFE.FLD"; static char notesfilename[] = "LIFE.CMT"; field mainfield; comments notes; /* Array 'notes' is used for comments */ int gmode, gdriver; /* Graphics mode and driver */ char *gpath; /* Gets initialized by main() */ #define min(a_1,b_1) (a_1 < b_1 ? a_1 : b_1) #define max(a_1,b_1) (a_1 > b_1 ? a_1 : b_1) /* Functions for this 'module': */ void set_color( f, b ) /* Creates attribute byte 'att' from 'fgd' and 'bgd' colors */ color f, b; { /* Are we setting colors that have already been set? */ if ( (fgd != f) || (bgd != b) ) { /* Set foreground and background colors: */ fgd = f; bgd = b; /* Make attribute byte: */ att = (bgd << 4) + fgd; } } scrncoord xytoadr( x, y ) scrncoord x, y; /* Returns offset from (0,0), of location (x,y) on screen, according to screen width and height #defined */ { return (scrncoord) ( (y-1)* *CRT_COL + (x-1) ); } void writeat( x, y, s ) /* Write string 's' at screen location 'x','y' (1,1 = top left). Does NOT check whether string extends past last screen location. */ scrncoord x,y; char s[]; { int i; /* Index into 's' */ vid_addr scrn; /* Points to char/attribute */ scrn = (vid_addr) (SCREEN + xytoadr(x,y)); /* Must be FAR because screen memory is in a different data segment */ /* @@@ scrn = VAL( scrnchar, SCREEN + xytoadr(x,y) ); */ /* @@@ REPLACE IF USED: curx = x; cury = y; */ for ( i=0; s[i] != '\0'; i++ ) { scrn->ch = s[i]; /* Write character on screen */ (scrn++)->at = att; /* Write attribute and point to next char on screen */ /* @@@ REPLACE IF USED: curx++; */ } } void writeatcenter( y, s ) scrncoord y; char s[]; { writeat( (*CRT_COL-strlen(s))/2, y, s ); } void writeatch( x, y, c ) /* Write character 'c' at screen location 'x','y' (1,1 = top left). Note: c must be passed as 'c', NOT "c", because it is a char. */ scrncoord x,y; unsigned char c; { char s[2]; s[0] = c; s[1] = '\0'; writeat( x, y, s ); } /* NOT USED void writestr( s ) /* Writes string 's' at (curx,cury) */ char s[]; { writeat( curx, cury, s ); } void writech( c ) /* Writes char 'c' at (curx,cury) */ unsigned char c; { writeatch( curx, cury, c ); } */ void fillarea( x1, y1, x2, y2, f, b, c ) /* Fills area of screen bounded by (x1,y1) and (x2,y2) [offset from (1,1)] with char 'c' using foreground and background colors 'f' and 'b' */ scrncoord x1, y1, x2, y2; color f, b; char c; { scrncoord x, y; /* Local row, col counters */ set_color( f, b ); for ( y=y1; y<=y2; y++ ) { /* Fill all requested rows */ for ( x=x1; x<=x2; x++ ) { /* Fill all requested columns of current row */ writeatch( x, y, c ); } } } void clearscr( f, b ) unsigned int f, b; { fillarea( 1, 1, *CRT_COL, MAXROW, f, b, ' ' ); } char *inttostr( x, w, s ) long int x; unsigned short int w; char s[]; { char format[] = "%1li"; if (w > 9) s[0] = '\0'; else { format[1] = '0' + w; sprintf( s, format, x ); } return s; } /* NOTE: IF YOU DON'T WANT TO USE [OR FOR SOME REASON DON'T HAVE ACCESS TO] STDIO, YOU CAN USE THE FOLLOWING PROCEDURE: /* Sets 's' to character string representation of 'x' [assumes 's' is long enough to hold up to 7 chars ["-32767\0"]. Includes minus sign ('-') if negative, but omits plus sign ('+') if positive. Returns pointer to beginning of converted string. */ unsigned int i = 0; /* Index into 's' */ unsigned short int d; /* Each digit [takes values 0..9] */ unsigned int place; /* Counts down from 10000 to 1 */ int firstnonzero = FALSE; /* Set to TRUE when first non-zero char found */ if (x<0) { x = -x; /* Make positive */ s[i++] = '-'; } /* Assume 'x' can be no larger than 32767 */ for (place=10000; place>=1; place /= 10) { if (x>=place) { /* Should digit be '1'..'9'? */ d = x / place; /* Result will be 1..9 */ x -= (d*place); /* Remainder */ s[i++] = '0' + d; /* ASCII '0'..'9' */ firstnonzero = TRUE; } else { /* Zero in this place, but add it only if a non-zero was previously printed: */ if ( (firstnonzero) || (place==1) ) s[i++] = '0'; else s[i++] = ' '; } } s[i] = '\0'; /* Terminate string properly */ return &(s[0]); */ /*************************************************************************/ int keyhit(void) { int ch; if ((ch=getch()) == NULL) /* Assumes that extended key code is next char returned, and that getch() will NOT wait for a key [i.e. key will be available immediately]. */ return (getch()+EXTENDED); else return ch; } boolean fileexists( s ) /* If file 's' exists [i.e. could be opened for reading], returns TRUE, else FALSE */ char s[]; { FILE *fp; boolean result; if ( (result = ((fp=fopen(s,"r")) != NULL)) == TRUE ) fclose(fp); return result; } /*************************************************************************/ int lotus( x, y, f1, b1, f2, b2, h_v, s ) scrncoord x, y; color f1, b1, f2, b2; boolean h_v; char s[]; /* At screen location (x,y), draw words from 's' either horizontally or vertically, depending on 'h_v', in colors 'f1', 'b1', letting user pick one with arrow keys; highlighted choice is drawn in 'f2', 'b2'; returns which one picked [0=error, 1=first, 2=second, etc.]. If ESCape hit, returns -1. Words in 's' are separated by tabs ['\t']. Assumes no leading and trailing TABs. */ { struct { scrncoord x,y; /* Screen coords of this menu item */ char choice[80]; } mnu[20]; /* 'mnu' is an array of up to 20 menu items, each < 80 chars */ int i=0,j=0,k=0; /* Indices into s, mnu, and mnu[k].choice, respectively */ scrncoord a=x,b=y; /* Coords of each menu choice */ unsigned int ch; /* Keystroke */ boolean update = FALSE; /* Whether screen needs updating after keypress */ /* First menu choice is at x,y: */ mnu[0].x = a; mnu[0].y = b; /* Parse 's' into local array: */ for ( i=0; s[i]!='\0'; i++ ) { if (s[i]=='\t') { /* New word/menu choice */ /* Seal off this string and point to next: */ mnu[k++].choice[j] = '\0'; j=0; /* Start at first char of new string */ /* Point to next char in 's' [assumed not to be '\t']: */ i++; if (h_v) a++; else b++; /* Set coords of next menu choice: */ mnu[k].x = a; mnu[k].y = b; } mnu[k].choice[j++] = s[i]; if (h_v) a++; } mnu[k].choice[j] = '\0'; /* Seal off last string */ /* 'k' now contains number of menu choices */ /* Display menu: */ set_color( f1, b1 ); for (i=0; i<=k; i++) { writeat( mnu[i].x, mnu[i].y, mnu[i].choice ); } /* Write first menu choice in selected colors: */ set_color( f2, b2 ); writeat( mnu[0].x, mnu[0].y, mnu[0].choice ); /* Let user select choice: */ i = 0; do { if ((ch=keyhit())==ESCKEY) return (-1); switch(ch) { case UPKEY: case LEFTKEY: j = i; /* Save old index */ if (--i < 0) i=k; update = TRUE; break; case DOWNKEY: case RIGHTKEY: j = i; /* Save old index */ if (++i > k) i=0; update = TRUE; break; default: update = FALSE; break; } if (update) { set_color( f2, b2 ); writeat( mnu[i].x, mnu[i].y, mnu[i].choice ); set_color( f1, b1 ); writeat( mnu[j].x, mnu[j].y, mnu[j].choice ); } } while (ch != ENTERKEY); return i; } /*************************************************************************/ unsigned int fheight(void) /* Returns height [in bytes] of a field */ { return FIELDHEIGHT; } unsigned int fwidth(void) /* Returns width [in bytes] of a field */ { return (FIELDWIDTH/CELLSINBLOCK+(FIELDWIDTH%CELLSINBLOCK==0 ? 0 : 1)); } unsigned int fsize(void) { return ( fheight() * fwidth() ); } char commentat( x, y ) cellcoord x, y; { if ( USE_COMMENTS && (x=FIELDWIDTH) || (y>=FIELDHEIGHT) ) return BORDER; else { tmp = (BITSINCELL*(CELLSINBLOCK-(x%CELLSINBLOCK))) - BITSINCELL; return ( f[y*fwidth()+ x/CELLSINBLOCK].cell0 >> tmp ) & ANDMASK.cell0; } } void setcellat( f, x, y, c ) /* Sets cell in field 'f' at coordinates (x,y) [offset from (0,0)] to 'c' [assumed to be LIVE, DEAD, or WALL] */ field f; cellcoord x, y; cell c; { int tmp; struct cellblock cb; if ( (c != DEAD) && (c != LIVE) && (c != WALL) ) { printf( "setcellat(): internal error: invalid cell value passed: %u", c ); return; } tmp = (BITSINCELL*(CELLSINBLOCK-(x%CELLSINBLOCK))) - BITSINCELL; cb = f[y*fwidth() + x/CELLSINBLOCK]; f[y*fwidth() + x/CELLSINBLOCK].cell0 = (cb.cell0 & ~(ANDMASK.cell0<= MAXGEN) lastgen = 0; showgen = lastgen; /* Assume user always sees current generation after a 'grow' */ /* Update screen: */ if (hires) showfieldhires( &mainfield[fsize()*lastgen], cellX,cellY, 1,1,min(getmaxx(),FIELDWIDTH),min(getmaxy(),FIELDHEIGHT) ); else showfield( &mainfield[fsize()*lastgen], cellX,cellY, winx1, winy1, winx2, winy2 ); } while ( !shift() ); /* Until shift key down */ if (hires) { closegraph(); /* Return to text mode */ clearscr( BRIGHTWHITE, BROWN ); showfield( &mainfield[fsize()*lastgen], cellX,cellY, winx1, winy1, winx2, winy2 ); } } void edit( f ) /* Edits ARRAY OF fields 'f' in a window bounded by (winx1,winy1) and (winx2,winy2) */ field f; { int ch; /* Key hit */ pendn = FALSE; /* Default to 'up' */ pen = cellat( &f[fsize()*showgen], editX, editY ); linemode = FALSE; initstatus(EDITING); do { if (pendn) { setcellat( &f[fsize()*showgen], editX, editY, pen ); } /* Draw cursor: */ set_color( crsrfg, crsrbg ); writeatch( crsrX, crsrY, crsrch ); updatestatus(); ch = keyhit(); if (linemode) { switch(ch) { case UPKEY: case DOWNKEY: setcommentat( editX, editY, VERT_LINE ); break; case LEFTKEY: case RIGHTKEY: setcommentat( editX, editY, HORIZ_LINE ); break; } } if (ch==BACKSPACEKEY) { /* Erase comment to let cell show through, then move left: */ setcommentat( editX, editY, '\0' ); ch = LEFTKEY; } else if (ch==DELETEKEY) { /* Erase comment to let cell show through, then move right: */ setcommentat( editX, editY, '\0' ); ch = RIGHTKEY; } else if ( (ch!=ESCKEY) && (ch= (winy2-winy1+1)) { editY -= (winy2-winy1+1); /* Scroll down: */ showfield( &f[fsize()*showgen], cellX, (cellY-=(winy2-winy1+1)), winx1, winy1, winx2, winy2 ); } break; case PGDNKEY: /* If next page won't have to show last row of cells in memory... */ if ( cellY+2*(winy2-winy1+1)-1 < FIELDHEIGHT ) { editY += (winy2-winy1+1); /* Scroll up: */ showfield( &f[fsize()*showgen], cellX, (cellY+=(winy2-winy1+1)), winx1, winy1, winx2, winy2 ); } break; case UPKEY: /* If won't go past first row of cells in memory... */ if (editY >= 1) { editY--; /* If won't go past first editor row... */ if (crsrY-1 >= winy1) { /* Redraw cell under cursor: */ showfield( &f[fsize()*showgen], editX, editY+1, crsrX, crsrY--, crsrX, crsrY ); } else { /* Will go past first editor row, so scroll down: */ showfield( &f[fsize()*showgen], cellX, --cellY, winx1, winy1, winx2, winy2 ); /* Note: this can be made more efficient by writing a procedure to scroll the physical screen, and then calling showfield() on just the bottom row. This results in fewer calls to cellat(), which takes most of the time. */ } } break; case DOWNKEY: /* If won't go past last row of cells in memory... */ if (editY+1 < FIELDHEIGHT) { editY++; /* If won't go past last editor row... */ if (crsrY+1 <= winy2) { /* Redraw cell under cursor: */ showfield( &f[fsize()*showgen], editX, editY-1, crsrX, crsrY++, crsrX, crsrY ); } else { /* Will go past last editor row, so scroll up: */ showfield( &f[fsize()*showgen], cellX, ++cellY, winx1, winy1, winx2, winy2 ); /* Note: this can be made more efficient by writing a procedure to scroll the physical screen, and then calling showfield() on just the top row. This results in fewer calls to cellat(), which takes most of the time. */ } } break; case LEFTKEY: /* If won't go past first column of cells in memory... */ if (editX >= 1) { editX--; /* If won't go past first editor column... */ if (crsrX-1 >= winx1) { /* Redraw cell under cursor: */ showfield( &f[fsize()*showgen], editX+1, editY, crsrX--, crsrY, crsrX, crsrY ); } else { /* Will go past first editor column, so scroll right: */ showfield( &f[fsize()*showgen], --cellX, cellY, winx1, winy1, winx2, winy2 ); /* Note: this can be made more efficient by writing a procedure to scroll the physical screen, and then calling showfield() on just the left column. This results in fewer calls to cellat(), which takes most of the time. */ } } break; case RIGHTKEY: /* If won't go past last column of cells in memory... */ if (editX+1 < FIELDWIDTH) { editX++; /* If won't go past last editor column... */ if (crsrX+1 <= winx2) { /* Redraw cell under cursor: */ showfield( &f[fsize()*showgen], editX-1, editY, crsrX++, crsrY, crsrX, crsrY ); } else { /* Will go past last editor column, so scroll left: */ showfield( &f[fsize()*showgen], ++cellX, cellY, winx1, winy1, winx2, winy2 ); /* Note: this can be made more efficient by writing a procedure to scroll the physical screen, and then calling showfield() on just the right column. This results in fewer calls to cellat(), which takes most of the time. */ } } break; case SH_F1KEY: /* Change live char */ do { if (livech++ == '\377') livech = '\0'; } while ( (livech==deadch) || (livech==wallch) || (livech==borderch) ); /* Redraw field to reflect new changes: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case SH_F2KEY: /* Change dead char */ do { if (deadch++ == '\377') deadch = '\0'; } while ( (deadch==livech) || (deadch==wallch) || (deadch==borderch) ); /* Redraw field to reflect new changes: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case SH_F3KEY: /* Change wall char */ do { if (wallch++ == '\377') wallch = '\0'; } while ( (wallch==livech) || (wallch==deadch) || (wallch==borderch) ); /* Redraw field to reflect new changes: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case SH_F4KEY: /* Change border char */ do { if (borderch++ == '\377') borderch = '\0'; } while ( (borderch==livech) || (borderch==deadch) || (borderch==wallch) ); /* Redraw field to reflect new changes: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case CTRL_F1KEY: /* Change live fg */ if (livefg++ == BRIGHTWHITE) livefg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case CTRL_F2KEY: /* Change dead fg */ if (deadfg++ == BRIGHTWHITE) deadfg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case CTRL_F3KEY: /* Change wall fg */ if (wallfg++ == BRIGHTWHITE) wallfg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; /* case CTRL_F4KEY: /* Change border fg */ if (borderfg++ == BRIGHTWHITE) borderfg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; */ case CTRL_F4KEY: /* Change comment fg: */ if (commentfg++ == BRIGHTWHITE) commentfg = BLACK; if (showcomments) /* Redraw field: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case ALT_F1KEY: /* Change live bg */ if (livebg++ == BRIGHTWHITE) livebg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case ALT_F2KEY: /* Change dead bg */ if (deadbg++ == BRIGHTWHITE) deadbg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case ALT_F3KEY: /* Change wall bg */ if (wallbg++ == BRIGHTWHITE) wallbg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; /* case ALT_F4KEY: /* Change border bg */ if (borderbg++ == BRIGHTWHITE) borderbg = BLACK; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, wimy2 ); break; */ case ALT_F4KEY: /* Change comment fg */ if (commentbg++ == BRIGHTWHITE) commentbg = BLACK; if (showcomments) /* Redraw field: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case F5KEY: /* Toggle show comments: */ showcomments = (showcomments==TRUE) ? FALSE : TRUE; /* Redraw field [without comments]: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case F6KEY: /* Toggle line mode: */ linemode = (linemode==TRUE) ? FALSE : TRUE; break; case F1KEY: /* Select live */ pen = LIVE; break; case F2KEY: /* Select dead */ pen = DEAD; break; case F3KEY: /* Select wall */ pen = WALL; break; case F7KEY: /* Toggle 'pendn' */ pendn = (pendn == TRUE) ? FALSE : TRUE; break; case F8KEY: /* Clear field */ clearfield( &f[fsize()*showgen] ); /* Redraw field */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case SH_F8KEY: /* Clear comments: */ clearcomment(); /* Redraw field: */ showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); break; case F9KEY: /* Skip to previous generation [older] */ /* If not already at last [oldest] generation... */ if ( showgen != (lastgen+1==MAXGEN ? 0 : lastgen+1) ) { showgen = (showgen==0 ? MAXGEN-1 : showgen-1 ); gencount++; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); } break; case F10KEY: /* Skip to next generation [younger] */ /* If not already at most recent [youngest] generation... */ if (showgen != lastgen) { if (++showgen >= MAXGEN) showgen = 0; showfield( &f[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); gencount--; } break; default: /* Ignore illegal keys in editor */ break; } } while (ch != ESCKEY); } int loadfield( f ) field f; /* Attempts to load field from default file name. Warns if not successful [file nonexistent, or file too wide/high]. Returns TRUE if successful, else FALSE. */ { FILE *fp; cellcoord x=0, y=0; char c; boolean unknown=FALSE, toowide=FALSE, toohigh=FALSE, error=FALSE; initstatus(LOADING); writeat( statx1+17, staty1, "field" ); set_color( RED, BLUE ); if ( (fp=fopen(fieldfilename,"r")) == NULL ) { writeat( statx1+1, staty1+1, "Unable to open field file." ); error = TRUE; } else { while ( ((c=getc(fp)) != EOF) && !unknown && !toohigh ) { switch(c) { case 'a': /* Alive cell */ case 'd': /* Dead cell */ case 'w': /* Wall cell */ if (x>=FIELDWIDTH) toowide=TRUE; else if (y>=FIELDHEIGHT) toohigh=TRUE; else setcellat( f, x++,y, ( (c=='a') ? LIVE : ((c=='d') ? DEAD : WALL) ) ); break; case '\n': /* next row */ y++; x=0; break; default: /* Unknown */ unknown = TRUE; } } fclose(fp); if (toowide) { writeat( statx1+1, staty1+1, "Warning: field file is too wide; some columns were not loaded." ); } else if (toohigh) { writeat( statx1+1, staty1+1, "Warning: field file is too high; some rows were not loaded." ); } else if (unknown) { writeat( statx1+1, staty1+1, "Error: unknown character encountered in field file." ); /* } else { writeat( statx1+1, staty1+1, "LIFE.FLD successfully loaded." ); */ } } return !(error); } int loadcomments(void) /* Attempts to load comments from default file name. Warns if not successful [file nonexistent, or too wide or too high]. Returns TRUE if successful [comment file existed], else FALSE. */ { FILE *fp; cellcoord x=0, y=0; char c; boolean toowide=FALSE, toohigh=FALSE, error=FALSE; initstatus(LOADING); writeat( statx1+17, staty1, "comments" ); set_color( RED, BLUE ); if ( (fp=fopen(notesfilename,"r")) == NULL ) { writeat( statx1+1, staty1+1, "Unable to open comment file." ); error = TRUE; } else { while ( ((c=getc(fp)) != EOF) && !(toowide || toohigh) ) { if (c=='\n') { y++; x=0; } else { if (x>=FIELDWIDTH) toowide=TRUE; else if (y>=FIELDHEIGHT) toohigh=TRUE; else setcommentat(x++,y,c); } } fclose(fp); if (toowide) { writeat( statx1+1, staty1+1, "Warning: comment file is too wide; some columns were not read." ); } else if (toohigh) { writeat( statx1+1, staty1+1, "Warning: comment file is too high; some rows were not read." ); /* } else { writeat( statx1+1, staty1+1, "LIFE.CMT successfully loaded." ); */ } } return !(error); } boolean dosave( f ) field f; /* Saves field 'f' and comments if they don't already exist on disk. If either exists, user is asked if he wants to overwrite them both. If user answers NO, then returns FALSE, else saves them and returns TRUE. */ { FILE *fp; cellcoord x,y; char c; initstatus(SAVING); if ( fileexists(fieldfilename) || fileexists(notesfilename) ) { writeat( statx1+1, staty1+1, "Are you sure you want to overwrite the old field and comment files?" ); switch( lotus(statx1+1, staty1+2, BRIGHTWHITE, BLUE, BRIGHTWHITE, RED, TRUE, " NO \t YES ") ) { case 0: case -1: return FALSE; /* Do not overwrite */ default: /* Fall through */ break; } initstatus(SAVING); } writeat( statx1+16, staty1, "field" ); set_color( RED, BLUE ); if ( (fp=fopen(fieldfilename,"w")) == NULL ) { writeat( statx1+1, staty1+1, "Unable to open field file for writing." ); return FALSE; } for (y=0; y 10) FIELDWIDTH -= 9; /* Arbitrary; next line makes it to 10 */ else break; case LEFTKEY: /* Dec field width */ if (FIELDWIDTH > 1) { FIELDWIDTH--; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { FIELDWIDTH++; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { printf( "Unable to shrink field width.\nAborting...\n" ); exit(1); } } } break; case PGUPKEY: /* Dec field height */ if (FIELDHEIGHT > 10) FIELDHEIGHT -= 9; /* Arbitrary; next line makes it to 10: */ else break; case UPKEY: if (FIELDHEIGHT > 1) { FIELDHEIGHT--; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { FIELDHEIGHT++; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { printf( "Unable to shrink field height.\nAborting...\n" ); exit(1); } } } break; case PGDNKEY: FIELDHEIGHT += 9; /* Arbitrary; next line makes it to 10: */ case DOWNKEY: /* Inc field height */ FIELDHEIGHT++; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { set_color( RED, BLUE+FLASHING ); writeat( statx1+47, staty1, "Not enough memory; resizing..." ); do { FIELDHEIGHT--; } while (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )); writeat( statx1+47, staty1, " " ); } break; case '+': /* Inc MAXGEN */ MAXGEN++; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { set_color( RED, BLUE+FLASHING ); writeat( statx1+47, staty1, "Not enough memory; resizing..." ); do { MAXGEN--; } while (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )); writeat( statx1+47, staty1, " " ); } break; case '-': /* Dec MAXGEN */ if (MAXGEN > 2) { MAXGEN--; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { MAXGEN++; if (!resize( f, c, USE_WALLS, USE_COMMENTS, &FREERAM )) { printf( "Unable to shrink MAXGEN.\nAborting...\n" ); exit(1); } } } break; case 'G': /* Change graphics mode */ case 'g': if ( (gmode++) == PC3270) gmode = DETECT; break; case 'W': /* Toggle walls */ case 'w': if ( !resize(f, c, (USE_WALLS ? FALSE : TRUE), USE_COMMENTS, &FREERAM) ) { printf( "Unable to toggle USE_WALLS. Aborting...\n" ); exit(1); } break; case 'C': /* Toggle comments */ case 'c': if ( !resize(f, c, USE_WALLS, (USE_COMMENTS ? FALSE : TRUE), &FREERAM) ) { printf( "Unable to toggle USE_COMMENTS. Aborting...\n" ); exit(1); } break; default: /* Ignore other keys */ update = FALSE; break; } /* @@@ Not needed: /* Determine whether to redraw field: */ /* if ((it grew) && (last visible cell row/col WAS < last editor row/col)) || ((it shrunk) && (last visible cell row/col IS < last editor row/col)) */ if ( ((FIELDWIDTH > oldfwidth) && (oldfwidth < (winx2-winx1)+1)) || ((FIELDWIDTH < oldfwidth) && (FIELDWIDTH < (winx2-winx1)+1)) || ((FIELDHEIGHT > oldfheight) && (oldfheight < (winy2-winy1)+1)) || ((FIELDHEIGHT < oldfheight) && (FIELDHEIGHT < (winy2-winy1)+1)) ) */ if (update) showfield( &((*f)[fsize()*howmany]), cellX, cellY, winx1, winy1, winx2, winy2 ); } while (ch!=ENTERKEY && ch!=ESCKEY); } void mainmenu(void) { int ch; boolean done=FALSE; do { initstatus(MAINMENU); while ( ( ch=lotus(statx1+1, staty1+1, BRIGHTWHITE, BLUE, BRIGHTWHITE, RED, TRUE, " EDIT \t GROW \t LOAD \t SAVE \t CONFIGURE \t QUIT ") ) == -1 ); switch(ch) { /* Process user selection: */ case 0: /* EDIT */ edit( mainfield ); break; case 1: /* GROW */ dogrow(TRUE); /* Grow in hires mode */ break; case 2: /* LOAD */ if ( loadfield(&mainfield[fsize()*showgen]) && loadcomments() ) /* Update screen: */ showfield( &mainfield[fsize()*showgen], cellX, cellY, winx1, winy1, winx2, winy2 ); set_color( YELLOW, BLUE ); writeat( statx1+1, staty1+2, "Press any key to continue." ); keyhit(); break; case 3: /* SAVE */ if ( dosave( &mainfield[fsize()*showgen] ) ) { set_color( YELLOW, BLUE ); writeat( statx1+1, staty1+2, "Press any key to continue." ); keyhit(); } break; case 4: /* CONFIGURE */ configure( &mainfield, showgen, ¬es ); break; case 5: /* QUIT */ fillarea( statx1, staty1, statx2, staty2, BRIGHTWHITE, BLUE, ' ' ); set_color( BRIGHTWHITE, BLUE ); writeat( statx1+1, staty1, "Are you sure you want to quit?" ); done = (lotus(statx1+1, staty1+1, BRIGHTWHITE, BLUE, BRIGHTWHITE, RED, TRUE, " NO \t YES ")==1); break; default: /* This should never happen, but we check for it anyway: */ printf( "mainmenu(): invalid menu choice selected: %d", ch ); break; } } while (!done); /* Until QUIT selected */ clearscr( BRIGHTWHITE, BLACK ); } #define MS 90 /* How many miliseconds to delay between shades of color */ void fadeatcenter( y, f, b, s ) scrncoord y; color f, b; char s[]; { struct { char line[80]; } str[20]; int i=0,k=0,j=0; /* Indices into s, str, and str[k].line, respectively */ /* Parse 's' into local array: */ for ( i=0; s[i]!='\0'; i++ ) { if (s[i]=='\n') { /* New line */ /* Seal off this string and point to next: */ str[k++].line[j] = '\0'; j=0; /* Start at first char of new string */ /* Point to next char in 's' [ass〠not to be '\n']: */ i++; } str[k].line[j++] = s[i]; } str[k].line[j] = '\0'; /* Seal off last string */ /* 'k' now contains number of menu choices */ /* Fade: */ return; /* !!!! */ if (*CRT_MODE != 7) { /* Not a monochrome system */ set_color( BLUE, BLACK ); for (i=0; i<=k; i++) writeatcenter( y+i, str[i].line ); delay(MS); set_color( LIGHTBLUE, BLACK ); for (i=0; i<=k; i++) writeatcenter( y+i, str[i].line ); delay(MS); set_color( LIGHTCYAN, BLACK ); for (i=0; i<=k; i++) writeatcenter( y+i, str[i].line ); delay(MS); set_color( BRIGHTWHITE, BLACK ); for (i=0; i<=k; i++) writeatcenter( y+i, str[i].line ); delay(1000); } set_color( f, b ); for (i=0; i<=k; i++) { writeatcenter( y+i, str[i].line ); } } void intro(void) { clearscr( BRIGHTWHITE, BLACK ); fadeatcenter( 6, BRIGHTWHITE, BLACK, "LIFE" ); fadeatcenter( 7, LIGHTGREY, BLACK, "The computer simulation" ); fadeatcenter( 10, BLUE, BLACK, "Original idea\nby" ); fadeatcenter( 12, LIGHTBLUE, BLACK, "John Conway" ); fadeatcenter( 15, CYAN, BLACK, "IBM PC version\nby" ); fadeatcenter( 17, LIGHTCYAN, BLACK, "Kim Moser" ); fadeatcenter( 18, LIGHTGREEN, BLACK, "1/23/89 (v2.0)" ); set_color( YELLOW, BLACK ); writeatcenter( 24, "Initializing [please wait]..." ); /* /* Initialize/clear fields: */ for (i=0; i= 2. Please fix, then recompile.\n" ); exit(1); } gmode = DETECT; /* Default */ gpath = argv[1]; /* Should be '\0' if no args */ crsrX = winx1; crsrY = winy1; intro(); clearscr( BRIGHTWHITE, BROWN ); showfield( &mainfield[fsize()*lastgen], cellX,cellY, winx1, winy1, winx2, winy2 ); mainmenu(); free( mainfield ); if (USE_COMMENTS) free( notes ); }