Conway's Life Program

General discussions related to the Altair 8800 Clone

Conway's Life Program

Postby Wayne Parham » July 1st, 2022, 7:32 pm

Remember Conway's "Game of Life" - the "Life Simulator?"

I always thought it was kind of cool, way back when. It's still cool, actually.

Want one for your Altair or your Altair Clone? A faster one than the BASIC version?

So grab a copy of BDS C, copy the source file below, save it as "clife.c" on your BDS disk and compile it using these commands:

cc clife
clink clife

It'll make the CLIFE.COM executable.

Hope it's fun for you! Happy 4th of July weekend!

Code: Select all
/* cLife.c                           */

/* Conway's Life Simulation          */   

/* Wayne Parham                      */


#include <stdio.h>

#ifndef XMAX
#define XMAX      80
#endif

#ifndef YMAX
#define YMAX      22
#endif

#ifndef LINES
#define LINES     24
#endif

#ifndef IN_MAX
#define IN_MAX     3
#endif


int i,j;
int life;
int cycle;
int density;
int iteration;
int generations;
char array1[XMAX][YMAX];
char array2[XMAX][YMAX];


void cls() {
   for( j=0; j<LINES; j++ ) {
      putchar( '\n' );
   }
}


int getnum() {
   char c;
   int i;
   char  input[IN_MAX];
   int   value;

   for( i=0; i < IN_MAX && c != '\n' && c != '\r'; i++ ) {
      c = getchar();
      input[i] = c;
   }
   value = atoi( input );
   return( value );
}


void seed() {
   int n;
   srand(n);
   for( j=0; j<YMAX; j++ ) {
      for( i=0; i<XMAX; i++ ) {
         life = ( rand() % 100 );
         if( life < density )
            array1[i][j]='o';
         else
            array1[i][j]=' ';
      }
   }
}


void applyRule( world, alive, x, y ) {
   int neighbors;
   neighbors = 0;
   if( world == 1 ) {
      if( array1[x-1][y]   =='o') neighbors++;
      if( array1[x+1][y]   =='o') neighbors++;
      if( array1[x-1][y+1] =='o') neighbors++;
      if( array1[x][y+1]   =='o') neighbors++;
      if( array1[x+1][y+1] =='o') neighbors++;
      if( array1[x-1][y-1] =='o') neighbors++;
      if( array1[x][y-1]   =='o') neighbors++;
      if( array1[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array2[x][y]=' ';
      else if( (alive==1) && (neighbors>3) )  array2[x][y]=' ';
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array2[x][y]='o';
      else if( (alive==0) && (neighbors==3) ) array2[x][y]='o';
      else array2[x][y]=' ';
   }
   else if( world == 2 ) {
      if( array2[x-1][y]   =='o') neighbors++;
      if( array2[x+1][y]   =='o') neighbors++;
      if( array2[x-1][y+1] =='o') neighbors++;
      if( array2[x][y+1]   =='o') neighbors++;
      if( array2[x+1][y+1] =='o') neighbors++;
      if( array2[x-1][y-1] =='o') neighbors++;
      if( array2[x][y-1]   =='o') neighbors++;
      if( array2[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array1[x][y]=' ';
      else if( (alive==1) && (neighbors>3) )  array1[x][y]=' ';
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array1[x][y]='o';
      else if( (alive==0) && (neighbors==3) ) array1[x][y]='o';
      else array1[x][y]=' ';
   }
}


void generate( world ){
   int alive;
   if( world == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array2[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(2,alive,i,j);
         }
      }
   }
   else if( world == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array1[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(1,alive,i,j);
         }
      }
   }
}


void display( array ) {
   if( array == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array1[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
         }
      }
   }
   else if( array == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array2[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
    }
      }
   }
   printf( "                              Generation %d\n", iteration );
}


