
// GameOfLife
// Simon Patrzalek, Mads Bornebusch and Kristian Lauszus
// Modified by Andre Davison, ad@coe.psu.ac.th, Nov. 2022


/* Conway's Game of Life
   https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life

   Many implementations:
   https://rosettacode.org/wiki/Conway%27s_Game_of_Life
*/

import java.io.*;
import java.util.*;


public class GameOfLife
{
  private static final int GRID_SIZE = 100;
  private static final int NUM_ITERS = 500;

  private static Random random = new Random();
  private static int[][] grid;


  public GameOfLife()
  {  grid = makeGrid(GRID_SIZE);  }


  public GameOfLife(String fnm)
  {  grid = loadGrid(fnm);  }


  private int[][] loadGrid(String fnm) 
  // Create an nxn array golding the initial state.
  {
    List<Integer> list = loadState(fnm);
    if (list.size() == 0)
      return makeGrid(GRID_SIZE);
    else {
      int n  = (int) Math.sqrt(list.toArray().length);
      System.out.println("Making a grid of size " + n + "x" + n);
      int[][] grid = new int[n][n];      
      int i = 0;
      for (int y = 0; y < n; y++) {
        for (int x = 0; x < n; x++) {
          grid[y][x] = list.get(i);
          i++;
        }
      }
      // System.out.println("Loaded grid: \n" + toString(grid) + "\n");
      return grid;
    }
  } /* loadGrid */



  private ArrayList<Integer> loadState(String fnm) 
  // Load a file containing the initial state as a list
  {
    ArrayList<Integer> states = new ArrayList<>();
    try {
      BufferedReader br = new BufferedReader(
                            new FileReader(new File(fnm))); 
      String line;
      int colSize = -1;
      int rowSize = 0;
      while ((line = br.readLine()) != null) {
        System.out.println(line);
        rowSize++;
        if (colSize == -1)
          colSize = line.length();
        else {
          if (line.length() != colSize)
            System.out.println("Mismatched column size in row " + rowSize);
        }
        for(char ch : line.toCharArray()) {
          if (ch == '1')
            states.add(1);
          else
            states.add(0);
        }
      }
      if (rowSize != colSize)
        System.out.println("Mismatched column and row sizes: " +
                                  colSize + " != " + rowSize);
      br.close();
      System.out.println("\nLoaded " + fnm);
    }
    catch(Exception e)
    {  System.out.println("\nCould not load " + fnm);  }

    return states;
  }  // end of loadState()



  private int[][] makeGrid(int n)
  {
    System.out.println("Making a random grid of size " + n + "x" + n);

    int[][] grid = new int[n][n];
    for (int y = 0; y < n; y++) {
      for (int x = 0; x < n; x++) {
        grid[y][x] = random.nextInt(2); 
                        // a value from 0-1
      }
    }
    return grid;
  } // end of makeGrid()


  public int getSize()
  {  return grid[0].length;  }


  public void next()
  {
    int n = grid[0].length;
    int[][] nxtGrid = new int[n][n];

    for (int y = 0; y < n; y++) {
      for (int x = 0; x < n; x++) {
        int num = numNeighbours(x,y);
        if ((num < 2) || (num > 3))
          nxtGrid[y][x] = 0;
        else if (num == 3)
          nxtGrid[y][x] = 1;
        else
          nxtGrid[y][x] = grid[y][x];
      }
    }
    grid = nxtGrid;
  } /* next */



  public int numNeighbours(int x, int y)
  // count how many neighbours a point has
  {
    int n = grid[0].length;
    int num = 0;
    for (int i = -1; i <= 1; i++) {
      int corY = Math.floorMod(y+i, n);
      for (int j = -1; j <= 1; j++) {
        int corX = Math.floorMod(x+j, n);
        if ((corX != x) || (corY != y)) {
          if (grid[corY][corX] == 1)
            num++;
        }
      }
    }
    return num;
  } /* numNeighbours */


  public void draw(String label)
  {
    StdDraw.clear();

    int n = grid[0].length;
    for (int y = 0; y < n; y++) {
      for (int x = 0; x < n; x++) {
        if (grid[n-1-y][x] == 1)
          StdDraw.point(x, y);
      }
    }
    StdDraw.setPenColor(StdDraw.RED);
    StdDraw.text(5, 0, label);
    StdDraw.setPenColor(StdDraw.BLACK);

    StdDraw.show();
    StdDraw.pause(100);
  }  // end of draw()


  public String toString()
  {  return toString(grid);  }


  private String toString(int[][] grid)
  {
    int n = grid[0].length;
    StringBuilder sb = new StringBuilder();
    for(int y = 0; y < n; y++) {
      for(int x = 0; x < n; x++)
        sb.append(grid[y][x] + " ");
      sb.append("\n");
    }
    return sb.toString();
  } /* toString */


  // -------------------------------------------------


  public static void main(String[] args) 
  {
    GameOfLife gol;
    if (args.length == 0)
      gol = new GameOfLife(); 
    else if (args.length == 1)
      gol = new GameOfLife(args[0]); 
    else {
      System.out.println("Usage: run GameOfLife [<fnm>]");
      return;
    }

    // System.out.println("Initial Grid: ");
    // System.out.println(gol);

    int n = gol.getSize();
    StdDraw.setCanvasSize(700, 700); 
    StdDraw.setScale(-1, n); 
    StdDraw.setPenRadius(1.0/(double)n);

    StdDraw.enableDoubleBuffering();

    for(int i = 1; i <= NUM_ITERS; i++) {
      gol.draw("Step " + i);
      gol.next();
    }
  } /* main */


}  // end of GameOfLife class