/* 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 [-T] [path_to_graphics_driver] 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 Note: ignore "Conversion may lose significant errors" warnings, or set Options/Compiler/Errors/Portability warnings/"Conversion may..." errors OFF so they won't be displayed. The compiler is just warning you that unsigned long int arithmetic may overflow, but that shouldn't happen. All pointers are declared as 'huge' even though some need to be only 'far'. Pointers which will not be used to point beyond their default segment, such as CRT_COL, need to be only 'far'. In fact, the only pointers which need to be 'huge' are field pointers, since a field may span a segment. All other pointers may be 'far'. Linker settings: o Graphics library: ON */ #include /* Import toupper() */ #include #include /* Import exit() */ #include #include /* Import strlen() */ #include /* Import delay() */ #include #include #define FALSE 0 #define TRUE 1 typedef unsigned char 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 huge *) KBDFLAGS) & 0x01)) || ( (*((char huge *) 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 is [0..7] will cause foreground to flash */ /* Types for screen I/O: */ typedef unsigned char scrnbyte; typedef struct scrnchar { scrnbyte ch, at; /* Character and its attribute [on screen] */ }; typedef unsigned int scrncoord; typedef unsigned short int color; typedef struct scrnchar huge *vid_addr; /* Must be FAR because video memory is in a different data segment */ struct { unsigned long int size; /* How many bytes in buffer - 1 */ vid_addr buf; /* Holds pushed screens */ } SCRNBUF; /* 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 huge *CRT_MODE = (unsigned char huge *) 0x00400049L; /* BIOS video mode number */ unsigned int huge *CRT_COL = (unsigned int huge *) 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 ALT_V_KEY (47+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; typedef unsigned long int ndx; /* Used for index into fields */ /* 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; ndx FREEMEM; /* How much free core left >before< field(s) and comments are allocated. */ ndx OVERHEAD = (ndx) 50000L; /* Arbitrary */ /* At least 'OVERHEAD' bytes must be available when resizing. You may change this default. */ unsigned int MAXGEN = 5; /* Previous generation buffer [arbitrary size, but must be >=2]: */ boolean USE_WALLS = TRUE; boolean USE_COMMENTS = TRUE; /* Cell types: */ typedef unsigned short int cell; typedef struct one { unsigned int cell0: 1; unsigned int cell1: 1; unsigned int cell2: 1; unsigned int cell3: 1; unsigned int cell4: 1; unsigned int cell5: 1; unsigned int cell6: 1; unsigned int cell7: 1; }; typedef struct two { unsigned int cell0: 2; unsigned int cell1: 2; unsigned int cell2: 2; unsigned int cell3: 2; }; typedef union cellblock { unsigned char cell0; struct one o; struct two t; }; union cellblock emptycell = {0}; /* Empty cellblock used for quick clearing of fields */ unsigned short int BITSINCELL; /* How many bits are needed to represent a cell */ unsigned short int CELLSINBLOCK; /* How many cells fit in a cellblock */ union cellblock ANDMASK; /* Mask used for extracting cells from cellblocks */ /* Typical field of cells: */ typedef union cellblock huge *field; /* mainfield -> field->cell[] | V field->cell[] | V field->cell[] | V field->cell[] ... {MAXGEN times} */ /* (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 huge *comments; /* Other vars: */ static unsigned int status; static unsigned int latestgen = 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; /* Border foreground color */ static color borderbg = BLACK; /* Border background color */ 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 = NULL; /* The main array of fields */ comments notes = NULL; /* 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': */ char *inttostr( x, w, s ) /* Assumes 1 <= w <= 9 */ 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]); */ 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)); /* @@@ 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, ' ' ); } void pushscrn(void) /* Saves text screen in buffer. Calls are NOT stacked; popscrn() must be called before next call to pushscrn() or previous screen will be lost and its memory will be 'leaked'. */ { vid_addr scrn = (vid_addr) (SCREEN); /* Points to char/attribute */ unsigned long int i; SCRNBUF.size=((((unsigned long int)(*CRT_COL)) * ((unsigned long int)MAXROW))); if ( (SCRNBUF.buf=(vid_addr) farmalloc(SCRNBUF.size*sizeof(struct scrnchar))) == NULL ) { fprintf(stderr, "pushscrn(): Unable to allocate enough memory for buffer.\n" ); exit(1); } /* Xfer buf to screen: */ for (i=0; ifar< pointer, not a huge one. */ } void popscrn(void) { vid_addr scrn = (vid_addr) (SCREEN); /* Points to char/attribute */ unsigned long int i; /* Xfer buf to screen: */ for (i=0; 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. Note: field may be no higher than MAX(int)==65535. */ { return FIELDHEIGHT; } unsigned int fwidth(void) /* Returns width [in bytes] of a field. Note: field may be no wider than MAX(int)==65535. */ { return ((FIELDWIDTH/CELLSINBLOCK)+(1 /*FIELDWIDTH%CELLSINBLOCK==0 ? 0 : 1*/)); /* Commented out clause makes sure that, if CELLSINBLOCK is not an even multiple of FIELDWIDTH, the field width (in bytes) is increased by one to make room for the column(s) of cells which extend "past" the last byte. */ } /* Macro version of fwidth(): #define fwidth() ((unsigned int) (FIELDWIDTH/CELLSINBLOCK+1 /*(FIELDWIDTH%CELLSINBLOCK==0 ? 0 : 1)*/ )) */ ndx fsize(x) /* Returns how many bytes 'x' fields would take */ unsigned int x; { return (ndx) ( ((ndx)fheight()) * ((ndx)fwidth()) * ((ndx)x) ); } ndx cmtsize(void) /* Returns how many bytes a comment 'field' would take */ { return (ndx) ((ndx)FIELDWIDTH * (ndx)FIELDHEIGHT); } field fld(n) unsigned int n; /* Returns pointer to the nth field of mainfield */ { return (mainfield+fsize(n)); } char commentat( x, y ) cellcoord x, y; { if ( USE_COMMENTS && (x=FIELDWIDTH) || (y>=FIELDHEIGHT) ) return BORDER; else { if (BITSINCELL==1) switch(x%CELLSINBLOCK) { case 0: return (c.o.cell0); case 1: return (c.o.cell1); case 2: return (c.o.cell2); case 3: return (c.o.cell3); case 4: return (c.o.cell4); case 5: return (c.o.cell5); case 6: return (c.o.cell6); case 7: return (c.o.cell7); default: fprintf(stderr, "cellat(): BITSINCELL==1 but unknown case: %u.\n", x%CELLSINBLOCK ); exit(1); } else if (BITSINCELL==2) switch(x%CELLSINBLOCK) { case 0: return (c.t.cell0); case 1: return (c.t.cell1); case 2: return (c.t.cell2); case 3: return (c.t.cell3); default: fprintf(stderr, "cellat(): BITSINCELL==1 but unknown case: %u.\n", x%CELLSINBLOCK ); exit(1); } else fprintf(stderr, "cellat(): invalid value for BITSINCELL: %u\n", BITSINCELL ); /* tmp = (BITSINCELL*(CELLSINBLOCK-(x%CELLSINBLOCK))) - BITSINCELL; return ( f[y*fwidth()+ x/CELLSINBLOCK].cell0 >> tmp ) & ANDMASK.cell0; */ /* Don't bother returning anything because if we've gotten this far, something is very wrong. */ } } /* Macro version of cellat(): #define cellat(ff,xx,yy) ( ((xx>=FIELDWIDTH)||(yy>=FIELDHEIGHT)) ? BORDER : \ ( ff[((ndx)yy)*((ndx)fwidth())+(ndx)(((ndx)xx)/((ndx)CELLSINBLOCK))].cell0 >> \ ((BITSINCELL*(CELLSINBLOCK-(xx%CELLSINBLOCK))) - BITSINCELL) ) & 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; { /* register unsigned int tmp = (BITSINCELL*(CELLSINBLOCK-(x%CELLSINBLOCK))) - BITSINCELL; */ union cellblock cb = f[(ndx)y*(ndx)fwidth() + (ndx)x/(ndx)CELLSINBLOCK]; if ( (c != DEAD) && (c != LIVE) && (c != WALL) ) fprintf(stderr, "setcellat(): internal error: invalid cell value passed: %u", c ); else if (BITSINCELL==1) switch(x%CELLSINBLOCK) { case 0: cb.o.cell0=c; break; case 1: cb.o.cell1=c; break; case 2: cb.o.cell2=c; break; case 3: cb.o.cell3=c; break; case 4: cb.o.cell4=c; break; case 5: cb.o.cell5=c; break; case 6: cb.o.cell6=c; break; case 7: cb.o.cell7=c; break; default: fprintf(stderr, "setcellat(): BITSINCELL==1 but unknown case: %u.\n", x%CELLSINBLOCK ); exit(1); } else if (BITSINCELL==2) switch(x%CELLSINBLOCK) { case 0: cb.t.cell0=c; break; case 1: cb.t.cell1=c; break; case 2: cb.t.cell2=c; break; case 3: cb.t.cell3=c; break; default: fprintf(stderr, "setcellat(): BITSINCELL==1 but unknown case: %u.\n", x%CELLSINBLOCK ); exit(1); } f[(ndx)y*(ndx)fwidth() + (ndx)x/(ndx)CELLSINBLOCK]=cb; /* f[(ndx)y*(ndx)fwidth() + (ndx)x/(ndx)CELLSINBLOCK].cell0 = (cb.cell0 & ~(ANDMASK.cell0<= (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 ( prevgen(showgen) != latestgen ) { showgen = prevgen(showgen); 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 != latestgen) { showgen = nextgen(showgen); gencount--; showfield( f+fsize(showgen), cellX, cellY, winx1, winy1, winx2, winy2 ); } break; default: /* Ignore illegal keys in editor */ break; } } while (ch != ESCKEY); } boolean 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) ) ); x++; } 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); } boolean 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; yfar< pointer, not a huge one. */ notes = NULL; } if (mainfield!=NULL) { farfree( (void far *)mainfield ); /* Cast prevents 'suspicious pointer conversion' since farfree() expects a >far< pointer, not a huge one. */ mainfield = NULL; } FREEMEM = farcoreleft(); } void resize(void) /* Attempt to (re)allocate 'mainfield' and 'notes' according to FIELDWIDTH, FIELDHEIGHT, and MAXGEN. */ { if ( (mainfield=(field) farmalloc(fsize(MAXGEN)) ) == NULL ) { fprintf(stderr, "resize(): Unable to allocate 'mainfield'.\n" ); exit(1); } if (USE_COMMENTS) if ( (notes=(comments) farmalloc(cmtsize()) ) == NULL ) { fprintf(stderr, "resize(): Unable to allocate 'notes'.\n" ); exit(1); } } boolean fits(void) /* Returns TRUE if there will be enough memory to allocate fields [all MAXGEN of them] and notes [if USE_COMMENTS == TRUE], else FALSE. */ { BITSINCELL = (USE_WALLS ? 2 : 1); CELLSINBLOCK = (USE_WALLS ? 4 : 8); ANDMASK.cell0 = (USE_WALLS ? 0x03 : 0x01); /* $00000011 or $00000001, when ANDed (bitwise) with a cellblock, will yield the cell value of the rightmost cell in that block, assuming a cell is represented in either 2 or 1 bit(s), respectively. */ return ( fsize(MAXGEN) + (ndx) (USE_COMMENTS ? cmtsize() : 0) < FREEMEM - OVERHEAD ); } void configure(void) /* Configures all fields pointed to by 'mainfield' [all 'MAXGEN' of them], as well as comments pointed to by 'notes' */ { char s[15]; unsigned int ch; unsigned long int left; /* Max number of [potential] fields left */ boolean oldUW=USE_WALLS, oldUC=USE_COMMENTS; boolean oldFH=FIELDHEIGHT, oldFW=FIELDWIDTH; boolean oldMG=MAXGEN; unsigned int i; initstatus( CONFIGURING ); editX = 0; editY = 0; crsrX = winx1; crsrY = winy1; /* Redraw from top-left: */ showfield( fld(showgen), (cellX=0), (cellY=0), winx1, winy1, winx2, winy2 ); do { set_color( RED, BLUE ); writeat( statx1+22, staty1+1, inttostr((long int)FIELDHEIGHT,5,s) ); writeat( statx1+44, staty1+1, inttostr((long int)FIELDWIDTH,5,s) ); writeat( statx1+70, staty1+1, inttostr((long int)MAXGEN,5,s) ); left = FREEMEM - OVERHEAD - (ndx) (USE_COMMENTS ? cmtsize() : 0); left /= fsize(1); /* 'left' == how many possible fields */ left -= MAXGEN; /* 'left' == how many MORE fields may be added */ writeat( statx1+66, staty1+2, inttostr(left,7,s) ); writeat( statx1+41, staty1+2, (USE_WALLS ? "YES" : "NO ") ); writeat( statx1+58, staty1+2, (USE_COMMENTS ? "YES" : "NO ") ); switch(gmode) { case DETECT: strcpy( s, "DETECT " ); break; case CGA: strcpy( s, "CGA " ); break; case MCGA: strcpy( s, "MCGA " ); break; case EGA: strcpy( s, "EGA " ); break; case EGA64: strcpy( s, "EGA64 " ); break; case EGAMONO: strcpy( s, "EGAMONO " ); break; case IBM8514: strcpy( s, "IBM8514 " ); break; case HERCMONO: strcpy( s, "HERCMONO " ); break; case ATT400: strcpy( s, "ATT400 " ); break; case VGA: strcpy( s, "VGA " ); break; case PC3270: strcpy( s, "PC3270 " ); break; default: strcpy( s, "[UNKNOWN]" ); break; } writeat( statx1+16, staty1+2, s ); switch((ch=keyhit())) { case RIGHTKEY: /* Inc field width */ FIELDWIDTH += ( shift() ? 10 : 1 ); if (!fits()) { set_color( RED, BLUE+FLASHING ); writeat( statx1+47, staty1, "Not enough memory; resizing..." ); do { FIELDWIDTH--; } while (!fits()); writeat( statx1+47, staty1, " " ); } break; case LEFTKEY: /* Dec field width */ if (shift()) /* Dec by 10, if at least 10 wide already */ if (FIELDWIDTH > 10) FIELDWIDTH -= 9; else break; if (FIELDWIDTH > 1) { FIELDWIDTH--; } break; case UPKEY: if (shift()) /* Dec by 10, if at least 11 high already: */ if (FIELDHEIGHT > 10) FIELDHEIGHT -= 9; else break; if (FIELDHEIGHT > 1) { FIELDHEIGHT--; } break; case DOWNKEY: /* Inc field height */ if (shift()) /* Inc by 10 */ FIELDHEIGHT += 9; /* Arbitrary; next line makes it to 10: */ FIELDHEIGHT++; if ( !fits() ) { set_color( RED, BLUE+FLASHING ); writeat( statx1+47, staty1, "Not enough memory; resizing..." ); do { FIELDHEIGHT--; } while ( !fits() ); writeat( statx1+47, staty1, " " ); } break; case '+': /* Inc MAXGEN */ MAXGEN += (shift() ? 10 : 1); if ( !fits() ) { set_color( RED, BLUE+FLASHING ); writeat( statx1+47, staty1, "Not enough memory; resizing..." ); do { MAXGEN--; } while ( !fits() ); writeat( statx1+47, staty1, " " ); } break; case '-': /* Dec MAXGEN */ if (shift()) if (MAXGEN > 11) MAXGEN -= 10; else break; else if (MAXGEN > 2) MAXGEN--; else break; break; case 'G': /* Change graphics mode */ case 'g': if ( (gmode++) == PC3270) gmode = DETECT; break; case 'W': /* Toggle walls */ case 'w': USE_WALLS = USE_WALLS ? FALSE : TRUE; setstuff(); if (!fits()) { USE_WALLS = USE_WALLS ? FALSE : TRUE; setstuff(); } break; case 'C': /* Toggle comments */ case 'c': USE_COMMENTS = USE_COMMENTS ? FALSE : TRUE; if (!fits()) USE_COMMENTS = USE_COMMENTS ? FALSE : TRUE; break; case ENTERKEY: case ESCKEY: break; default: /* Ignore other keys */ break; } } while (ch!=ENTERKEY && ch!=ESCKEY); if ( (oldUW!=USE_WALLS) || (oldUC!=USE_COMMENTS) || (oldFH!=FIELDHEIGHT) || (oldFW!=FIELDWIDTH) || (oldMG!=MAXGEN) ) { set_color( RED, BLUE+FLASHING ); writeat( statx1+48, staty1, "Clearing fields; please wait..." ); showgen = latestgen = gencount = 0; purge(); resize(); clearfield( fld(0) ); clearcomment(); showfield( fld(0), cellX, cellY, winx1, winy1, winx2, winy2 ); for (i=1; i= 2. Please fix, then recompile.\n" ); exit(1); } setstuff(); purge(); /* The only purpose is to set FREEMEM */ if (!fits()) { /* Set up BITSINCELL, CELLSINBLOCK, and ANDMASK */ fprintf(stderr, "Unable to allocate enough memory.\n" ); exit(1); } resize(); if ( (sizeof(union cellblock)*8) != (BITSINCELL*CELLSINBLOCK) ) { fprintf(stderr, "Size of cellblock is not %u. Please fix, then recompile.\n", BITSINCELL*CELLSINBLOCK ); fprintf(stderr, "Remember, alignment must be set to BYTE, not WORD.\n" ); exit(1); } gmode = DETECT; /* Default */ gpath = nopath; /* Default */ if (argc>3) usage(); /* Parse command line params: */ for (i=1; i