Randomized Life Program

by Dave Aronson

/* randlife.c -- randomized version of the famous "life" screensaver */
/* Copyright 1997, David J. Aronson */


/** INCLUDES **/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>


/** OTHER DEFINES **/

/* maximum allowable width & height of world */
#define MAXWID  255
#define MAXHT   255

/* chars that represent dead/alive-ness */
#define DEAD   ' '
#define ALIVE  '@'


/** TYPES **/

typedef unsigned char bool;
typedef unsigned int uint;


/** SUPPORT ROUTINE PROTOTYPES **/

static void AllocMaps (char **newMap, char **oldMap, uint size);

static void Generate
(
   char  *newMap,
   char  *oldMap,
   uint  *birthChances,
   uint  *survChances,
   uint   xSize,
   uint   ySize,
   bool   xWrap,
   bool   yWrap
);

static void GiveUsage (uint *birthChances, uint *survChances);

static uint InitMaps
(
   char  **newMap, 
   char  **oldMap,
   uint   *xSize,
   uint   *ySize,
   int     initChance,
   char   *fileName
);

static void ParseArgs
(
   uint   argc,
   char  *argv[],
   uint  *birthChances,
   uint  *survChances,
   uint  *gens,
   char  *fileName,
   int   *initChance,
   bool  *pause,
   uint  *xSize,
   uint  *ySize,
   bool  *xWrap,
   bool  *yWrap
);

static uint rnd100 (void);

static uint ShowGen
(
   char  *newMap,
   uint   xSize,
   uint   ySize,
   uint   gen,
   uint   gens,
   uint   totCells
);


/** MAIN ROUTINE **/

void main (uint argc, char *argv[])
{
   uint   alive;
   uint   birthChances[] = { 0, 12, 14,  17,  20,  25, 33, 50, 100 };
   char   fileName[256] = "";
   uint   gen = 0;
   uint   gens = 100;
   int    i;
   int    initChance = 25;  /* probability of starting alive (dead if -) */
   char  *newMap;
   char  *oldMap;
   bool   pause = FALSE;
   uint   survChances[]  = { 0, 10, 30, 100, 100, 100, 30, 10,   0 };
   char  *tmpPtr;
   uint   totCells;
   uint   xSize = 79;       /* width of world */
   bool   xWrap = FALSE;    /* is world "closed" on the x-axis? */
   uint   ySize = 24;       /* height of world */
   bool   yWrap = FALSE;    /* is world "closed" on the y-axis? */

   /* parse args */
   ParseArgs (argc, argv, birthChances, survChances, &gens, fileName,
	      &initChance, &pause, &xSize, &ySize, &xWrap, &yWrap);

   /* seed random number generator */
   srandom (time (NULL));

   /* init maps and set total # of cells */
   totCells = InitMaps (&newMap, &oldMap, &xSize, &ySize, initChance, fileName);

   /* clear screen */
   printf ("%c[H%c[J", 27, 27);

   /* for each generation */
   while (gen++ != gens || ! gens)
   {
      /* show it and get # of cells alive */
      alive = ShowGen (newMap, xSize, ySize, gen, gens, totCells);

      /* if all dead, STOP! */
      if (0 == alive) break;

      /* swap maps */
      tmpPtr = newMap;
      newMap = oldMap;
      oldMap = tmpPtr;

      /* generate new map from old */
      Generate (newMap, oldMap, birthChances, survChances,
		xSize, ySize, xWrap, yWrap);

      /* if we're supposed to pause, AND this isn't the last generation */
      if (pause && (gen != gens || ! gens))
      {
         /* tell user to press enter */
         printf (" Press ENTER to continue: %c[K", 27);

         /* look for enter... but parse other chars as commands! */
         do
         {
            i = getchar();
            switch (i)
            {
               /* clear */
               case 'c':
                  memset (newMap, xSize * ySize, DEAD);
               break;

               /* fill */
               case 'f':
                  memset (newMap, xSize * ySize, ALIVE);
               break;

               /* invert */
               case 'i':
                  for (i = xSize * ySize - 1; i >= 0; --i)
                     newMap[i] = (DEAD == newMap[i]) ? ALIVE : DEAD;
               break;

               /* loop -- infinite # of gens, AND turn pause off */
               case 'l':
                  gens = 0;
               /* NO BREAK here; want to fall thru to p */

               /* pause no more */
               case 'p':
                  pause = FALSE;
               break;

               /* quit */
               case 'q':
                  gen = gens = 1;
               break;
            }
         } while (i != '\n');
      /* end if pausing and not last generation */
      }
   /* end for each generation */
   }

   /* free all space we allocated */
   free (newMap);
   free (oldMap);
}