int main() {

   printf( "\n                                      Life\n" );

   printf( "\nThis  is a cellular automaton devised by the British mathematician John  Conway" );
   printf( "\nin  1970.   Its  evolution  is determined by its initial  state,  requiring  no" );
   printf( "\nfurther input.  You will only be asked for initial population density, which is" );
   printf( "\nthen used to create a random distribution of cells.\n" );

   printf( "\nThis  simulation  is  Turing complete and can produce and contain  a  universal" );
   printf( "\nconstructor or any other Turing machine.\n" );

   printf( "\nThe universe is a two-dimensional orthogonal grid, with each position in one of" );
   printf( "\ntwo possible states, live or dead, or populated and unpopulated,  respectively.\n" );

   printf( "\nEvery  cell interacts with its eight neighbours,  which are the cells that  are" );
   printf( "\nhorizontally,  vertically, or diagonally adjacent.   The following  transitions" );
   printf( "\noccur at each iteration, definining a generation:\n" );

   printf( "\n- A live cell with fewer than two live neighbours dies from underpopulation." );
   printf( "\n- A live cell with two or three live neighbours lives to the next generation." );
   printf( "\n- A live cell with more than three live neighbours dies from overpopulation." );
   printf( "\n- A space surrounded by three cells becomes a live cell from reproduction.\n" );

   printf( "\nStarting population density as a percentage? " );
   density = getnum();

   if( (density < 1) || (density > 100) ) {
      puts( "\nWhat?  Density must be between 1% and 99%.  Setting to 33%." );
      density = 33;
   }

   printf( "\nNumber of generations (0 to loop forever) ? " );
   generations = getnum();

   if( generations < 0 )
      generations = 0;

   seed();

   while( (generations == 0) || (generations > iteration) ) {

      cls();      
      iteration++;
      if( cycle ) {
         display(2);
         generate(1);
         cycle = 0;
      } else {
         display(1);
         generate(2);
         cycle = 1;
      }
   }

   return(0);
}

Wayne Parham
 
Posts: 94
Joined: March 18th, 2022, 3:01 pm

Re: Conway's Life Program

Postby BillO » July 3rd, 2022, 7:39 pm

Loaded up BDS C onto my dev volume and tried this out.

Thanks Wayne.
BillO
 
Posts: 100
Joined: November 11th, 2020, 6:29 am

Re: Conway's Life Program

Postby BillO » July 8th, 2022, 6:16 pm

I hope you don't mind Wayne, but I added a tiny bit of code for ANSI terminal support.

It allows you to see the evolution between generations a bit better if you have an ANSI terminal.

Code: Select all
/* cLife.c                                        */

/* Conway's Life Simulation                       */   

/* Wayne Parham                                   */

/* ANSI Terminal Support - 22/07/06 Bill O'Neill  */


#include <stdio.h>

#ifndef XMAX
#define XMAX      80
#endif

#ifndef YMAX
#define YMAX      22
#endif

#ifndef LINES
#define LINES     24
#endif

#ifndef IN_MAX
#define IN_MAX     3
#endif


int i,j;
int life;
int cycle;
int density;
int iteration;
int generations;
int term;
char array1[XMAX][YMAX];
char array2[XMAX][YMAX];

void cls() {
   for( j=0; j<LINES; j++ ) {
      putchar( '\n' );
   }
}

int getnum() {
   char c;
   int i;
   char  input[IN_MAX];
   int   value;

   for( i=0; i < IN_MAX && c != '\n' && c != '\r'; i++ ) {
      c = getchar();
      input[i] = c;
   }
   value = atoi( input );
   return( value );
}


void seed() {
   int n;
   n = 0;
   srand(n);
   for( j=0; j<YMAX; j++ ) {
      for( i=0; i<XMAX; i++ ) {
         life = ( rand() % 100 );
         if( life < density )
            array1[i][j]='o';
         else
            array1[i][j]=' ';
      }
   }
}


void applyRule( world, alive, x, y ) {
   int neighbors;
   neighbors = 0;
   if( world == 1 ) {
      if( array1[x-1][y]   =='o') neighbors++;
      if( array1[x+1][y]   =='o') neighbors++;
      if( array1[x-1][y+1] =='o') neighbors++;
      if( array1[x][y+1]   =='o') neighbors++;
      if( array1[x+1][y+1] =='o') neighbors++;
      if( array1[x-1][y-1] =='o') neighbors++;
      if( array1[x][y-1]   =='o') neighbors++;
      if( array1[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array2[x][y]=' ';
      else if( (alive==1) && (neighbors>3) )  array2[x][y]=' ';
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array2[x][y]='o';
      else if( (alive==0) && (neighbors==3) ) array2[x][y]='o';
      else array2[x][y]=' ';
   }
   else if( world == 2 ) {
      if( array2[x-1][y]   =='o') neighbors++;
      if( array2[x+1][y]   =='o') neighbors++;
      if( array2[x-1][y+1] =='o') neighbors++;
      if( array2[x][y+1]   =='o') neighbors++;
      if( array2[x+1][y+1] =='o') neighbors++;
      if( array2[x-1][y-1] =='o') neighbors++;
      if( array2[x][y-1]   =='o') neighbors++;
      if( array2[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array1[x][y]=' ';
      else if( (alive==1) && (neighbors>3) )  array1[x][y]=' ';
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array1[x][y]='o';
      else if( (alive==0) && (neighbors==3) ) array1[x][y]='o';
      else array1[x][y]=' ';
   }
}


void generate( world ){
   int alive;
   if( world == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array2[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(2,alive,i,j);
         }
      }
   }
   else if( world == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array1[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(1,alive,i,j);
         }
      }
   }
}


void display( array ) {
   if( array == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array1[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
         }
      }
   }
   else if( array == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array2[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
    }
      }
   }
   printf( "                              Generation %d\n", iteration );
}


