// Snowflake pattern generator based on Conway's game of life
//
// Uses a fairly standard 32x32 matrix display (similar to Adafruit version).
//
// If a SPST switch is connected between "Display_Sw" and ground, then you
// can go back and forth between the Game of Life and Snowflake display modes.
// If no switch is added the default display is Snowflake mode.
//
// NOTE: The Game of Life pattern has a tendency to go static so a spaceship
//       of aliens arrives every so often to repopulate the world.
//
//  The code snippets for determining neighbors and "next generation" are borrowed from:
//  https://gist.github.com/markfink/d051d999b8d151d8ad75
//
// Written by Boomer48: January 2021

// Rules
//    Any live cell with fewer than two live neighbors dies (underpopulation)
//    Any live cell with two or three live neighbors lives stays for next generation
//    Any live cell with more than three live neighbors dies (overpopulation)
//    Any dead cell with exactly three live neighbors becomes a live cell (birth)

#define update_delay 200 // Time in usec that LEDs are on each cycle
#define Num_Colors 3 // Maximum = 6

// Definitions for glider construction
#define R2LU 0 // right to left, moving up
#define R2LD 1 // right to left, moving down
#define L2RU 2 // left to right, moving up
#define L2RD 3 // left to right, moving down

unsigned long int newCells[HEIGHT];
byte New_Members_Timer = 0;
byte new_member_time; // Number of generations before new members are added

// Make routines globally visible
void Init_Population();
void Snowflake_Update_Matrix();
void Next_Generation();
byte countNeighbors(byte x, byte y);
boolean isAlive(byte x, byte y);
void Add_New_Members();

//----------------------------------------------------------------------------
void Snowflake_setup() {
  cycle_time = 50;

  if (Game_Num == Life_Display)
    new_member_time = 50;
  else
    new_member_time = 3;

  Init_Population();
}

void Snowflake_loop() {
  Update_Matrix();

  if (Cycle_Timer == cycle_time)
    Next_Generation();
  else
    Cycle_Timer++;
}

void Next_Generation() {
  // Use the GoL rules to calculate the next generation
  for (byte x = 0; x < WIDTH; x++) {
    for (byte y = 0; y < HEIGHT; y++) {
      byte non = countNeighbors(x, y);
      if (non == 3 || (non == 2 && isAlive(x, y)))
        bitSet(newCells[y], x);
      else
        bitClear(newCells[y], x);
    }
  }

  // Copy new array to old
  for (row = 0; row < HEIGHT; row++)
    cells[row] = newCells[row];

  if (New_Members_Timer == new_member_time)
  {
    New_Members_Timer = 0;
    Add_New_Members();
  }
  else
    New_Members_Timer++;

  Cycle_Timer = 0;
  color = (color + 1) % Num_Colors; // Change colors each generation
}

// Count the active neighbors of a given cell
// Assumes edges are neighbors of the opposite edge
byte countNeighbors(byte x, byte y) {
  byte count = 0;
  for (int i = -1; i < 2; i++)
    for (int j = -1; j < 2; j++)
      if (!(i == 0 && j == 0)) {
        int a = x + i;
        int b = y + j;
        if (a == -1)
          a = WIDTH - 1;
        if (b == -1)
          b = HEIGHT - 1;
        if (a == WIDTH)
          a = 0;
        if (b == HEIGHT)
          b = 0;
        if (isAlive(a, b))
          count++;
      }

  return count;
}

boolean isAlive(byte x, byte y) {
  return bitRead(cells[y], x);
}

// Pass in start position of glider and direction of travel
void drawGlider(byte x, byte y, byte dir) {
  switch (dir) {
    case R2LU:
      bitSet(cells[y], x);
      bitSet(cells[y], x - 1);
      bitSet(cells[y], x - 2);
      bitSet(cells[y + 1], x - 2);
      bitSet(cells[y + 2], x - 1);
      break;
    case R2LD:
      bitSet(cells[y], x);
      bitSet(cells[y], x + 1);
      bitSet(cells[y], x + 2);
      bitSet(cells[y + 1], x + 2);
      bitSet(cells[y + 2], x + 1);
      break;
    case L2RU:
      bitSet(cells[y], x);
      bitSet(cells[y], x - 1);
      bitSet(cells[y], x - 2);
      bitSet(cells[y - 1], x - 2);
      bitSet(cells[y - 2], x - 1);
      break;
    case L2RD:
      bitSet(cells[y], x);
      bitSet(cells[y], x + 1);
      bitSet(cells[y], x + 2);
      bitSet(cells[y - 1], x + 2);
      bitSet(cells[y - 2], x + 1);
      break;
  }
}

// Draws Heavyweight Spaceship pattern
// Pass in the starting row (between 4 and 26)
void drawSpaceship(byte y) {
  bitSet(cells[y], 1);
  bitSet(cells[y], 2);
  bitSet(cells[y], 3);
  bitSet(cells[y], 4);
  bitSet(cells[y], 5);
  bitSet(cells[y], 6);
  bitSet(cells[y + 1], 0);
  bitSet(cells[y + 1], 6);
  bitSet(cells[y + 2], 6);
  bitSet(cells[y + 3], 5);
}

void Init_Population() {
  if (Game_Num == Snowflake_Display)
  {
    // Draw 12 gliders that all travel toward the middle
    drawGlider(30, HEIGHT - 4, R2LU);
    drawGlider(1, HEIGHT - 4, R2LD);
    drawGlider(30, 3, L2RU);
    drawGlider(1, 3, L2RD);

    drawGlider(25, HEIGHT - 9, R2LU);
    drawGlider(6, HEIGHT - 9, R2LD);
    drawGlider(25, 8, L2RU);
    drawGlider(6, 8, L2RD);

    drawGlider(20, HEIGHT - 14, R2LU);
    drawGlider(11, HEIGHT - 14, R2LD);
    drawGlider(20, 13, L2RU);
    drawGlider(11, 13, L2RD);
  }
  else // Game of Life
    for (byte row = 0; row < HEIGHT; row++)
    {
      // "random" returns a signed long so the MSB needs to get initialized separately
      cells[row] = random(2147483647);
      bitWrite(cells[row], 31, random(2));
    }
}

void Add_New_Members() {
  if (Game_Num == Snowflake_Display)
    Init_Population(); // Repeat initial pattern
  else // Game of Life
    drawSpaceship(byte(random(4, HEIGHT - 5)));
}
