dos_compilers/Microsoft QuickC v251/SAMPLES/LIFE.C
2024-07-02 08:12:15 -07:00

1150 lines
34 KiB
C

/* LIFE.C - Game of LIFE demonstration program.
*
* If GRAPHICS.LIB was not included in your run-time libraries during
* setup, you must you must add it to the LIFE.MAK program list or
* give it on the QCL command line.
*
* 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
{
double 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 */
unsigned mrows; /* Matrix rows */
unsigned 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 number of generations? */
long repeat; /* Maximum number of generations to do */
CELL _near 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 */
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( double 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 _near 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
*/
void 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()
{
unsigned 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];
unsigned 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 = (double)(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;
unsigned 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 );
/* Set the highest best possible number of rows. */
cfg.mrows = _settextrows( _MAXTEXTROWS ) - 4;
/* Get configuration and set variables based on adapter. */
_getvideoconfig( &vc );
if( vc.adapter == _CGA )
cfg.rescan = TRUE;
if( vc.numxpixels || (vc.numtextcols != 80) )
{
_outtext( "Start LIFE in 80-column text mode" );
exit( TRUE );
}
/* Set variables based on mode. Use default for mono. Reset video
* address for graphics adapters. Reset attributes for color text.
*/
if( vc.mode != _TEXTMONO )
{
cfg.videomem = (int _far *)0xb8000000;
if( vc.mode == _TEXTC80 )
{
cfg.boxatrib = SETATRIB( _TBRIGHTWHITE, _TBLUE );
life.atrib = death.atrib = SETATRIB( _TWHITE, _TBLUE );
cfg.helpatrib = SETATRIB( _TWHITE, _TBLACK );
}
}
}
/* 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 */
unsigned 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()
{
unsigned 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( double chance )
{
char *p = (char *)mat1; /* Pointer to matrix 1 */
register CELL *bp = scrnbuf; /* Pointer to screen buffer */
unsigned 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 char *p2= (char *)mat2;/* Pointer into matrix 2 */
CELL *bp = scrnbuf; /* Pointer into screen buffer */
unsigned i, j; /* Loop variables */
unsigned 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 */
unsigned 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 mbyte, fbyte; /* Mask byte and read byte */
unsigned 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 _near 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:
}
}