/* Program: LIFE Author : Kim Moser Date : 08/88 Descrip: IBM PC implementation of John Conway's LIFE simulation. Requires IBM PC or compatible with color card. */ #include #define FALSE 0 #define TRUE 1 typedef unsigned short int boolean; #define CAPADDR 0x000000417 /* Memory address of CAPLOCK key status [bit 6; & with 40h for status] */ caplock() /* Returns state of CAPLOCK key: 0 if up, 0x40 if down */ { char far *k; return ( *((char far *) CAPADDR) & 0x040 ); } /************************************************************************** Screen I/O identifiers **************************************************************************/ #define SCREEN 0x0B8000000 /* Assumes color card; change to 0x0B000 if using monochrome */ #define MAXCOL 80 #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 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 /* If FLASHING is added to a background color that is 0..7, the foreground will flash */ #define UPKEY 72 #define DOWNKEY 80 #define LEFTKEY 75 #define RIGHTKEY 77 #define ESCKEY 27 #define HOMEKEY 71 #define ENDKEY 79 #define PGUPKEY 73 #define PGDNKEY 81 #define F1KEY 59 #define F2KEY 60 #define F3KEY 61 #define F4KEY 62 #define SH_F1KEY 84 #define SH_F2KEY 85 #define SH_F3KEY 86 #define SH_F4KEY 87 #define CTRL_F1KEY 94 #define CTRL_F2KEY 95 #define CTRL_F3KEY 96 #define CTRL_F4KEY 97 #define ENTERKEY 13 /* Types for screen I/O: */ typedef 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; /* Screen I/O vars */ static scrnbyte fgd = BRIGHTWHITE; /* Default foreground color */ static scrnbyte bgd = BLACK; /* Default background color */ static scrnbyte att = BRIGHTWHITE; /* Create default attr byte */ static scrncoord curx=1, cury=1; /* Cursor coordinates for write() procedures */ /************************************************************************** LIFE identifiers **************************************************************************/ typedef int cellcoord; /* Definitions specific to LIFE: */ #define DEAD 0 #define LIVE 1 #define WALL 2 #define BORDER 3 /* Status: */ #define MAINMENU 0 #define GROWING 1 #define EDITING 2 /* You may change these defaults: */ #define FIELDWIDTH 100 #define FIELDHEIGHT 90 #define MAXGEN 3 /* Cell types: */ typedef unsigned short int cell; typedef struct cellblock { cell cell0; }; typedef struct cellblock field [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 = 1; /* Number shown to user [1=current, 2=previous, etc.] */ static scrncoord winx1=1, winy1=5, winx2=70, winy2=15; /* 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, crsrY; /* 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 = 23; static scrncoord statx2 = MAXCOL; static scrncoord staty2 = MAXROW; /* You may change these defaults: */ static char livech = '*'; /* 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 = '.'; /* 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 = 'W'; /* Screen character to represent walls */ static color wallfg = GREEN; /* Wall foreground color */ static scrnbyte wallbg = BLUE; /* Wall background color */ static char borderch = 'B'; /* Screen character to represent out-of-field */ static color borderfg = RED; static color borderbg = YELLOW; static char crsrch = 'X'; /* Default screen character to represent cursor */ static color crsrfg = BRIGHTWHITE; /* Cursor foreground color */ static color crsrbg = BLUE; /* Cursor background color */ /* Functions for this 'module': */ goxy( x, y ) scrncoord x, y; /* Sets imaginary cursor coords to (x,y) */ { extern scrncoord curx, cury; curx = x; cury = y; } setcolor( 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)*MAXCOL*sizeof(struct scrnchar) + (x-1)*sizeof(struct scrnchar) ); } writeat( x, y, s ) /* Write string 's' at screen location 'x','y' (1,1 = top left) */ scrncoord x,y; char s[]; { int i; /* Index into 's' */ struct scrnchar far *scrn; /* Points to char/attribute */ scrn = (struct scrnchar far *) (SCREEN + xytoadr(x,y)); /* Must be FAR because screen memory is in a different data segment */ /* @@@ scrn = VAL( scrnchar, SCREEN + xytoadr(x,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 */ } } 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 ); /* struct scrnchar far *scrn; /* Points to char/attribute */ scrn = (struct scrnchar far *) (SCREEN + xytoadr(x,y)); /* Must be FAR because screen memory is in a different data segment */ /* @@@ scrn = VAL( scrnchar, SCREEN + xytoadr(x,y) ); */ scrn->ch = c; /* Write character on screen */ (scrn)->at = att; /* Write attribute */ */ } writestr( s ) /* Writes string 's' at (curx,cury) */ char s[]; { extern scrncoord curx, cury; writeat( curx, cury, s ); } writech( c ) /* Writes char 'c' at (curx,cury) */ unsigned char c; { extern scrncoord curx, cury; writeatch( curx, cury, c ); } 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 */ setcolor( 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 ); } } } clearscr() { extern scrnbyte fgd, bgd; fillarea( 1, 1, MAXCOL, MAXROW, fgd, bgd, ' ' ); } char *inttostr( x, s ) /* 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. */ int x; char s[]; { 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]); } /*************************************************************************/ cell cellat( f, x, y ) /* Depending on value of cell at (x,y) [offset from (0,0)], returns either LIVE if alive, DEAD if dead, WALL if wall, or BORDER if out of range. */ field f; cellcoord x, y; { struct cellblock cb; if ( (x>=FIELDWIDTH) || (x<0) || (y>=FIELDHEIGHT) || (y<0) ) return BORDER; else { cb = f[y][x]; /* 'cb' now contains CellBlock which contains cell we want */ return cb.cell0; } /* i = (unsigned short int) c; 'i' now contains CellBlock containing cell we want -- but with the advantage that it can be manipulated as an "unsigned short int" Right-justify bit-field representing cell we want: (i >>= (BITSINBLOCK-(((x%CELLSINBLOCK)+1)*BITSINCELL))); Isolate bit-field representing cell we want: i &= ANDMASK; If this value is neither LIVE, nor DEAD, nor WALL, then something is very wrong. */ } setcellat( f, x, y, c ) /* Sets cell in field 'f' at coordinates (x,y) [offset from (0,0)] to 'ce' [assumed to be LIVE, DEAD, or WALL] */ field f; cellcoord x, y; cell c; { if ( (c != DEAD) && (c != LIVE) && (c != WALL) ) { printf( "setcellat(): internal error: invalid cell value passed: %u", c ); } f[y][x].cell0 = c; } clearfield( f ) /* Clears field 'f' [i.e. sets all cells to DEAD] */ field f; { cellcoord x, y; /* Indexes into 'f' */ for (y=0; y= 0) { editY -= (y2-y1+1); /* Scroll down: */ showfield( f[generation], cellX, (cellY-=(y2-y1+1)), x1, y1, x2, y2 ); } break; case PGDNKEY: /* If next page won't have to show last row of cells in memory... */ if ( cellY+2*(y2-y1+1)-1 < FIELDHEIGHT ) { editY += (y2-y1+1); /* Scroll up: */ showfield( f[generation], cellX, (cellY+=(y2-y1+1)), x1, y1, x2, y2 ); } break; case UPKEY: /* If won't go past first row of cells in memory... */ if (editY-1 >= 0) { editY--; /* If won't go past first editor row... */ if (crsrY-1 >= y1) { /* Redraw cell under cursor: */ showfield( f[generation], editX, editY+1, crsrX, crsrY--, crsrX, crsrY ); } else { /* Will go past first editor row, so scroll down: */ showfield( f[generation], cellX, --cellY, x1, y1, x2, y2 ); /* 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 <= y2) { /* Redraw cell under cursor: */ showfield( f[generation], editX, editY-1, crsrX, crsrY++, crsrX, crsrY ); } else { /* Will go past last editor row, so scroll up: */ showfield( f[generation], cellX, ++cellY, x1, y1, x2, y2 ); /* 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 >= 0) { editX--; /* If won't go past first editor column... */ if (crsrX-1 >= x1) { /* Redraw cell under cursor: */ showfield( f[generation], editX+1, editY, crsrX--, crsrY, crsrX, crsrY ); } else { /* Will go past first editor column, so scroll right: */ showfield( f[generation], --cellX, cellY, x1, y1, x2, y2 ); /* 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 <= x2) { /* Redraw cell under cursor: */ showfield( f[generation], editX-1, editY, crsrX++, crsrY, crsrX, crsrY ); } else { /* Will go past last editor column, so scroll left: */ showfield( f[generation], ++cellX, cellY, x1, y1, x2, y2 ); /* 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 F1KEY: /* Change live char */ do { if (livech++ == '\255') livech = '\0'; } while ( (livech==deadch) || (livech==wallch) || (livech==borderch) ); /* Redraw field to reflect new changes: */ showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case F2KEY: /* Change dead char */ do { if (deadch++ == '\255') deadch = '\0'; } while ( (deadch==livech) || (deadch==wallch) || (deadch==borderch) ); /* Redraw field to reflect new changes: */ showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case F3KEY: /* Change wall char */ do { if (wallch++ == '\255') wallch = '\0'; } while ( (wallch==livech) || (wallch==deadch) || (wallch==borderch) ); /* Redraw field to reflect new changes: */ showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case F4KEY: /* Change border char */ do { if (borderch++ == '\255') borderch = '\0'; } while ( (borderch==livech) || (borderch==deadch) || (borderch==wallch) ); /* Redraw field to reflect new changes: */ showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case SH_F1KEY: /* Change live fg */ if (livefg++ == BRIGHTWHITE) livefg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case SH_F2KEY: /* Change dead fg */ if (deadfg++ == BRIGHTWHITE) deadfg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case SH_F3KEY: /* Change wall fg */ if (wallfg++ == BRIGHTWHITE) wallfg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case SH_F4KEY: /* Change border fg */ if (borderfg++ == BRIGHTWHITE) borderfg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case CTRL_F1KEY: /* Change live bg */ if (livebg++ == BRIGHTWHITE) livebg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case CTRL_F2KEY: /* Change dead bg */ if (deadbg++ == BRIGHTWHITE) deadbg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case CTRL_F3KEY: /* Change wall bg */ if (wallbg++ == BRIGHTWHITE) wallbg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case CTRL_F4KEY: /* Change border bg */ if (borderbg++ == BRIGHTWHITE) borderbg = BLACK; showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; default: /* Ignore illegal extended keys */ break; } break; case 'A': case 'a': pen = LIVE; break; case 'D': case 'd': pen = DEAD; break; case 'W': case 'w': pen = WALL; break; case ' ': /* Toggle 'pendn' */ pendn = (pendn == TRUE) ? FALSE : TRUE; break; case 'C': case 'c': /* Clear field */ clearfield( f[generation] ); /* Redraw field */ showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case '+': /* Skip to next generation [younger] */ /* If not already at most recent [youngest] generation... */ if (generation != lastgen) { showgen--; if (++generation >= MAXGEN) generation = 0; } showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; case '-': /* Skip to previous generation [older] */ /* If not already at last [oldest] generation... */ if ( generation != (lastgen+1 < MAXGEN ? lastgen+1 : 0) ) { showgen++; /* Make sure it wraps past zero... */ generation = (generation == 0) ? MAXGEN-1 : generation-1; } showfield( f[generation], cellX, cellY, x1, y1, x2, y2 ); break; default: /* Ignore other keys */ break; } } while (ch != ESCKEY); /* Latest field is that in which user was when they exited: */ lastgen = generation; } grow( f1, f2 ) /* 'Grows' field1 to field2 */ field f1, f2; { register int n; /* Counts number of live neighbors */ register cell c1, c2; /* Holds cell at f1[y][x] and f2[y][x] */ register cellcoord x, y, i; for (y=0; y MAXMAIN) where = 0; break; default: break; /* Ignore invalid extended keys */ } break; case ENTERKEY: break; default: break; /* Ignore invalid keys */ } } while ( ch != ENTERKEY ); /* Process user selection: */ switch( where ) { case M_EDIT: /* EDIT */ edit( mainfield, 1, 5, 70, 15 ); break; case M_GROW: initstatus( GROWING ); do { grow( mainfield[lastgen], mainfield[ (lastgen+1 < MAXGEN) ? lastgen+1 : 0 ] ); /* Update generation pointers/counters/whatever: */ if (++lastgen >= MAXGEN) lastgen = 0; showgen = 1; /* Assume user always sees current generation after a 'grow' */ /* Update screen: */ showfield( mainfield[lastgen], cellX,cellY, winx1, winy1, winx2, winy2 ); } while ( !caplock() ); break; case M_LOAD: printf( "LOAD not yet available.\n" ); break; case M_SAVE: printf( "SAVE not yet available.\n" ); break; case M_QUIT: break; /* Do nothing */ default: printf( "mainmenu(): invalid menu choice selected: %d", where ); break; } } while ( where != M_QUIT ); /* Until QUIT selected */ } initstatus( s ) /* Assigns 's' to 'status' and writes initial status line according to status */ unsigned int s; { extern unsigned int status; extern scrncoord statx1, staty1, statx2, staty2; status = s; fillarea( statx1, staty1, statx2, staty2, BRIGHTWHITE, BLUE, ' ' ); setcolor( BRIGHTWHITE, BLUE ); writeat( statx1+1, staty1, "Status:" ); setcolor( YELLOW, BLUE ); switch (status) { case MAINMENU: writeat( statx1+9, staty1, "MAIN MENU" ); setcolor( BRIGHTWHITE, BLUE ); writeat( statx1+20, staty1, "Move: Select:" ); setcolor( YELLOW, BLUE ); writeat( statx1+26, staty1, "Left & Right arrow keys" ); writeat( statx1+59, staty1, "ENTER" ); break; case EDITING: writeat( statx1+9, staty1, "EDITING" ); setcolor( BRIGHTWHITE, BLUE ); writeat( statx1+18, staty1, "Generation: (Next: Prev: ) Cell: ," ); writeat( statx1+1, staty1+1, "Alive: Dead: Wall: Clear: Move:" ); writeat( statx1+1, staty1+2, "Char: Fgnd: Bgnd: Done: Pen:" ); /* Write fields that won't change: */ setcolor( YELLOW, BLUE ); writeat( statx1+45, staty1, "+" ); writeat( statx1+54, staty1, "-" ); writeat( statx1+39, staty1+1, "C" ); writeat( statx1+50, staty1+1, "Arrows PgUp PgDn Home End" ); writeat( statx1+7, staty1+2, "F1..F4" ); writeat( statx1+21, staty1+2, "SHIFT-F1..F4" ); writeat( statx1+41, staty1+2, "CTRL-F1..F4" ); writeat( statx1+60, staty1+2, "ESC" ); break; case GROWING: writeat( statx1+9, staty1, "GROWING" ); setcolor( BRIGHTWHITE, BLUE ); writeat( statx1+1, staty1+1, "Press to return to menu." ); setcolor( YELLOW, BLUE ); writeat( statx1+7, staty1+1, "CAPS LOCK" ); break; default: printf( "initstatus(): invalid 'status': %d", status ); } } updatestatus() /* Updates EDITOR status line */ { extern scrncoord statx1, staty1, statx2, staty2; extern cell pen; extern boolean pendn; extern unsigned int showgen; char s[255]; /* Temporary string */ setcolor( RED, BLUE ); writeat( statx1+64, staty1, inttostr( editX, s ) ); writeat( statx1+70, staty1, inttostr( editY, s ) ); writeat( statx1+70, staty1+2, (pendn ? "Down" : "Up ") ); setcolor( livefg, livebg ); writeatch( statx1+8, staty1+1, livech ); setcolor( YELLOW, BLUE ); writeat( statx1+9, staty1+1, (pen==LIVE ? "<" : " ") ); setcolor( deadfg, deadbg ); writeatch( statx1+18, staty1+1, deadch ); setcolor( YELLOW, BLUE ); writeat( statx1+19, staty1+1, (pen==DEAD ? "<" : " ") ); setcolor( wallfg, wallbg ); writeatch( statx1+28, staty1+1, wallch ); setcolor( YELLOW, BLUE ); writeat( statx1+29, staty1+1, (pen==WALL ? "<" : " ") ); /* User should never see border chars: setcolor( borderfg, borderbg ); writeatch( statx1+40, staty1+1, borderch ); */ setcolor( RED, BLUE ); writeat( statx1+31, staty1, inttostr(showgen), s ); } field mainfield[MAXGEN]; unsigned int i; main() { extern unsigned int lastgen, showgen; extern scrncoord crsrX, crsrY; extern scrncoord winx1, winy1, winx2, winy2; /***************************************************************** INIT: *****************************************************************/ clearscr(); /* Initialize/clear fields: */ for (i=0; i