int main() {

   printf( "\n                                      Life\n" );

   printf( "\nThis  is a cellular automaton devised by the British mathematician John  Conway" );
   printf( "\nin  1970.   Its  evolution  is determined by its initial  state,  requiring  no" );
   printf( "\nfurther input.  You will only be asked for initial population density, which is" );
   printf( "\nthen used to create a random distribution of cells.\n" );

   printf( "\nThis  simulation  is  Turing complete and can produce and contain  a  universal" );
   printf( "\nconstructor or any other Turing machine.\n" );

   printf( "\nThe universe is a two-dimensional orthogonal grid, with each position in one of" );
   printf( "\ntwo possible states, live or dead, or populated and unpopulated,  respectively.\n" );

   printf( "\nEvery  cell interacts with its eight neighbours,  which are the cells that  are" );
   printf( "\nhorizontally,  vertically, or diagonally adjacent.   The following  transitions" );
   printf( "\noccur at each iteration, definining a generation:\n" );

   printf( "\n- A live cell with fewer than two live neighbours dies from underpopulation." );
   printf( "\n- A live cell with two or three live neighbours lives to the next generation." );
   printf( "\n- A live cell with more than three live neighbours dies from overpopulation." );
   printf( "\n- A space surrounded by three cells becomes a live cell from reproduction.\n" );

   printf( "\nStarting population density as a percentage? " );
   density = getnum();

   if( (density < 1) || (density > 100) ) {
      puts( "\nWhat?  Density must be between 1% and 99%.  Setting to 33%." );
      density = 33;
   }

   printf( "\nNumber of generations (0 to loop forever) ? " );
   generations = getnum();

   if( generations < 0 ) {
      generations = 0;
   }
 
   printf( "\n\nTerminal type, ASCII (0) or ANSI (1) ? " );
   term = getnum();

   if(( term < 0 ) || (term > 1)) {
      printf ( "\nDefaulting to ASCII.");
      term = 0;
   }

   printf("\n");
   seed();
   
     if (term == 1){
      printf("\033[2J");   /* ANSI - Clear screen */
   }
   
 
   while( (generations == 0) || (generations > iteration) ) {
     if (term == 1){
        printf("\033[H");    /* ANSI - Home */
     } else {
        cls();
     }
      
      iteration++;
      if( cycle ) {
         display(2);
         generate(1);
         cycle = 0;
      } else {
         display(1);
         generate(2);
         cycle = 1;
      }
   }

   return(0);
}
Last edited by BillO on July 8th, 2022, 7:34 pm, edited 1 time in total.
BillO
 
Posts: 100
Joined: November 11th, 2020, 6:29 am

Re: Conway's Life Program

Postby Wayne Parham » July 8th, 2022, 7:26 pm

Nice! Super groovy cool!
Wayne Parham
 
Posts: 94
Joined: March 18th, 2022, 3:01 pm

Re: Conway's Life Program

Postby KenF » July 12th, 2022, 6:11 pm

Do you have a pointer to a good source for BDS C for CP/M? I have downloaded about 5 copies from various archives and all have different byte sizes, different files in their archives, and don't work. Two of them even have a CC file that is larger than 64k even though they are labeled for CP/M 2.x. Those obviously don't fit.

Started looking for a good C compiler for 2.2, but no luck so far. Apparently, you have found one.

Thanks.
Ken
KenF
 
Posts: 14
Joined: April 25th, 2022, 11:13 am

Re: Conway's Life Program