/* allocate old and new maps, of a given size */

static void AllocMaps (char **newMap, char **oldMap, uint size)
{
   /* allocate current and old maps */
   *newMap = malloc (size);
   if (NULL == *newMap)
   {
      puts ("Could not allocate space for current map!");
      exit (1);
   }

   *oldMap = malloc (size);
   if (NULL == *oldMap)
   {
      puts ("Could not allocate space for temp map!");
      free (*newMap);
      exit (1);
   }
}



/* generate a new generation from the old one */

static void Generate
(
   char  *newMap,
   char  *oldMap,
   uint  *birthChances,
   uint  *survChances,
   uint   xSize,
   uint   ySize,
   bool   xWrap,
   bool   yWrap
)
{
   uint  neighbors, x, xLeft, xRight, y, yDown, yUp;

   /* for each spot in current map */
   for (y = 0; y < ySize; y++) for (x = 0; x < xSize; x++)
   {
      /** see what x and y coords we need for the neighbors **/

      /* if we're not on the left edge, left is to the left */
      if (x > 0) xLeft = x - 1;
      /* else if we're wrapping horizontally, it's the far right */
      else if (xWrap) xLeft = xSize - 1;
      /* else it's invalid */
      else xLeft = MAXWID;

      /* follow same logic for right, up, and down */
      if (x < xSize - 1) xRight = x + 1;
      else if (xWrap) xRight = 0;
      else xRight = MAXWID;

      if (y > 0) yUp = y - 1;
      else if (yWrap) yUp = ySize - 1;
      else yUp = MAXHT;

      if (y < ySize - 1) yDown = y + 1;
      else if (yWrap) yDown = 0;
      else yDown = MAXHT;

      /* now count them -- first init count to 0 */
      neighbors = 0;

      /* if we can count the ones on the left */
      if (MAXWID != xLeft)
      {
	 /* count the ones with valid y values */
	 if (MAXHT != yUp)
	 {
	    if (ALIVE == oldMap [yUp * xSize + xLeft]) ++neighbors;
	 }
	 if (ALIVE == oldMap [y * xSize + xLeft]) ++neighbors;
	 if (MAXHT != yDown)
         {
	    if (ALIVE == oldMap [yDown * xSize + xLeft]) ++neighbors;
	 }
      }

      /* apply same logic to x proper (guaranteed valid) and right */
      if (MAXHT != yUp)
      {
	 if (ALIVE == oldMap [yUp * xSize + x]) ++neighbors;
      }
      if (MAXHT != yDown)
      {
	 if (ALIVE == oldMap [yDown * xSize + x]) ++neighbors;
      }
      if (MAXWID != xRight)
      {
	 /* count the ones with valid y values */
	 if (MAXHT != yUp)
	 {
	    if (ALIVE == oldMap [yUp * xSize + xRight]) ++neighbors;
	 }
	 if (ALIVE == oldMap [y * xSize + xRight]) ++neighbors;
	 if (MAXHT != yDown)
	 {
	    if (ALIVE == oldMap [yDown * xSize + xRight]) ++neighbors;
	 }
      }

      /* if it was alive, see if it survived, else see if it got born */
      if (ALIVE == oldMap [y * xSize + x])
      {
	 if (rnd100() <= survChances[neighbors]) newMap[y * xSize + x] = ALIVE;
	 else newMap[y * xSize + x] = DEAD;
      }
      else
      {
	 if (rnd100() <= birthChances[neighbors]) newMap[y * xSize + x] = ALIVE;
	 else newMap[y * xSize + x] = DEAD;
      }

   /* end for each spot */
   }

/* end Generate() */
}



