
// Life.java
// Andrew Davison, ad@coe.psu.ac.th, March 2023

/* A version of John Conway's Game of Life.
   https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

   Transition rules:
    1. Any live cell with fewer than two live neighbours dies.

    2. Any live cell with two or three live neighbours lives 
       on to the next generation.

    3. Any live cell with more than three live neighbours dies.

    4. Any dead cell with exactly three live neighbours becomes 
       a live cell.

   Uses Lanterna 3
   https://github.com/mabe02/lanterna

   Usage (Windows):
     > compile.bat Life.java
     > run.bat Life

   Usage (Linux):
     > ./compile.sh Life.java
     > ./run.sh Life

*/


import java.io.*;
import java.util.*;

import com.googlecode.lanterna.*;
import com.googlecode.lanterna.graphics.TextGraphics;
import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.screen.*;
import com.googlecode.lanterna.terminal.*;



public class Life 
{
  private static int numCols, numRows;


  public static void main(String[] args)
  {
    Screen scr = Utils.createScreen();
    scr.setCursorPosition(null);
    
    TerminalSize termSz = scr.getTerminalSize();
    numCols = termSz.getColumns();
    numRows = termSz.getRows();
    
    int[][] grid = new int[numRows][numCols];
    int[][] prevGrid = new int[numRows][numCols];
  
    // defines an inverted U
    grid[15][39] = 1;
    grid[15][40] = 1;
    grid[15][41] = 1;
    grid[16][39] = 1;
    grid[17][39] = 1;
    grid[16][41] = 1;
    grid[17][41] = 1;
  
    // a block
    /*
    grid[13][37] = 1;
    grid[14][37] = 1;
    grid[13][38] = 1;
    grid[14][38] = 1;
    */

    while (true) {
      KeyStroke ks = Utils.pollInput(scr);
      if (Utils.hasQuit(ks))
        break;

      display(scr, grid);
      update(prevGrid, grid);
      Utils.pause(150);
    }
    
    Utils.close(scr);
  }  // end of main()


  private static void display(Screen scr, int[][] grid)
  // live grid cells are drawn in white
  { 
    Utils.fill(scr, TextColor.ANSI.BLACK);
    for (int i = 0; i < numRows; i++) {
      for (int j = 0; j < numCols; j++)
        if (grid[i][j] == 1)
          Utils.setCell(scr, j, i, TextColor.ANSI.WHITE_BRIGHT);
    }
    Utils.refresh(scr);
  }


  private static void update(int[][] prevGrid, int[][] grid)
  { 
    // backup grid[] into prevGrid[]
    for (int i = 0; i < numRows; i++)
      for (int j = 0; j < numCols; j++)
        prevGrid[i][j] = grid[i][j];

    // calculate the changes on prevGrid[], and store
    // the new values in grid[]
    for (int i = 0; i < numRows; i++)
      for (int j = 0; j < numCols; j++)
        transition(prevGrid, grid, i, j);
  }  // end of update()


  private static void transition(int[][] prevGrid, int[][] grid, 
                                   int i, int j)
  {  
    // previous and next cells implement wrap-around on the grid
    int iPrev = (i-1+numRows) % numRows;
    int iNext = (i+1) % numRows;
    int jPrev = (j-1+numCols) % numCols;
    int jNext = (j+1) % numCols;

    int numNeighbours  = 
      prevGrid[iPrev][j] +
      prevGrid[iPrev][jPrev] +
      prevGrid[iPrev][jNext] +
      prevGrid[iNext][j] +
      prevGrid[iNext][jPrev] +
      prevGrid[iNext][jNext] +
      prevGrid[i][jPrev] +
      prevGrid[i][jNext];
  
    // apply the transition rules
    int nState = 0; // default is death: rules 1 and 3
    if ((prevGrid[i][j] == 1) && (numNeighbours == 2 || 
                                  numNeighbours == 3))
       nState = 1;  // rule 2
    else if ((prevGrid[i][j] == 0) && (numNeighbours == 3))
       nState = 1;  // rule 4

    grid[i][j] = nState;
  }  // end of transition()


} // end of Life class