Postby AltairClone » July 12th, 2022, 6:13 pm

AltairClone
Site Admin
 
Posts: 546
Joined: April 5th, 2013, 10:55 am

Re: Conway's Life Program

Postby toml_12953 » July 12th, 2022, 9:27 pm

BillO wrote:I hope you don't mind Wayne, but I added a tiny bit of code for ANSI terminal support.

It allows you to see the evolution between generations a bit better if you have an ANSI terminal.



This runs great when compiled with gcc in a Windows Command Prompt window! The Command prompt window interprets ANSI codes.
toml_12953
 
Posts: 262
Joined: June 7th, 2013, 12:54 pm

Re: Conway's Life Program

Postby Wayne Parham » July 14th, 2022, 11:22 am

Here's another version that compiles in gcc, and is probably better for use on processors of today. I originally wrote it for cc65, but made it a little more "platform-portable" by having a delay that is set by choosing processor speed using defined "MIPS." You can define it at compile time, or if you don't, the program will ask at run time. This works better on modern machines, which race through thousands of generations in a blink if there's no delay mechanism in the program.

I "back-ported" this code to make the version shown above, which is compatible with BDS, being more of a K&R-style C. Not too many changes were required, but I did remove the MIPS code, 'cause we know what it is for the Altair. No delay needed on the 8-bit machines.

Code: Select all
// cLife.c
//
// Conway's Life Simulation
//
// Wayne Parham


#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


#ifndef XMAX
#define XMAX   80
#endif

#ifndef YMAX
#define YMAX   22
#endif

#ifndef LINES
#define LINES  24
#endif

#ifndef MIPS
#define MIPS   -1
#endif


int i,j;
int life;
int cycle;
int density;
int iteration;
int generations;
char array1[XMAX][YMAX];
char array2[XMAX][YMAX];


void cls() {       // scroll the screen to make it blank
   for( j=0; j<LINES; j++ ) {
      putchar( '\n' );
   }
}


void delay( int count ) {
   for( j=0; j<count; j++ ) {
   }
}


int getnum() {
   char  input[10];
   char* check;
   int   value;
   if( !fgets(input, sizeof input, stdin) ) {
      value = -1;
   } else {
      if( ! strchr(input, '\n') ) {
         while( fgets(input, sizeof input, stdin) && !strchr(input, '\n') );
      } else {
         value = (int) strtol(input, &check, 10);
         if( ! ( (isspace(*check)) || (*check == 0) ) ) {
            value = -1;
         }
      }
   }

   return( value );
}


void seed() {
   int n;          // Using an uninitialized local int to seed the random number generator
   srand(n);
   for( j=0; j<YMAX; j++ ) {
      for( i=0; i<XMAX; i++ ) {
         life = ( rand() % 100 );
         if( life < density )
            array1[i][j]='o';
         else
            array1[i][j]=' ';
      }
   }
}


void applyRule(int world, int alive, int x, int y) {
   int neighbors=0;
   if( world == 1 ) {
      if( array1[x-1][y]   =='o') neighbors++;
      if( array1[x+1][y]   =='o') neighbors++;
      if( array1[x-1][y+1] =='o') neighbors++;
      if( array1[x][y+1]   =='o') neighbors++;
      if( array1[x+1][y+1] =='o') neighbors++;
      if( array1[x-1][y-1] =='o') neighbors++;
      if( array1[x][y-1]   =='o') neighbors++;
      if( array1[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array2[x][y]=' ';                         // underpopulation
      else if( (alive==1) && (neighbors>3) )  array2[x][y]=' ';                         // overpopulation
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array2[x][y]='o';   // living
      else if( (alive==0) && (neighbors==3) ) array2[x][y]='o';                         // spawned
      else array2[x][y]=' ';
   }
   else if( world == 2 ) {
      if( array2[x-1][y]   =='o') neighbors++;
      if( array2[x+1][y]   =='o') neighbors++;
      if( array2[x-1][y+1] =='o') neighbors++;
      if( array2[x][y+1]   =='o') neighbors++;
      if( array2[x+1][y+1] =='o') neighbors++;
      if( array2[x-1][y-1] =='o') neighbors++;
      if( array2[x][y-1]   =='o') neighbors++;
      if( array2[x+1][y-1] =='o') neighbors++;
      if( (alive==1) && (neighbors<2) )       array1[x][y]=' ';                         // underpopulation
      else if( (alive==1) && (neighbors>3) )  array1[x][y]=' ';                         // overpopulation
      else if( (alive==1) && ((neighbors==2) || (neighbors==3)) )   array1[x][y]='o';   // living
      else if( (alive==0) && (neighbors==3) ) array1[x][y]='o';                         // spawned
      else array1[x][y]=' ';
   }
}


void generate(int world){
   int alive;
   if( world == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array2[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(2,alive,i,j);
         }
      }
   }
   else if( world == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            if( array1[i][j]=='o' ) {
               alive = 1;
            } else {
               alive = 0;
            }
            applyRule(1,alive,i,j);
         }
      }
   }
}