static void GiveUsage (uint *birthChances, uint *survChances)
{
   uint   u;

   puts   ("\nRandomized Life, v1.0.  Copyright 1997, Dave Aronson\n");
   puts   ("Usage: randlife [options...]\n");
   puts   ("Options:");
   puts   ("-bNC: set birth chance with N neighbors to C percent");
   printf ("      (default is");
   for (u = 0; u <= 8; u++) printf (" %d", birthChances[u]);
   puts   (")");
   puts   ("-gN:  set number of generations to run (default 100)");
   puts   ("-h:   give this help");
   puts   ("-iC:  set each spot's initial chance of life to C percent (default 25)");
   puts   ("-mF:  read initial map from file named F.");
   puts   ("-p:   pause for ENTER between generations");
   puts   ("-sNC: set survival chance with N neighbors to C percent");
   printf ("      (default is");
   for (u = 0; u <= 8; u++) printf (" %d", survChances[u]);
   puts   (")");
   printf ("-xS:  set width to S, an integer from 0 to %d (default 75)\n", MAXWID);
   printf ("-yS:  set height to S, an integer from 0 to %d (default 20)\n", MAXHT);
   puts   ("-X:   turn on x-axis wrap");
   puts   ("-Y:   turn on y-axis wrap");

   exit (1);
}



/* initialize the "current" and temp maps */

static uint InitMaps
(
   char  **newMap, 
   char  **oldMap,
   uint   *xSize,
   uint   *ySize,
   int     initChance,
   char   *fileName
)
{
   int	  c;
   FILE  *file;
   uint   i, total;

   /* if we didn't get an initial map file */
   if ('\0' == fileName[0])
   {
      total = *xSize * *ySize;
      /* allocate space according to command line args */
      AllocMaps (newMap, oldMap, total);

      /* for each spot */
      for (i = 0; i < total; i++)
      {
	 /* if the init chance is that of LIFE, roll for that, else for death */
	 if (initChance > 0)
	 {
	    (*newMap) [i] = (rnd100() <=  initChance) ? ALIVE : DEAD;
	 }
	 else (*newMap) [i] = (rnd100() <=  initChance) ? DEAD : ALIVE;
      }
   }

   /* else */
   else
   {
      /* open the file to read AS TEXT */
      file = fopen (fileName, "rt");
      if (NULL == file)
      {
         printf ("Cannot open initial map file '%s'!\n", fileName);
         exit (1);
      }

      /* read the x and y sizes */
      fscanf (file, "%d %d", xSize, ySize);

      total = *xSize * *ySize;

      /* allocate space */
      AllocMaps (newMap, oldMap, total);

      /* for each char in the file */
      i = 0;
      do
      {
         c = getc (file);

         /* if it's one we care about */
         if (DEAD == c || ALIVE == c)
         {
            /* put it in the map */
            (*newMap) [i++] = (char) c;

            /* if out of room, fake end */
            if (i == total) c = EOF;
         }
      } while (EOF != c);

      /* close file */
      fclose (file);

      /* all remaining spots (if any) are dead */
      if (i < total) memset (newMap + i, total - i, DEAD);
   /* end if we got a file */
   }

   /* return total # of cells */
   return total;

/* end InitMaps() */
}



