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

#define COLS 80
#define ROWS 25

#define DEAD ' '
#define ALIVE '#'

#define INIT_RATIO 4

#define STR(x) _STR (x)
#define _STR(x) #x

static uint16_t xss; /* xs() state */

static uint16_t
xs () /* xorshift prng */
{
  xss ^= (xss << 1);
  xss ^= (xss >> 3);
  xss ^= (xss << 10);

  return xss;
}

static void
clearscr ()
{
  printf ("\e[1;1H\e[2J");
}

static void
print_row (char row[COLS])
{
  printf ("%." STR (COLS) "s\n", row);
}

static void
print_grid (char grid[COLS * ROWS])
{
  for (int i = 0; i < ROWS; i++)
    print_row (grid + i * COLS);
}

static void
init_grid (char grid[COLS * ROWS])
{
  for (int i = 0; i < COLS * ROWS; i++)
    {
      if (xs () % INIT_RATIO)
        grid[i] = DEAD;
      else
        grid[i] = ALIVE;
    }
}

static char
get_state (char grid[COLS * ROWS], int row, int col)
{
  if (row < 0 || row >= ROWS || col < 0 || col >= COLS)
    return DEAD;

  return grid[row * COLS + col];
}

static int
alive_neighbours (char grid[COLS * ROWS], int idx)
{
  int row = idx / COLS;
  int col = idx % COLS;
  int neighbours = 0;

  for (int i = -1; i <= 1; i++)
    for (int j = -1; j <= 1; j++)
      if (get_state (grid, row + i, col + j) == ALIVE)
        neighbours++;

  if (get_state (grid, row, col) == ALIVE)
    neighbours--;

  return neighbours;
}

static char
next_state (char grid[COLS * ROWS], int idx)
{
  int n = alive_neighbours (grid, idx);

  if (get_state (grid, idx / COLS, idx % COLS) == ALIVE)
    {
      if (n < 2 || n > 3)
        return DEAD;

      return ALIVE;
    }

  if (n == 3)
    return ALIVE;

  return DEAD;
}

static void
update_grid (char grid[COLS * ROWS])
{
  char new[COLS * ROWS];

  for (int i = 0; i < COLS * ROWS; i++)
    new[i] = next_state (grid, i);

  memcpy (grid, new, COLS * ROWS);
}

int
main ()
{
  char grid[COLS * ROWS];

  xss = time (0);
  init_grid (grid);

  do
    {
      clearscr ();
      print_grid (grid);
      update_grid (grid);
    }
  while (getchar () != 'q');

  return 0;
}