void display(int array){
   if( array == 1 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array1[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
         }
      }
   }
   else if( array == 2 ) {
      for( j=0; j<YMAX; j++ ) {
         for( i=0; i<XMAX; i++ ) {
            printf("%c", array2[i][j]);
            if( i==XMAX-1 )
               printf( "\n" );
    }
      }
   }
   printf( "                              Generation %d\n", iteration );
}


int main() {
   int  mips;
   long hold_time;

   printf( "\n                                      Life\n" );

   printf( "\nThis  is a cellular automaton devised by the British mathematician John  Conway" );
   printf( "\nin  1970.   Its  evolution  is determined by its initial  state,  requiring  no" );
   printf( "\nfurther input.  You will only be asked for initial population density, which is" );
   printf( "\nthen used to create a random distribution of cells.\n" );

   printf( "\nThis  simulation  is  Turing complete and can produce and contain  a  universal" );
   printf( "\nconstructor or any other Turing machine.\n" );

   printf( "\nThe universe is a two-dimensional orthogonal grid, with each position in one of" );
   printf( "\ntwo possible states, live or dead, or populated and unpopulated,  respectively.\n" );

   printf( "\nEvery  cell interacts with its eight neighbours,  which are the cells that  are" );
   printf( "\nhorizontally,  vertically, or diagonally adjacent.   The following  transitions" );
   printf( "\noccur at each iteration, definining a generation:\n" );

   printf( "\n- A live cell with fewer than two live neighbours dies from underpopulation." );
   printf( "\n- A live cell with two or three live neighbours lives to the next generation." );
   printf( "\n- A live cell with more than three live neighbours dies from overpopulation." );
   printf( "\n- A space surrounded by three cells becomes a live cell from reproduction.\n" );

   if( MIPS >= 0 ) {   // Compiling with -DMIPS=<cpu_mips> will omit the cpu speed question
      mips = MIPS;
   } else {
      printf( "\nProcessor speed (in MIPS)? " );
      mips = getnum();
   }

   if( mips >= 1 )
      hold_time = 250000 * mips;   // 1/4 second display for each generation
   else
      hold_time = 0;               // low-speed processors don't need delay

   printf( "\nStarting population density as a percentage? " );
   density = getnum();

   if( (density < 1) || (density > 100) ) {
      puts( "\nWhat?  Density must be between 1% and 99%.  Setting to 33%." );
      density = 33;
   }

   printf( "\nNumber of generations (0 to loop forever) ? " );
   generations = getnum();

   if( generations < 0 )
      generations = 0;

   seed();

   while( (generations == 0) || (generations > iteration) ) {

      cls();      
      iteration++;
      if( cycle ) {
         display(2);
         generate(1);
         cycle = 0;
      } else {
         display(1);
         generate(2);
         cycle = 1;
      }
      if( hold_time )
         delay( hold_time );
   }

   return(0);
}

Wayne Parham
 
Posts: 94
Joined: March 18th, 2022, 3:01 pm

Re: Conway's Life Program

Postby Wayne Parham » August 10th, 2022, 10:31 am

Quick note:

Not sure anyone would care to "benchmark" old 8-bit systems in 2022, but I noticed that the "Life Program" listed here - and built on BDS C - runs faster on an 8800c than it does on a Synertek System's Sym-1. That's basically comparing the 2Mhz 8080 with the 1Mhz 6502.

The compilers come into play too - BDS C versus CC65 - and that may be significant but whatever the case, the Life program on the Altair runs about 15% faster on the 8800c than it does on a Sym-1.
Wayne Parham
 
Posts: 94
Joined: March 18th, 2022, 3:01 pm


Return to General Discussions

Who is online

Users browsing this forum: No registered users and 1 guest