static void ParseArgs
(
   uint   argc,
   char  *argv[],
   uint  *birthChances,
   uint  *survChances,
   uint  *gens,
   char  *fileName,
   int   *initChance,
   bool  *pause,
   uint  *xSize,
   uint  *ySize,
   bool  *xWrap,
   bool  *yWrap
)
{
   uint  i, what, which;

   /* for each arg */
   for (i = 1; i < argc; i++)
   {
      /* birth chance with N neighbors */
      if (! strncmp (argv[i], "-b", 2))
      {
         which = argv[i][2] - '0';
         if (0 > which || 8 < which)
         {
            puts ("Number of neighbors (N in -bNC) must be 0 to 8!");
            GiveUsage (birthChances, survChances);
         }
         what = atoi (argv[i] + 3);
         if (0 > what || 100 < what)
         {
            puts ("Birth chance (C in -bNC) must be from 0 to 100!");
            GiveUsage (birthChances, survChances);
         }
         birthChances[which] = what;
      }


      /* # generations -- no invalid values possible.  B-> */
      else if (! strncmp (argv[i], "-g", 2)) *gens = atoi (argv[i] + 2);

      /* help */
      else if (! strcmp ("-h", argv[i])) GiveUsage (birthChances, survChances);

      /* initial chance of life */
      else if (! strncmp (argv[i], "-i", 2))
      {
         *initChance = atoi (argv[i] + 2);
         if (100 < *initChance || 0 > *initChance)
         {
            puts ("Initial %% chance of life must be between 0 and 100!");
            GiveUsage (birthChances, survChances);
         }
      }

      /* initial map file */
      else if (! strncmp (argv[i], "-m", 2)) strcpy (fileName, argv[i] + 2);

      /* pause */
      else if (! strcmp (argv[i], "-p")) *pause = TRUE;

      /* survival chance with N neighbors */
      else if (! strncmp (argv[i], "-s", 2))
      {
         which = argv[i][2] - '0';
         if (0 > which || 8 < which)
         {
            puts ("Number of neighbors (N in -sNC) must be 0 to 8!");
            GiveUsage (birthChances, survChances);
         }
         what = atoi (argv[i] + 3);
         if (0 > what || 100 < what)
         {
            puts ("Survival chance (C in -sNC) must be from 0 to 100!");
            GiveUsage (birthChances, survChances);
         }
         survChances[which] = what;
      }

      /* x size */
      else if (! strncmp (argv[i], "-x", 2))
      {
         *xSize = atoi (argv[i] + 2);
         if (*xSize < 0 || *xSize > MAXWID)
         {
            printf ("XSIZE must be between 1 and %d!\n", MAXWID);
            GiveUsage (birthChances, survChances);
         }
      }

      /* y size */
      else if (! strncmp (argv[i], "-y", 2))
      {
         *ySize = atoi (argv[i] + 2);
         if (*ySize < 0 || *ySize > MAXHT)
         {
            printf ("YSIZE must be between 1 and %d!\n", MAXHT);
            GiveUsage (birthChances, survChances);
         }
      }

      /* x wrap-toggle */
      else if (! strcmp (argv[i], "-X")) *xWrap = TRUE;

      /* y wrap-toggle */
      else if (! strcmp (argv[i], "-Y")) *yWrap = TRUE;

      /* unrecognized */
      else
      {
         printf ("Unrecognized option: '%s'\n", argv[i]);
         GiveUsage (birthChances, survChances);
      }
   /* end for each arg */
   }
/* end ParseArgs() */
}



static uint rnd100 (void)
{
   return (uint) (random() % 100) + 1;
}



static uint ShowGen
(
   char  *newMap,
   uint   xSize,
   uint   ySize,
   uint   gen,
   uint   gens,
   uint   totCells
)
{
   uint  alive, i, x, y;

   /* go to top of screen */
   printf ("%c[H", 27);

   /* reset alive counter */
   alive = 0;

   /* reset current index */
   i = 0;

   /* for each row in current map */
   for (y = 0; y < ySize; y++)
   {
      /* for each spot in current row */
      for (x = 0; x < xSize; x++, i++)
      {
	 /* display it */
	 putchar (newMap [i]);

	 /* if alive, count it */
	 if (ALIVE == newMap [i]) ++alive;
      }
      /* end row */
      puts ("");
   }

   /* report on conditions */
   printf ("Gen %u/%u (%d/%d = %.1f%% alive)%c[K",
	   gen, gens, alive, totCells, 100.0 * alive / totCells, 27);

   return alive;
}



/* end randlife.c */