*****Listing 5*****

/*
 *	life.c
 *	copyright 1985,1988 Ronald Florence
 *
 *      compile: cc -O -s life.c keys.c -lcurses -ltermcap -o life
 */


#include	<curses.h>
#include	<signal.h>
#ifndef KEY_DOWN
#include	"keys.h"
#endif

#define ESC		0x1b
#define	life		'@'
#define	crowd		(life + 4)
#define	lonely		(life + 2)
#define	birth		(' ' + 3)
#define	minwrap(a,d)	a = --a < 0 ? d : a
#define	maxwrap(a,d)	a = ++a > d ? 0 : a
#define wrap(a,z)	if (a < 0) (a) += z;		\
			else if (a > z) (a) = 1;	\
			else if (a == z) (a) = 0
#define	MAXX		(COLS-1)
#define	MAXY		(LINES-3)
#define	boredom		5

typedef struct node	
{
  int	y, x;
  struct node	*prev, *next;
}  LIFE;

struct	
{
  int	y, x;
} pos[8] = {	{ 1,-1}, { 1, 0}, { 1, 1}, { 0, 1},
		{-1, 1}, {-1, 0}, {-1,-1}, { 0,-1}	
	   };

LIFE	*head, *tail;

extern	char	*malloc();
char	
  *rules[] = {
    " ",
    "The Rules of Life:",
    " ",
    "   1. A cell with more than three neighbors dies of overcrowding.",
    "   2. A cell with less than two neighbors dies of loneliness.",
    "   3. A cell is born in an empty space with exactly three neighbors.",
    " ",
    0
    },
  
  *rules2[] = {
    "Use the arrow keys or the vi cursor keys",
    "(H = left, J = down, K = up, L = right)",
    "to move the cursor around the screen.",  
    "The spacebar creates and destroys life.",
    "<Esc> starts the cycle of reproduction.",
    "<Del> ends life.", 
    " ",
    "Press any key to play The Game of Life.",
    0
    };


main(ac, av)
     int	ac;
     char	**av; 
{
  int	i = 0, k, die(); 
		
  initscr();
  crmode();
  noecho();
  signal(SIGINT, die);
  lookupkeys();
  head = (LIFE *)malloc(sizeof(LIFE));
  tail = (LIFE *)malloc(sizeof(LIFE)); /* lest we have an unanchored pointer */
  head->next = tail;
  tail->prev = head;
  
  if (ac > 1) 
    readfn(*++av);
  else
    {
      erase();
      if (COLS > 40) 
	for ( ; rules[i]; i++)
	  mvaddstr(i+1, 0, rules[i]);
      for (k = 0; rules2[k]; k++)
	mvaddstr(i+k+1, 0, rules2[k]);
      refresh();
      while (!getch())
	;
      setup();
    }
  nonl();
  while (TRUE)
    {
      display();  
      mark_life();
      update();
    }
}


die()
{
  signal(SIGINT, SIG_IGN);
  move(LINES-1, 0);
  refresh();
  endwin();
  exit(0);
}


kill_life(ly, lx)
     register  int	ly, lx;
{
  register  LIFE	*lp;
  
  for (lp = head->next; lp != tail; lp = lp->next)
    if (lp->y == ly && lp->x == lx)  
      {
	lp->prev->next = lp->next;
	lp->next->prev = lp->prev;
	free(lp);
	break;
      }
}


display()
{
  int		pop = 0;
  static int	gen, oldpop, boring;
  char	c;
  register  LIFE  *lp;

  erase();
  for(lp = head->next; lp != tail; lp = lp->next) 
    {
      mvaddch(lp->y, lp->x, life);
      pop++;
    }  
  if (pop == oldpop)
    boring++;
  else 
    {
      oldpop = pop;
      boring = 0;
    }
  move(MAXY+1, 0);
  if (!pop)  
    {
      printw("Life ends after %d generations.", gen);
      die();
    }
  printw("generation - %-4d", ++gen);
  printw(" population - %-4d", pop);
  refresh();
  if (boring == boredom) 
    {
      mvprintw(MAXY, 0, "Population stable.  Abort? ");
      refresh();
      while (!(c = getch()))
	;
      if (toupper(c) == 'Y')
	die();
    }
}


mark_life()
{
  register  k, ty, tx;
  register  LIFE  *lp;	

  for (lp = head->next; lp; lp = lp->next)  
    for (k = 0; k < 8; k++) 
      {
	ty = lp->y + pos[k].y; 
	wrap(ty, MAXY);
	tx = lp->x + pos[k].x; 
	wrap(tx, MAXX); 
	stdscr->_y[ty][tx]++;
      }
}


update()
{
  register  int	i, j, c;

  for (i = 0; i <= MAXY; i++)
    for (j = 0; j <= MAXX; j++) 
      {
	c = stdscr->_y[i][j];
	if (c >= crowd || c >= life && c < lonely) 
	  kill_life(i, j);
	else if (c == birth) 
	  newlife(i, j);
      }
}
		
			
setup()
{
  int	x, y, c, start = 0;

  erase();
  y = MAXY/2;
  x = MAXX/2;
  while (!start) 
    {
      move(y, x);	
      refresh();
      switch (c = getkey())  
	{ 
	case 'h' : 
	case 'H' :
	case ('H' - '@'):
	case KEY_LEFT:
	case KEY_BACKSPACE:
	  minwrap(x, MAXX);
	  break;
	case 'j' :
	case 'J' :
	case ('J' - '@'):
	case KEY_DOWN:
	  maxwrap(y, MAXY);
	  break;
	case 'k' :
	case 'K' :
	case ('K' - '@'):
	case KEY_UP:
	  minwrap(y, MAXY);
	  break;
	case 'l' :
	case 'L' :
	case ('L' - '@'):
	case KEY_RIGHT:
	  maxwrap(x, MAXX);
	  break;
	case ' ' :
	  if (inch() == life)  
	    {
	      addch(' ');
	      kill_life(y, x);
	    }
	  else 
	    {
	      addch(life);
	      newlife(y, x);
	    }
	  break;
	case 'q' :
	case 'Q' :
	case ESC :
	  ++start;
	  break;
	}
    }  
}


newlife(ny, nx)
     int	ny, nx;
{
  LIFE	*new;
  
  new = (LIFE *)malloc(sizeof(LIFE));
  new->y = ny;
  new->x = nx;
  new->next = head->next;
  new->prev = head;
  head->next->prev = new;
  head->next = new;
}


readfn(f)
     char	*f;
{
  FILE	*fl;
  int	y, x;

  if ((fl = fopen(f, "r")) == NULL) 
    errx("usage: life [file (line/col pts)]\n", NULL);
  while (fscanf(fl, "%d%d", &y, &x) != EOF) 
    {
      if (y < 0 || y > MAXY || x < 0 || x > MAXX) 
	errx("life: invalid data point in %s\n", f);
      mvaddch(y, x, life);
      newlife(y, x);
    }
  fclose(fl);
  
}


errx(m,d)
     char	*m, *d;
{
  fprintf(stderr, m, d);
  endwin();
  exit(0);
}
