589 lines
14 KiB
C
589 lines
14 KiB
C
/* life.c
|
|
*
|
|
* Game of LIFE demonstration program.
|
|
*
|
|
* This program runs a version of the game LIFE that requires little
|
|
* interaction. Yet user is able to stop the game if desired, and edit
|
|
* the cells on the game grid.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <dos.h>
|
|
|
|
#define ROWS 21 /* Dimensions of population matrix */
|
|
#define COLS 78
|
|
#define SCRNSIZE ((ROWS+2)*(COLS+2)*2)
|
|
|
|
char mat1[ROWS][COLS]; /* Matrix 1: stores current population */
|
|
char mat2[ROWS][COLS]; /* Matrix 2: stores crowding-numbers */
|
|
int speed; /* Timing factor (1-10) */
|
|
int cell; /* Cell character */
|
|
int attrib; /* Video attribute of each location */
|
|
char far *videomem; /* Address of video memory
|
|
monochrome = b0000, cga = b8000 */
|
|
int adapter; /* Graphic adapter? cga, ega, mono */
|
|
int more_rounds = 0; /* Boolean: Continue LIFE? */
|
|
int forever; /* Boolean: unlimited no. of generations? */
|
|
int pause; /* Boolean: pause execution? */
|
|
long repeat; /* Maximum number of generations to do */
|
|
float density; /* Density of random distribution (0.0-100.0) */
|
|
char scrnbuf[SCRNSIZE]; /* Screen Buffer area */
|
|
|
|
#define BUFLIMIT (scrnbuf + SCRNSIZE)
|
|
|
|
#define MONO 1 /* Constant values for adapter */
|
|
#define CGA 2 /* variable */
|
|
#define EGA 3
|
|
|
|
/* Main function.
|
|
*
|
|
* Determine if command-line argument (an integer) is present.
|
|
* If so, run the number of generations given on the command line.
|
|
* Otherwise, run generations indefinitely.
|
|
*/
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
long atol(); /* Alpha string to long int conversion */
|
|
|
|
if (argc == 1) /* No command-line argument */
|
|
forever = 1; /* So run forever */
|
|
else
|
|
repeat = atol(argv[1]); /* Else, run given number */
|
|
init_life();
|
|
do {
|
|
more_rounds = 0;
|
|
init_buf();
|
|
draw_box(attrib);
|
|
init_mats();
|
|
rand_dist(density/100.0);
|
|
pause = 0;
|
|
while (forever || repeat--) {
|
|
proc_key();
|
|
if (!pause) {
|
|
if (speed < 10)
|
|
timer();
|
|
generation();
|
|
} else
|
|
if (pause_screen())
|
|
break;
|
|
}
|
|
} while(more_rounds);
|
|
set_cursor(24,0);
|
|
}
|
|
|
|
|
|
/* Initialize LIFE.
|
|
*
|
|
* Initialize parameters (static variables) used in the game, including
|
|
* background, cell type, population density, and location of video memory.
|
|
*/
|
|
init_life()
|
|
{
|
|
init_adapter();
|
|
switch (adapter) {
|
|
case MONO: videomem = (char far *) 0xb0000000;
|
|
speed = 5;
|
|
break;
|
|
case CGA: videomem = (char far *) 0xb8000000;
|
|
speed = 10;
|
|
break;
|
|
case EGA: videomem = (char far *) 0xb8000000;
|
|
speed = 5;
|
|
break;
|
|
}
|
|
cell = 254;
|
|
attrib = 11;
|
|
density = 40.0;
|
|
}
|
|
|
|
/* Initialize buffer.
|
|
*
|
|
* Initialize the screen buffer array with spaces and the attribute
|
|
* inidicated in the variable attrib.
|
|
*/
|
|
init_buf()
|
|
{
|
|
register char *p = scrnbuf;
|
|
|
|
while (p < BUFLIMIT) {
|
|
*p++ = ' ';
|
|
*p++ = attrib;
|
|
}
|
|
}
|
|
|
|
|
|
/* Draw box.
|
|
*
|
|
* Clear screen, and then use extended-ascii line characters to
|
|
* draw the frame (box) for the game of life.
|
|
*/
|
|
draw_box(attrib_val)
|
|
int attrib_val; /* Foreground/background attribute of box */
|
|
{
|
|
char *p; /* pointer into screen buffer */
|
|
int i, incr;
|
|
|
|
set_cursor(26,0);
|
|
p = scrnbuf;
|
|
|
|
/* Draw top of box. */
|
|
|
|
*p = 218;
|
|
p += 2;
|
|
for (i = 0; i < COLS; p += 2, i++)
|
|
*p = 196;
|
|
*p = 191;
|
|
p += 2;
|
|
|
|
/* Draw side of box. */
|
|
|
|
for (i = 0; i < ROWS; p += 160, i++) {
|
|
incr = (COLS + 1) * 2;
|
|
*p = 179;
|
|
*(p + incr) = 179;
|
|
}
|
|
|
|
/* Draw bottom of box. */
|
|
|
|
*p = 192;
|
|
p += 2;
|
|
for (i = 0; i < COLS; p += 2, i++)
|
|
*p = 196;
|
|
*p = 217;
|
|
p += 2;
|
|
|
|
refresh(scrnbuf, videomem); /* Copy scrnbuf to video memory */
|
|
draw_prompt_box();
|
|
}
|
|
|
|
/* Draw Prompt Box.
|
|
*
|
|
* Put main prompt sequence at bottom of the screen.
|
|
*/
|
|
draw_prompt_box()
|
|
{
|
|
set_cursor(23,0);
|
|
printf( "Press key to control execution: ");
|
|
printf( "F=faster S=slower P=pause(restart, quit) ");
|
|
set_cursor(26,0);
|
|
}
|
|
|
|
/* Initialize Matrixes.
|
|
*
|
|
* Clear Matrix 1 and Matrix 2, then initialize all the zones
|
|
* (1-9) of Matrix 1.
|
|
*
|
|
* The "zones" are used by the LIFE algorithm to determine method of
|
|
* calculating neighbors. Zones are pertinent to edges and corners:
|
|
*
|
|
* +-+--------------+-+
|
|
* |6| 2 |7|
|
|
* +-+--------------+-+
|
|
* | | | |
|
|
* |4| 1 |5|
|
|
* | | | |
|
|
* +-+--------------+-+
|
|
* |8| 3 |9|
|
|
* +-+--------------+-+
|
|
*
|
|
* Zones are recorded in Matrix 1 for ease of computation. If a cell
|
|
* lives, then we add 100 to mark cell's existence.
|
|
*/
|
|
init_mats()
|
|
{
|
|
int i, j; /* Loop counters. */
|
|
char *p = (char *) mat1; /* Pointer into Matrix 1. */
|
|
|
|
/* Initialize zones in Matrix 1 */
|
|
|
|
clear_mat(mat1, ROWS * COLS); /* Initialize matrix to all 0's */
|
|
clear_mat(mat2, ROWS * COLS);
|
|
|
|
*p++ = 6; /* Initilialize row 1 */
|
|
for (i = 0; i < COLS-2; i++)
|
|
*p++ = 2;
|
|
*p++ = 7;
|
|
|
|
for (j = 0; j < ROWS-2; j++) { /* Initialize center rows */
|
|
*p++ = 4;
|
|
for (i = 0; i < COLS-2; i++)
|
|
*p++ = 1;
|
|
*p++ = 5;
|
|
}
|
|
|
|
*p++ = 8; /* Initialize bottom row */
|
|
for (i = 0; i < COLS-2; i++)
|
|
*p++ = 3;
|
|
*p++ = 9;
|
|
|
|
}
|
|
|
|
|
|
/* Random Distribution.
|
|
*
|
|
* Put a random distribution into Matrix 1.
|
|
* Add 100 to each cell that is alive, according to the distribution.
|
|
*/
|
|
rand_dist(chance)
|
|
float chance;
|
|
{
|
|
char *p;
|
|
char *bp = (char *) scrnbuf;
|
|
int rnd; /* output from rand() */
|
|
int amt; /* Amount to exceed for a cell to live */
|
|
int i, j; /* Loop counters */
|
|
long time(); /* Grab time for seed of random sequence */
|
|
|
|
p = (char *) mat1;
|
|
amt = chance * 32768;
|
|
srand((unsigned) time(0));
|
|
bp += 162;
|
|
for (i = 0; i < ROWS; i++, bp += 4)
|
|
for (j = 0; j < COLS; j++, p++, bp += 2) {
|
|
rnd = rand();
|
|
if (rnd < amt) {
|
|
*p += 100;
|
|
*bp = cell;
|
|
*(bp+1) = attrib;
|
|
}
|
|
}
|
|
|
|
refresh(scrnbuf, videomem); /* Put results to the screen */
|
|
}
|
|
|
|
/* Timing mechanism.
|
|
*
|
|
* Use the variable "speed" to determine how long to delay before
|
|
* the next generation of life.
|
|
*/
|
|
timer()
|
|
{
|
|
long period; /* Count to this number */
|
|
long i; /* Loop variable */
|
|
long j; /* Dummy variable, to slow down loop */
|
|
|
|
period = (10 - speed);
|
|
period *= period;
|
|
period *= 700;
|
|
for (i = 0; i < period; i++)
|
|
j = i * 2;
|
|
}
|
|
|
|
|
|
#define NW (-1-COLS) /* Directional constants, within */
|
|
#define N (-COLS) /* Matrix 2. For example, NW refers */
|
|
#define NE (1-COLS) /* to the upper, left-hand neighbor */
|
|
#define E (1)
|
|
#define SE (1+COLS)
|
|
#define S (COLS)
|
|
#define SW (-1+COLS)
|
|
#define W (-1)
|
|
#define LIMIT ((char *)mat1+ROWS*COLS)
|
|
|
|
/* Do one generation of life.
|
|
*
|
|
* The algorithm used here first clears all of Matrix 2, calling
|
|
* an assembly routine for maximum speed. Then, Matrix 1 is scanned.
|
|
* Wherever a living cell is found, the CORRESPONDING NEIGHBOR CELLS
|
|
* IN MATRIX 2 are incremented by 1, and the corresponding cell itself
|
|
* is incremented by 100. If no living cell is found, do nothing.
|
|
* This gives us a fast method for determining neighbor count, which is
|
|
* kept track of in Matrix 2.
|
|
*
|
|
* The "zone" of each cell is checked, and used as a guide for determining
|
|
* neighbors. Nothern neighbors of northernmost row are found in the
|
|
* southernmost row, so that the game has a "boundless" effect...formations
|
|
* that move off one side automatically circle around to the other side.
|
|
*
|
|
* Pass 2 is called to determine what actually lives or dies, based on
|
|
* the neighbor-count of each cell.
|
|
*/
|
|
generation()
|
|
{
|
|
register char *p1; /* Pointers into mat1, mat2 */
|
|
register char *p2;
|
|
int diff; /* No. of bytes between mat1 & mat2 */
|
|
int zone; /* Which "zone" does cell lie in? */
|
|
|
|
clear_mat(mat2, ROWS * COLS);
|
|
diff = (char *) mat2 - (char *) mat1;
|
|
for (p1 = (char *) mat1; p1 < LIMIT; p1++) {
|
|
if (*p1 > 100) { /* Is cell alive? */
|
|
p2 = p1 + diff; /* P2 is corresponding cell */
|
|
*p2 += 100;
|
|
zone = (*p1 - 100); /* Zone is residue of 100 */
|
|
switch (zone) { /* Take action based on zone */
|
|
case 1: ++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 2: ++*(p2 + (NW + ROWS * COLS));
|
|
++*(p2 + (N + ROWS * COLS));
|
|
++*(p2 + (NE + ROWS * COLS));
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 3: ++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + (SE - ROWS * COLS));
|
|
++*(p2 + (S - ROWS * COLS));
|
|
++*(p2 + (SW - ROWS * COLS));
|
|
++*(p2 + W);
|
|
break;
|
|
case 4: ++*(p2 + (NW + COLS));
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + (SW + COLS));
|
|
++*(p2 + (W + COLS));
|
|
break;
|
|
case 5: ++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + (NE - COLS));
|
|
++*(p2 + (E - COLS));
|
|
++*(p2 + (SE - COLS));
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 6: ++*(p2 + NW + ROWS * COLS + COLS);
|
|
++*(p2 + N + ROWS * COLS);
|
|
++*(p2 + NE + ROWS * COLS);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW + COLS);
|
|
++*(p2 + W + COLS);
|
|
break;
|
|
case 7: ++*(p2 + NW + ROWS * COLS);
|
|
++*(p2 + N + ROWS * COLS);
|
|
++*(p2 + NE + ROWS * COLS - COLS);
|
|
++*(p2 + E - COLS);
|
|
++*(p2 + SE - COLS);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 8: ++*(p2 + NW + COLS);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE - ROWS * COLS);
|
|
++*(p2 + S - ROWS * COLS);
|
|
++*(p2 + SW + COLS - ROWS * COLS);
|
|
++*(p2 + W + COLS);
|
|
break;
|
|
case 9: ++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE - COLS);
|
|
++*(p2 + E - COLS);
|
|
++*(p2 + SE - ROWS * COLS - COLS);
|
|
++*(p2 + S - ROWS * COLS);
|
|
++*(p2 + SW - ROWS * COLS);
|
|
++*(p2 + W);
|
|
break;
|
|
default: break;
|
|
}
|
|
} /* End if. */
|
|
} /* End for. */
|
|
pass2(); /* Call pass2, for birth & death */
|
|
refresh(scrnbuf, videomem); /* Write final results to screen */
|
|
}
|
|
|
|
/* Do scan of Matrix 2 (pass 2).
|
|
*
|
|
* Scan Matrix 2: a value of 3 indicates a blank cell which should
|
|
* undergo a "birth." A value > 100 (cell was alive) but NOT equal
|
|
* to 102 or 103 means that there is a living cell that must die.
|
|
* Adjust screen buffer and Matrix 1 accordingly.
|
|
*/
|
|
pass2()
|
|
{
|
|
int i; /* Loop variable */
|
|
register char *p2; /* Pointer into Matrix 2 */
|
|
register int j; /* Inner-loop variable */
|
|
char *bp; /* Pointer into screen buffer */
|
|
char *top_left; /* Location of top-right cell within
|
|
video buffer */
|
|
int diff; /* Distance between Matrixes 1 & 2 */
|
|
|
|
top_left = scrnbuf + 162;
|
|
p2 = (char *) mat2;
|
|
diff = (char *) mat2 - (char *) mat1;
|
|
for (i = 0; i < ROWS * 160; i += 160)
|
|
for (j = 0; j < COLS; j++, p2++) {
|
|
if (*p2 < 100) {
|
|
if (*p2 == 3) {
|
|
*(p2 - diff) += 100;
|
|
bp = top_left + i + (j * 2);
|
|
*bp = cell;
|
|
*(bp + 1) = attrib;
|
|
}
|
|
} else
|
|
if (*p2 < 102 || *p2 > 103) {
|
|
*(p2 - diff) -= 100;
|
|
bp = top_left + i + (j * 2);
|
|
*bp = ' ';
|
|
*(bp + 1) = attrib;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Process keystroke.
|
|
*
|
|
* Check keyboard buffer and take action if a keystroke is waiting
|
|
* there in the buffer.
|
|
*/
|
|
proc_key()
|
|
{
|
|
int key; /* value of keystroke */
|
|
|
|
while (get_key(&key)) {
|
|
switch (key) {
|
|
case('p'):
|
|
case('P'): pause = 1;
|
|
break;
|
|
case('s'):
|
|
case('S'): if (speed > 1)
|
|
--speed;
|
|
break;
|
|
case('f'):
|
|
case('F'): if (speed < 10)
|
|
++speed;
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Pause screen.
|
|
*
|
|
* Print "pause condition" prompt and wait for a meaningful
|
|
* keystroke. Then take action based on this keystroke.
|
|
*/
|
|
pause_screen()
|
|
{
|
|
int key; /* value of keystroke */
|
|
|
|
set_cursor(23,0);
|
|
printf("PAUSE screen controls: C=continue Q=quit");
|
|
printf(" S=step R=restart ");
|
|
set_cursor(26,0);
|
|
|
|
do {
|
|
while (!get_key(&key))
|
|
;
|
|
} while (key!='c' && key!='C' && key!='Q' && key!= 'q' &&
|
|
key!='S' && key!='s' && key!='R' && key!='r');
|
|
switch(key) {
|
|
case('C'):
|
|
case('c'):
|
|
draw_prompt_box();
|
|
pause = 0;
|
|
break;
|
|
case('Q'):
|
|
case('q'):
|
|
more_rounds = 0;
|
|
return(1);
|
|
break;
|
|
case('S'):
|
|
case('s'):
|
|
generation();
|
|
repeat--;
|
|
break;
|
|
case('E'):
|
|
case('e'):
|
|
edit();
|
|
break;
|
|
case('R'):
|
|
case('r'):
|
|
more_rounds = 1;
|
|
return(1);
|
|
default:
|
|
break;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* Initialize video adapter indicator.
|
|
*
|
|
* Determine whether adapter is monochrome, ega, or cga.
|
|
* Issue set mode BIOS command, using standard mode for color,
|
|
* B&W, or monochrome.
|
|
*/
|
|
init_adapter()
|
|
{
|
|
int mode; /* Value returned by BIOS call */
|
|
union REGS regs;
|
|
|
|
regs.h.ah = 0xF;
|
|
int86(0x10, ®s, ®s); /* Get video mode, place in AL */
|
|
mode = regs.h.al;
|
|
if (mode == 7) /* 7 and 15 are MONO modes */
|
|
adapter = MONO;
|
|
else if (mode == 15) {
|
|
adapter = MONO;
|
|
set_mode(7); /* Set to 7, standard MONO mode */
|
|
} else {
|
|
adapter = is_ega(); /* Test for CGA vs. EGA */
|
|
if (mode >= 8 && mode <=14)
|
|
set_mode(3);
|
|
else switch (mode) {
|
|
case 1: /* Color */
|
|
case 3:
|
|
case 4: set_mode(3); /* 3 is standard color mode */
|
|
break;
|
|
case 0: /* B & W */
|
|
case 2:
|
|
case 5:
|
|
case 6: set_mode(2); /* 2 is standard B & W mode */
|
|
break;
|
|
} /* end switch */
|
|
} /* end else */
|
|
}
|
|
|
|
|
|
is_ega()
|
|
{
|
|
union REGS regs;
|
|
char far *ega_byte = (char far *) 0x487;
|
|
int ega_inactive;
|
|
|
|
regs.h.ah = 0x12;
|
|
regs.x.cx = 0;
|
|
regs.h.bl = 0x10;
|
|
int86(0x10, ®s, ®s);
|
|
if (regs.x.cx == 0)
|
|
return (CGA);
|
|
ega_inactive = *ega_byte & 0x8;
|
|
if (ega_inactive)
|
|
return (CGA);
|
|
return (EGA);
|
|
}
|
|
|
|
set_mode(mode)
|
|
int mode;
|
|
{
|
|
union REGS regs;
|
|
|
|
regs.h.al = (char) mode;
|
|
regs.h.ah = 0;
|
|
int86(0x10, ®s, ®s);
|
|
regs.h.al = 0;
|
|
regs.h.ah = 5;
|
|
int86(0x10, ®s, ®s);
|
|
}
|