1165 lines
34 KiB
C
1165 lines
34 KiB
C
/* LIFE.C - Game of LIFE demonstration program.
|
|
*
|
|
* The game of life was invented in 1970 by mathematics professor John
|
|
* Horton Conway. The object is to create and study the life forms that
|
|
* evolve from patterns entered on the board (usually a video screen,
|
|
* though life can also be played with pencil and paper).
|
|
*
|
|
* The game of life is based on the following laws:
|
|
*
|
|
* 1. Law of Survival - If a living cell has either two or or three
|
|
* neighbors, it survives.
|
|
*
|
|
* 2. Law of Death - A living cell with more than three neighbors
|
|
* dies of overcrowding. A living cell with less than two
|
|
* neighboers dies of isolation.
|
|
*
|
|
* 3. Law of Birth - A dead cell with exactly three neighbors is born
|
|
* in the next generation.
|
|
*
|
|
* These simple laws result in complex interactions. For example,
|
|
* try entering the following patterns:
|
|
*
|
|
* þþ þ þ þþ þ þ
|
|
* þ þ þ þ þþ þþþþþ þþ þþþþ þþ
|
|
* þ þþ þþþ þ þ þ þ þ þ þ
|
|
* þþþþ
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <graph.h>
|
|
#include "tools.h"
|
|
|
|
/* Dimensions of population matrix for largest possible screen--
|
|
* VGA 80 * 50).
|
|
*/
|
|
#define MAXROWS 46
|
|
#define MAXCOLS 78
|
|
#define MAXSCRN ((MAXROWS + 2) * (MAXCOLS + 2))
|
|
|
|
#define SROWS (cfg.mrows + 2) /* Screen rows */
|
|
#define SCOLS (cfg.mcols + 2) /* Screen columns */
|
|
#define SBUFSIZE (SROWS * SCOLS) /* Screen buffer size */
|
|
#define MBUFSIZE (cfg.mrows * cfg.mcols) /* Matrix buffer size */
|
|
|
|
#define BEEP 7
|
|
|
|
/* Action for change_cell */
|
|
enum CELLCHANGE { OFF, ON, TOGGLE };
|
|
|
|
/* Cell type specifies the symbol and attribute of a cell. The two
|
|
* most common cells, life and death, are initialized.
|
|
*/
|
|
typedef struct LIFECELL
|
|
{
|
|
unsigned char symbol;
|
|
unsigned char atrib;
|
|
} CELL;
|
|
CELL life =
|
|
{ 'þ',
|
|
SETATRIB( _TBRIGHTWHITE, _TBLACK )
|
|
};
|
|
CELL death =
|
|
{ ' ',
|
|
SETATRIB( _TBRIGHTWHITE, _TBLACK )
|
|
};
|
|
|
|
/* Structure for overall attributes of life, initialized for mono */
|
|
struct LIFECONFIG
|
|
{
|
|
float density; /* Percent of random distribution */
|
|
int rescan; /* CGA rescan flag */
|
|
int far *videomem; /* Address of video memory */
|
|
char boxatrib; /* Attribute for frame */
|
|
char helpatrib; /* Attribute for prompt line */
|
|
int mrows; /* Matrix rows */
|
|
int mcols; /* Matrix columns */
|
|
unsigned cursor; /* Cursor begin and end lines */
|
|
} cfg =
|
|
{
|
|
0.40,
|
|
FALSE,
|
|
(int far *)0xb0000000,
|
|
SETATRIB( _TWHITE, _TBLACK ),
|
|
SETATRIB( _TBLACK, _TWHITE ),
|
|
21,
|
|
78
|
|
};
|
|
|
|
/* Global variables */
|
|
char mat1[MAXROWS][MAXCOLS]; /* Matrix 1: stores current population */
|
|
char mat2[MAXROWS][MAXCOLS]; /* Matrix 2: stores crowding-numbers */
|
|
int cell; /* Cell character */
|
|
char attrib; /* Video attribute of each location */
|
|
int forever; /* Unlimited no. of generations? */
|
|
long repeat; /* Maximum number of generations to do */
|
|
CELL scrnbuf[MAXSCRN]; /* Screen Buffer area */
|
|
|
|
/* Key codes */
|
|
#define HM 0x0147
|
|
#define UA 0x0148
|
|
#define PU 0x0149
|
|
#define RA 0x014d
|
|
#define PD 0x0151
|
|
#define DA 0x0150
|
|
#define ED 0x014f
|
|
#define LA 0x014b
|
|
#define SH_HM 0x0247
|
|
#define SH_UA 0x0248
|
|
#define SH_PU 0x0249
|
|
#define SH_RA 0x024d
|
|
#define SH_PD 0x0251
|
|
#define SH_DA 0x0250
|
|
#define SH_ED 0x024f
|
|
#define SH_LA 0x024b
|
|
#define INS 0x0152
|
|
#define DEL 0x0153
|
|
#define ENTER 13
|
|
#define ESC 27
|
|
|
|
/* String prompts */
|
|
char run_str[] =
|
|
"RUN: F=Faster S=Slower O=Options Q=Quit";
|
|
|
|
char edit_str[] =
|
|
"EDIT: ARROWs=Move SHIFT-ARROWs=Move/toggle SPACE=Toggle ENTER=Done C=Clear";
|
|
|
|
char pause_str[] =
|
|
"PAUSE: G=Go C=Clear Q=Quit S=Step E=Edit N=New random R=Read W=Write";
|
|
|
|
char file_str[] = "Enter file name: ";
|
|
|
|
char ferr_str[] = "File access failure - press a key to continue . . .";
|
|
|
|
char dense_str[] = "Invalid density - press a key to continue . . .";
|
|
|
|
/* Function prototypes */
|
|
int main( int argc, char **argv );
|
|
void run_mode( void );
|
|
int pause_mode( void );
|
|
void edit_mode( void );
|
|
void init_life( void );
|
|
void init_buf( void );
|
|
void draw_box( void );
|
|
void init_mats( void );
|
|
void rand_dist( float chance );
|
|
void generation( void );
|
|
void pass2( void );
|
|
void change_cell( int action, int row, int col );
|
|
int read_life( void );
|
|
int write_life( void );
|
|
int show_prompt( char *prompt, char response[] );
|
|
void refresh( CELL inbuffer[], int far *outbuffer );
|
|
|
|
/* main - Runs the game of life.
|
|
*
|
|
* Params: argc - the number of command-line arguments
|
|
* argv - array of command-line strings
|
|
*
|
|
* Return: 0
|
|
*
|
|
* Uses: repeat
|
|
*/
|
|
int main( int argc, char **argv )
|
|
{
|
|
|
|
/* Initialize video and matrixes. Draw frame. */
|
|
init_life();
|
|
init_buf();
|
|
draw_box();
|
|
init_mats();
|
|
|
|
/* If no command-line argument, run forever. Otherwise, repeat the number
|
|
* of times specified in command line. 0 in command line means start
|
|
* by editing, not randomizing.
|
|
*/
|
|
if( argc > 1 )
|
|
{
|
|
repeat = atol( argv[1] );
|
|
forever = FALSE;
|
|
}
|
|
else
|
|
{
|
|
repeat = TRUE;
|
|
forever = TRUE;
|
|
}
|
|
if ( !repeat )
|
|
{
|
|
forever = TRUE;
|
|
edit_mode();
|
|
}
|
|
else
|
|
rand_dist( cfg.density );
|
|
|
|
/* Run life. */
|
|
run_mode();
|
|
|
|
/* Restore and quit. */
|
|
_settextcursor( cfg.cursor );
|
|
_displaycursor( _GCURSORON );
|
|
_setvideomode( _DEFAULTMODE );
|
|
exit( FALSE );
|
|
}
|
|
|
|
/* run_mode - Runs life, checking between generations for keystrokes.
|
|
* When a keystroke is received, take appropriate action.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: repeat, forever, run_str
|
|
*/
|
|
void run_mode()
|
|
{
|
|
char key;
|
|
static clock_t speed = 100; /* Delay in microseconds (less than 1000) */
|
|
|
|
/* Display command prompt and check keys while running. */
|
|
show_prompt( run_str, "" );
|
|
while( forever || repeat-- )
|
|
{
|
|
delay( speed );
|
|
generation();
|
|
if( key = getkey( NO_WAIT ) )
|
|
{
|
|
key = toupper( key );
|
|
switch( key )
|
|
{
|
|
case 'O': /* Do pause mode action */
|
|
if( !pause_mode() )
|
|
return;
|
|
break;
|
|
case 'F': /* Faster */
|
|
if( speed )
|
|
speed -= 100;
|
|
break;
|
|
case 'S': /* Slower */
|
|
if( speed < 1000 )
|
|
speed += 100;
|
|
break;
|
|
case 'Q': /* Terminate */
|
|
case ESC:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* pause_mode - Gets a pause mode keystroke and takes appropriate action.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: FALSE if quit, TRUE if any other command
|
|
*
|
|
* Uses: cfg and various message strings
|
|
*/
|
|
int pause_mode()
|
|
{
|
|
int i, pause = TRUE;
|
|
char tmp[80];
|
|
char key;
|
|
|
|
show_prompt( pause_str, "" );
|
|
while( pause )
|
|
{
|
|
key = getkey( WAIT );
|
|
switch( toupper( key ) )
|
|
{
|
|
|
|
case 'C': /* Clear life arena */
|
|
init_buf();
|
|
draw_box();
|
|
init_mats();
|
|
break;
|
|
case 'G': /* Go - restart life */
|
|
pause = FALSE;
|
|
break;
|
|
case 'E': /* Edit - edit current pattern */
|
|
edit_mode();
|
|
break;
|
|
case 'Q': /* Quit - end game */
|
|
case ESC:
|
|
return FALSE;
|
|
case 'S': /* Step - do one generation */
|
|
generation();
|
|
repeat--;
|
|
break;
|
|
case 'N': /* New - randomize again */
|
|
sprintf( tmp, "Current density: %.f Enter new: ",
|
|
cfg.density * 100 );
|
|
show_prompt( tmp, tmp );
|
|
i = atoi( tmp );
|
|
if ( (i < 1) || (i > 100) )
|
|
{
|
|
show_prompt( dense_str, "" );
|
|
putch( BEEP );
|
|
getch();
|
|
show_prompt( pause_str, "" );
|
|
break;
|
|
}
|
|
/* Clear screen and set new. */
|
|
init_buf();
|
|
draw_box();
|
|
init_mats();
|
|
show_prompt( pause_str, "" );
|
|
rand_dist( cfg.density = (float)(i / 100.0) );
|
|
break;
|
|
case 'R': /* Get a new pattern from file */
|
|
if( !read_life() )
|
|
{
|
|
show_prompt( ferr_str, "" );
|
|
putch( BEEP );
|
|
getch();
|
|
}
|
|
show_prompt( pause_str, "" );
|
|
break;
|
|
case 'W': /* Write current pattern to file */
|
|
if( !write_life() )
|
|
{
|
|
show_prompt( ferr_str, "" );
|
|
putch( BEEP );
|
|
getch();
|
|
}
|
|
show_prompt( pause_str, "" );
|
|
break;
|
|
}
|
|
}
|
|
/* Restore run prompt. */
|
|
show_prompt( run_str, "" );
|
|
return TRUE;
|
|
}
|
|
|
|
/* edit_mode - Repeatedly accepts editing keystrokes and takes
|
|
* appropriate action.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: repeat, cfg, edit_str, pause_str
|
|
*/
|
|
void edit_mode()
|
|
{
|
|
int more = TRUE;
|
|
unsigned key;
|
|
int curs_row = cfg.mrows / 2, curs_col = cfg.mcols / 2;
|
|
|
|
/* Update prompt, turn on cursor, and center cursor. */
|
|
show_prompt( edit_str, "" );
|
|
_displaycursor ( _GCURSORON );
|
|
_settextposition( curs_row + 2, curs_col + 2 );
|
|
|
|
do {
|
|
key = getkey( WAIT );
|
|
switch( key )
|
|
{
|
|
case SH_HM: /* Move northwest */
|
|
case HM:
|
|
if( (curs_col > 0) && (curs_row > 0) )
|
|
{
|
|
curs_col--;
|
|
curs_row--;
|
|
}
|
|
break;
|
|
case SH_UA: /* Move north */
|
|
case UA:
|
|
case 'k':
|
|
if( curs_row > 0 )
|
|
curs_row--;
|
|
break;
|
|
case SH_PU: /* Move northeast */
|
|
case PU:
|
|
if( (curs_col < cfg.mcols - 1) && (curs_row > 0) )
|
|
{
|
|
curs_col++;
|
|
curs_row--;
|
|
}
|
|
break;
|
|
case SH_RA: /* Move east */
|
|
case RA:
|
|
case 'l':
|
|
if( curs_col < cfg.mcols - 1)
|
|
curs_col++;
|
|
break;
|
|
case SH_PD: /* Move southeast */
|
|
case PD:
|
|
if( (curs_col < cfg.mcols - 1) && (curs_row < cfg.mrows - 1) )
|
|
{
|
|
curs_col++;
|
|
curs_row++;
|
|
}
|
|
break;
|
|
case SH_DA: /* Move south */
|
|
case DA:
|
|
case 'j':
|
|
if( curs_row < cfg.mrows - 1)
|
|
curs_row++;
|
|
break;
|
|
case SH_ED: /* Move southwest */
|
|
case ED:
|
|
if( (curs_col > 0 ) && (curs_row < cfg.mrows - 1) )
|
|
{
|
|
curs_col--;
|
|
curs_row++;
|
|
}
|
|
break;
|
|
case SH_LA: /* Move west */
|
|
case LA:
|
|
case 'h':
|
|
if( curs_col > 0 )
|
|
curs_col--;
|
|
break;
|
|
case ' ': /* Toggle current cell */
|
|
change_cell( TOGGLE, curs_row, curs_col );
|
|
break;
|
|
case INS: /* Turn current cell on */
|
|
change_cell( ON, curs_row, curs_col );
|
|
break;
|
|
case DEL: /* Turn current cell off */
|
|
change_cell( OFF, curs_row, curs_col );
|
|
break;
|
|
case 'C': /* Clear cells */
|
|
case 'c':
|
|
init_buf();
|
|
draw_box();
|
|
init_mats();
|
|
break;
|
|
case 'D': /* Done - accept editing changes */
|
|
case 'd':
|
|
case ENTER:
|
|
more = FALSE;
|
|
break;
|
|
default: /* Ignore unknown keys */
|
|
break;
|
|
}
|
|
/* If shift was down, toggle key. */
|
|
if( (key >> 8) == 2 )
|
|
change_cell( TOGGLE, curs_row, curs_col );
|
|
|
|
/* Update cursor position. */
|
|
_settextposition( curs_row + 2, curs_col + 2 );
|
|
|
|
} while( more );
|
|
|
|
/* Turn off cursor and restore pause prompt. */
|
|
_displaycursor (_GCURSOROFF );
|
|
show_prompt( pause_str, "" );
|
|
}
|
|
|
|
/* init_life - Initializes the screen mode, rows, and cursor status.
|
|
* Sets global screen, configuration, and life variables.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: Sets the following:
|
|
* cfg.rescan - flag for CGA retrace handling
|
|
* .cursor - cusor shape
|
|
* .mrows - maximum rows
|
|
* .videomem - pointer to screen buffer
|
|
* .boxatrib - foreground and background colors of frame
|
|
* .helpatrib - colors of help line
|
|
* life.atrib - colors of live cells
|
|
* death.atrib - colors of dead cells
|
|
*/
|
|
void init_life()
|
|
{
|
|
struct videoconfig vc;
|
|
|
|
/* Save starting cursor and set block cursor. Then turn it off. */
|
|
cfg.cursor = _settextcursor( SETCURSOR( 0, 7 ) );
|
|
_displaycursor( _GCURSOROFF );
|
|
|
|
/* Get configuration and set variables based on adapter. */
|
|
_getvideoconfig( &vc );
|
|
switch( vc.adapter )
|
|
{
|
|
case _CGA:
|
|
cfg.rescan = TRUE;
|
|
break;
|
|
case _EGA:
|
|
case _OEGA:
|
|
cfg.mrows = 39; /* 43-line - 4 lines */
|
|
break;
|
|
case _VGA:
|
|
case _OVGA:
|
|
cfg.mrows = 46; /* 50-line - 4 lines */
|
|
break;
|
|
default:
|
|
cfg.mrows = 21; /* 25-line - 4 lines */
|
|
break;
|
|
}
|
|
|
|
/* Set variables based on mode. */
|
|
switch( vc.mode )
|
|
{
|
|
|
|
case _HERCMONO:
|
|
case _ERESNOCOLOR:
|
|
case _TEXTMONO:
|
|
_setvideomoderows( _TEXTMONO, cfg.mrows + 4 );
|
|
break;
|
|
|
|
case _TEXTBW40:
|
|
case _TEXTBW80:
|
|
cfg.videomem = (int far *)0xb8000000;
|
|
_setvideomoderows( _TEXTBW80, cfg.mrows + 4 );
|
|
break;
|
|
|
|
default:
|
|
cfg.videomem = (int far *)0xb8000000;
|
|
cfg.boxatrib = SETATRIB( _TBRIGHTWHITE, _TBLUE );
|
|
life.atrib = death.atrib = SETATRIB( _TWHITE, _TBLUE );
|
|
cfg.helpatrib = SETATRIB( _TWHITE, _TBLACK );
|
|
_setvideomoderows( _TEXTC80, cfg.mrows + 4 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* init_buf - Initialize screen buffer dead cells.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void init_buf()
|
|
{
|
|
register CELL *p = scrnbuf;
|
|
|
|
while( p < scrnbuf + SBUFSIZE )
|
|
*p++ = death;
|
|
}
|
|
|
|
|
|
/* draw_box - Write extended-ascii line characters around the frame (box)
|
|
* of the screen buffer. Then write the modified buffer to the screen.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void draw_box()
|
|
{
|
|
register unsigned char *p = (char *)scrnbuf; /* Pointer into buffer */
|
|
int i, incr;
|
|
|
|
/* Draw top of box. */
|
|
*p = 'Ú';
|
|
p +=2;
|
|
for( i = 0; i < cfg.mcols; p += 2, i++ )
|
|
*p = 'Ä';
|
|
*p = '¿';
|
|
p += 2;
|
|
|
|
/* Draw side of box. */
|
|
incr = (SCOLS - 1) * 2;
|
|
for( i = 0; i < cfg.mrows; p += (SCOLS * 2), i++ )
|
|
{
|
|
*p = '³';
|
|
*(p + incr) = '³';
|
|
}
|
|
|
|
/* Draw bottom of box. */
|
|
*p = 'À';
|
|
p += 2;
|
|
for( i = 0; i < cfg.mcols; p += 2, i++)
|
|
*p = 'Ä';
|
|
*p = 'Ù';
|
|
|
|
/* Copy modified screen buffer to video memory. */
|
|
refresh( scrnbuf, cfg.videomem );
|
|
}
|
|
|
|
/* init_mats - Initializes life matrixes. Clears 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 the 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 add 100 to flag cell's existence.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void init_mats()
|
|
{
|
|
int i, j; /* Loop counters */
|
|
char *p = (char *)mat1; /* Pointer into matrix 1 */
|
|
|
|
/* Initialize zones in matrix 1 to 0. */
|
|
memset( mat1, 0, cfg.mrows * cfg.mcols );
|
|
memset( mat2, 0, cfg.mrows * cfg.mcols );
|
|
|
|
/* Initilialize row 1 to zones 6, 2, and 7. */
|
|
*p++ = 6;
|
|
for( i = 0; i < (cfg.mcols - 2); i++)
|
|
*p++ = 2;
|
|
*p++ = 7;
|
|
|
|
/* Initialize center rows to zones 4, 1, and 5. */
|
|
for( j = 0; j < (cfg.mrows - 2); j++ )
|
|
{
|
|
*p++ = 4;
|
|
for( i = 0; i < (cfg.mcols - 2); i++ )
|
|
*p++ = 1;
|
|
*p++ = 5;
|
|
}
|
|
|
|
/* Initialize bottom row to zones 8, 3, and 9. */
|
|
*p++ = 8;
|
|
for( i = 0; i < (cfg.mcols - 2); i++ )
|
|
*p++ = 3;
|
|
*p++ = 9;
|
|
}
|
|
|
|
/* rand_dist - Initializes a random distribution of cells. The cells
|
|
* are updated both in matrix 1 and in the screen buffer. If a cell has
|
|
* a random value greater than the calculated distribution, 100 is added
|
|
* to its value in matrix 1, and it is written into the screen buffer.
|
|
*
|
|
* Params: chance - the percentage of randomness
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void rand_dist( float chance )
|
|
{
|
|
char *p = (char *)mat1; /* Pointer to matrix 1 */
|
|
register CELL *bp = scrnbuf; /* Pointer to screen buffer */
|
|
int i, j; /* Loop counters */
|
|
int amt, rnd;
|
|
|
|
amt = (int)(chance * 32768); /* Amount to exceed for a cell to live */
|
|
srand( (unsigned)time( NULL ) );/* Randomize seed */
|
|
bp += SCOLS + 1; /* Start at first non-frame cell */
|
|
|
|
/* Assign life or death to each cell. */
|
|
for( i = 0; i < cfg.mrows; i++, bp += 2 )
|
|
{
|
|
for( j = 0; j < cfg.mcols; j++, p++, bp++ )
|
|
{
|
|
rnd = rand();
|
|
if( rnd < amt )
|
|
{
|
|
*p += 100;
|
|
*bp = life;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Put results on the screen. */
|
|
refresh( scrnbuf, cfg.videomem );
|
|
}
|
|
|
|
#define NW (-1-cfg.mcols) /* Directional constants, within */
|
|
#define N (-cfg.mcols) /* matrix 2. For example, NW refers */
|
|
#define NE (1-cfg.mcols) /* to the upper, left-hand neighbor */
|
|
#define E (1)
|
|
#define SE (1+cfg.mcols)
|
|
#define S (cfg.mcols)
|
|
#define SW (-1+cfg.mcols)
|
|
#define W (-1)
|
|
|
|
/* generation - Do one generation of life. First matrix 2 is cleared, 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 the cell is not living, do nothing.
|
|
* This provides a fast method of 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.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void generation()
|
|
{
|
|
register char *p1; /* Pointers into matrixes 1 and 2 */
|
|
register char *p2;
|
|
int diff; /* Bytes between matrixes 1 and 2 */
|
|
int zone; /* Zone of each cell */
|
|
int msize = MBUFSIZE;
|
|
|
|
/* Clear matrix 2 and calculate distance between zones 1 and 2. */
|
|
memset( mat2, 0, msize );
|
|
diff = (char *)mat2 - (char *)mat1;
|
|
|
|
/* For each cell . . . */
|
|
for( p1 = (char *)mat1; p1 < (char *)mat1 + msize; p1++ )
|
|
{
|
|
/* If matrix 1 cell is alive . . . */
|
|
if( *p1 > 100 )
|
|
{
|
|
/* Point to matrix 2 cell and update it. */
|
|
p2 = p1 + diff;
|
|
*p2 += 100;
|
|
|
|
/* Get the zone and update the neighbors accordingly. */
|
|
zone = (*p1 - 100);
|
|
switch( zone )
|
|
{
|
|
case 1:
|
|
++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 2:
|
|
++*(p2 + NW + msize);
|
|
++*(p2 + N + msize);
|
|
++*(p2 + NE + msize);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 3:
|
|
++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE - msize);
|
|
++*(p2 + S - msize);
|
|
++*(p2 + SW - msize);
|
|
++*(p2 + W);
|
|
break;
|
|
case 4:
|
|
++*(p2 + NW + cfg.mcols);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW + cfg.mcols);
|
|
++*(p2 + W + cfg.mcols);
|
|
break;
|
|
case 5:
|
|
++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE - cfg.mcols);
|
|
++*(p2 + E - cfg.mcols);
|
|
++*(p2 + SE - cfg.mcols);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 6:
|
|
++*(p2 + NW + msize + cfg.mcols);
|
|
++*(p2 + N + msize);
|
|
++*(p2 + NE + msize);
|
|
++*(p2 + E);
|
|
++*(p2 + SE);
|
|
++*(p2 + S);
|
|
++*(p2 + SW + cfg.mcols);
|
|
++*(p2 + W + cfg.mcols);
|
|
break;
|
|
case 7:
|
|
++*(p2 + NW + msize);
|
|
++*(p2 + N + msize);
|
|
++*(p2 + NE + msize - cfg.mcols);
|
|
++*(p2 + E - cfg.mcols);
|
|
++*(p2 + SE - cfg.mcols);
|
|
++*(p2 + S);
|
|
++*(p2 + SW);
|
|
++*(p2 + W);
|
|
break;
|
|
case 8:
|
|
++*(p2 + NW + cfg.mcols);
|
|
++*(p2 + N);
|
|
++*(p2 + NE);
|
|
++*(p2 + E);
|
|
++*(p2 + SE - msize);
|
|
++*(p2 + S - msize);
|
|
++*(p2 + SW + cfg.mcols - msize);
|
|
++*(p2 + W + cfg.mcols);
|
|
break;
|
|
case 9:
|
|
++*(p2 + NW);
|
|
++*(p2 + N);
|
|
++*(p2 + NE - cfg.mcols);
|
|
++*(p2 + E - cfg.mcols);
|
|
++*(p2 + SE - msize - cfg.mcols);
|
|
++*(p2 + S - msize);
|
|
++*(p2 + SW - msize);
|
|
++*(p2 + W);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} /* End if */
|
|
} /* End for */
|
|
|
|
/* Call pass2 to calculate birth or death of each cell. */
|
|
pass2();
|
|
}
|
|
|
|
/* pass2 - Scan matrix 2 and update matrix 1 according to the following:
|
|
*
|
|
* Matrix 2 value Matrix 1 result
|
|
* -------------- ----------------------
|
|
* 3 Dead cell becomes live
|
|
* 102, 103 No change
|
|
* other > 100 Live cell becomes dead
|
|
* other < 100 No change
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void pass2()
|
|
{
|
|
register int i, j; /* Loop variables */
|
|
register char *p2= (char *)mat2;/* Pointer into matrix 2 */
|
|
CELL *bp = scrnbuf; /* Pointer into screen buffer */
|
|
int diff; /* Distance between matrixes */
|
|
|
|
/* Skip frame to first cell and calculate distance between matrixes. */
|
|
bp += SCOLS + 1;
|
|
diff = (char *)mat2 - (char *)mat1;
|
|
|
|
/* Outer loop counts rows. */
|
|
for( i = 0; i < cfg.mrows; i++, bp += 2 )
|
|
{
|
|
/* Next loop counts columns. */
|
|
for( j = 0; j < cfg.mcols; j++, p2++, bp++ )
|
|
{
|
|
/* Write live cell if 3. */
|
|
if( *p2 < 100 )
|
|
{
|
|
if( *p2 == 3 )
|
|
{
|
|
*(p2 - diff) += 100;
|
|
*bp = life;
|
|
}
|
|
}
|
|
else
|
|
/* Dead cell if above 100, but not 102 or 103. */
|
|
{
|
|
if( (*p2 < 102) || (*p2 > 103) )
|
|
{
|
|
*(p2 - diff) -= 100;
|
|
*bp = death;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Put results on the screen. */
|
|
refresh( scrnbuf, cfg.videomem );
|
|
}
|
|
|
|
/* change_cell - Set the state of a specified cell. The cell may be turned
|
|
* on, off, or toggled. Update the status in matrix 1 and in the screen
|
|
* buffer.
|
|
*
|
|
* Params: action - OFF, ON, or TOGGLE
|
|
* row
|
|
* col
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
void change_cell( int action, int row, int col )
|
|
{
|
|
register CELL *sp = scrnbuf;
|
|
|
|
/* Skip frame to first cell. */
|
|
sp += SCOLS + 1;
|
|
sp += row * SCOLS;
|
|
sp += col;
|
|
|
|
/* Set cell state. */
|
|
switch( action )
|
|
{
|
|
case OFF:
|
|
mat1[row][col] -= 100;
|
|
*sp = death;
|
|
break;
|
|
case ON:
|
|
mat1[row][col] += 100;
|
|
*sp = life;
|
|
break;
|
|
case TOGGLE:
|
|
if( mat1[row][col] > 100 )
|
|
{
|
|
mat1[row][col] -= 100;
|
|
*sp = death;
|
|
}
|
|
else
|
|
{
|
|
mat1[row][col] += 100;
|
|
*sp = life;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Show result on screen. */
|
|
refresh( scrnbuf, cfg.videomem );
|
|
}
|
|
|
|
/* show_prompt - Displays a specified prompt line on the bottom of the
|
|
* screen. If a non-null buffer is passed for a response, waits for the
|
|
* user to enter a string.
|
|
*
|
|
* Params: prompt - prompt or help string
|
|
* response - buffer for string response (if NULL, no response)
|
|
*
|
|
* Return: TRUE if no response expected or valid received, FALSE if
|
|
* invalid response
|
|
*/
|
|
int show_prompt( char *prompt, char response[] )
|
|
{
|
|
char tmp[81];
|
|
|
|
/* Clear old prompt, and write new. */
|
|
memset( tmp, ' ', 80 );
|
|
tmp[80] = 0;
|
|
_settextcolor( cfg.helpatrib & 0x00ff );
|
|
_setbkcolor( (long)(cfg.helpatrib >> 4) );
|
|
_settextposition( SROWS + 1, 1 );
|
|
_outtext( tmp );
|
|
_settextposition( SROWS + 1, 1 );
|
|
_outtext( prompt );
|
|
|
|
/* If a response buffer was passed, get a response for it. */
|
|
if ( *response )
|
|
{
|
|
_displaycursor( _GCURSORON );
|
|
tmp[0] = 80;
|
|
cgets( tmp );
|
|
strcpy( response, tmp + 2 );
|
|
|
|
_displaycursor( _GCURSOROFF );
|
|
if( *response )
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
/* read_life - Reads a life pattern from a file. The pattern is bit decoded
|
|
* (life for bit set, death for bit clear). If the file contains more cells
|
|
* than the screen, extra cells are ignored. For example, this happens if
|
|
* a pattern was saved in 25-line mode, but read in 43-line mode.
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: TRUE if successful, FALSE if unsuccessful
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
int read_life()
|
|
{
|
|
CELL *scrnp = scrnbuf; /* Pointer into screen buffer */
|
|
char *matp = (char *)mat1; /* Pointer into matrix 1 */
|
|
unsigned char mbyte, fbyte; /* Mask byte and read byte */
|
|
int i, j, k; /* Loop counters */
|
|
int more = TRUE; /* Continuation flag */
|
|
FILE *filep; /* Ptr to file struct */
|
|
char fname[81]; /* File name buffer */
|
|
|
|
/* Fail if prompt or file open fails. */
|
|
if( !show_prompt( file_str, fname ) )
|
|
return FALSE;
|
|
if( !(filep = fopen( fname, "rb" )) )
|
|
return FALSE;
|
|
|
|
/* Initialize buffer, screen, and pointers. */
|
|
init_buf();
|
|
init_mats();
|
|
draw_box();
|
|
scrnp += SCOLS + 1;
|
|
|
|
/* Initialize mask byte and read first byte. */
|
|
mbyte = 0x80;
|
|
fread( (void *)&fbyte, 1, 1, filep );
|
|
|
|
/* Count rows . . . */
|
|
for( i = 0, k = 0; (i < cfg.mrows) && more; i++, scrnp += 2 )
|
|
{
|
|
/* Count columns . . . */
|
|
for( j = 0; (j < cfg.mcols) && more; j++, scrnp++, matp++)
|
|
{
|
|
/* If bit is on, make cell live. */
|
|
if( mbyte & fbyte )
|
|
{
|
|
*matp += 100;
|
|
*scrnp = life;
|
|
}
|
|
|
|
/* Adjust mask and read another byte if necessary. */
|
|
mbyte >>= 1;
|
|
if( ++k > 7 )
|
|
{
|
|
k = 0;
|
|
mbyte = 0x80;
|
|
|
|
/* Quit if there are no more bytes in file. */
|
|
if( !fread( (void *)&fbyte, 1, 1, filep ) )
|
|
more = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Show on screen and close file. */
|
|
refresh( scrnbuf, cfg.videomem );
|
|
fclose( filep );
|
|
return TRUE;
|
|
}
|
|
|
|
/* write_life - Writes a life pattern to a file. The pattern is bit encoded
|
|
* (life for bit set, death for bit clear).
|
|
*
|
|
* Params: None
|
|
*
|
|
* Return: TRUE if successful, FALSE if unsuccessful
|
|
*
|
|
* Uses: scrnbuf, cfg
|
|
*/
|
|
int write_life()
|
|
{
|
|
char *matp = (char *)mat1; /* Pointer into matrix 1 */
|
|
unsigned char mbyte, fbyte; /* Mask byte and read byte */
|
|
int i, j, k; /* Loop counters */
|
|
FILE *filep; /* Ptr to file struct */
|
|
char fname[81]; /* File name buffer */
|
|
|
|
/* Fail if prompt or file open fails. */
|
|
if( !show_prompt( file_str, fname ) )
|
|
return FALSE;
|
|
if( !(filep = fopen( fname, "wb" )) )
|
|
return FALSE;
|
|
|
|
/* Initialize mask and read bytes. */
|
|
mbyte = 0x80;
|
|
fbyte = k = 0;
|
|
|
|
/* Count rows . . . */
|
|
for( i = 0; i < cfg.mrows; i++)
|
|
{
|
|
/* Count columns . . . */
|
|
for( j = 0; j < cfg.mcols; j++, matp++)
|
|
{
|
|
/* If cell is live, turn bit on. */
|
|
if( *matp > 100 )
|
|
fbyte += mbyte;
|
|
|
|
/* Adjust mask and write another byte if necessary. */
|
|
mbyte >>= 1;
|
|
if( ++k > 7 )
|
|
{
|
|
fwrite( (void *)&fbyte, 1, 1, filep );
|
|
fbyte = k = 0;
|
|
mbyte = 0x80;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Make sure last byte is written, then close file. */
|
|
if( k > 0 )
|
|
fwrite( (void *)&fbyte, 1, 1, filep );
|
|
fclose( filep );
|
|
return TRUE;
|
|
}
|
|
|
|
/* refresh - Writes buffer containing cells to the actual video screen
|
|
* buffer. If CGA, adjust for rescan while copying. Otherwise, copy
|
|
* directly. The CGA variation can only be done fast enough in assembly.
|
|
*
|
|
* Params: inbuffer - internal buffer containing cells
|
|
* outbuffer - pointer to hardware video memory
|
|
*
|
|
* Return: None
|
|
*
|
|
* Uses: cfg
|
|
*/
|
|
void refresh( CELL inbuffer[], int far *outbuffer )
|
|
{
|
|
int ssize = SBUFSIZE;
|
|
|
|
_asm \
|
|
{
|
|
mov si, inbuffer ; Load src = screen buffer
|
|
les di, outbuffer ; Load dest = video memory
|
|
mov cx, ssize ; rows * columns
|
|
cld ; DF = 0 (direction flag)
|
|
|
|
cmp cfg.rescan, FALSE ; If not CGA, don't check rescan
|
|
je notcga
|
|
|
|
mov dx, 03DAh
|
|
wait0:
|
|
sti
|
|
nop
|
|
cli
|
|
lodsw ; Load character and save in BX
|
|
mov bx, ax
|
|
|
|
wait1: in al, dx ; Wait till horizontal active
|
|
shr al, 1
|
|
jc wait1
|
|
cli
|
|
|
|
wait2: in al, dx ; Wait till horizontal inactive (retrace)
|
|
shr al, 1
|
|
jnc wait2
|
|
|
|
mov ax, bx ; Restore character and
|
|
stosw ; move to video memory
|
|
sti
|
|
|
|
loop wait0 ; Next
|
|
jmp SHORT getout ; Done for CGA
|
|
|
|
notcga: ; Non-CGA version
|
|
rep movsw ; Copy the whole screen with no waiting
|
|
getout:
|
|
|
|
}
|
|
}
|