/* 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; /************************************************************************** Screen I/O identifiers **************************************************************************/ #define SCREEN 0x0B8000000 /* Assumes color card; change to 0x0B000 if using monochrome */ #define WIDTH 80 #define HEIGHT 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 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 /* Types for screen IO: */ 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 int cellcoord; typedef unsigned short int direction; typedef unsigned short int color; /* Screen IO vars */ static scrnbyte fgd = BRIGHTWHITE; /* Default foreground color */ static scrnbyte bgd = BLACK; /* Default background color */ static scrnbyte att = BRIGHTWHITE; /* Create default attr byte */ /************************************************************************** LIFE identifiers **************************************************************************/ /* Definitions specific to LIFE: */ #define DEAD 0 #define LIVE 1 #define WALL 2 #define BORDER 3 /* Status: */ #define GROWING 0 #define EDITING 1 /* 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: */ unsigned int status; unsigned int lastgen = 0; /* Index of latest valid generation in array of fields */ unsigned int showgen = 1; /* Number shown to user [1=current, 2=previous, etc.] */ static cellcoord editX=0, editY=0; /* Cell coordinates of editor cursor */ static cell pen = LIVE; /* Pen character [for editor] */ static int pendn; /* Boolean; 0=up, 1=dn */ /* Status box coords [you may change these defaults]: */ static scrncoord statx1 = 1; static scrncoord staty1 = 23; static scrncoord statx2 = 80; static scrncoord staty2 = 25; /* 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 */ static scrncoord curx=1, cury=1; /* 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)*WIDTH*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() { fillarea( 1, 1, 80, 25, BRIGHTWHITE, BLACK, ' ' ); } 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], x, (y-=(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 ( y+2*(y2-y1+1)-1 < FIELDHEIGHT ) { editY += (y2-y1+1); /* Scroll up: */ showfield( f[generation], x, (y+=(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], x, --y, x1, y1, x2, y2 ); } } 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], x, ++y, x1, y1, x2, y2 ); } } 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], --x, y, x1, y1, x2, y2 ); } } 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], ++x, y, x1, y1, x2, y2 ); } } 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], x, y, 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], x, y, 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], x, y, 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], x, y, x1, y1, x2, y2 ); break; case SH_F1KEY: /* Change live fg */ if (livefg++ == BRIGHTWHITE) livefg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case SH_F2KEY: /* Change dead fg */ if (deadfg++ == BRIGHTWHITE) deadfg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case SH_F3KEY: /* Change wall fg */ if (wallfg++ == BRIGHTWHITE) wallfg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case SH_F4KEY: /* Change border fg */ if (borderfg++ == BRIGHTWHITE) borderfg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case CTRL_F1KEY: /* Change live bg */ if (livebg++ == BRIGHTWHITE) livebg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case CTRL_F2KEY: /* Change dead bg */ if (deadbg++ == BRIGHTWHITE) deadbg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case CTRL_F3KEY: /* Change wall bg */ if (wallbg++ == BRIGHTWHITE) wallbg = BLACK; showfield( f[generation], x, y, x1, y1, x2, y2 ); break; case CTRL_F4KEY: /* Change border bg */ if (borderbg++ == BRIGHTWHITE) borderbg = BLACK; showfield( f[generation], x, y, 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 == 1) ? 0 : 1; 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], x, y, 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], x, y, 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; { int n; /* Counts number of live neighbors */ cell c; /* Holds value of next generation [for cell (x,y)] */ cellcoord x, y, i; for (y=0; y= MAXGEN) lastgen = 0; showgen = 1; /* Assume user always sees current generation after a 'grow' */ if ( getch() == 'q' ) exit(0); } }