001package edu.pdx.cs410J.whitlock;
002
003import com.google.common.annotations.VisibleForTesting;
004
005import java.io.*;
006
007/**
008 * This class is represents a <code>Student</code>.                                 
009 */                                                                                 
010public class GameOfLife {
011
012  static final char DEAD_CELL = '.';
013  private static final char ALIVE_CELL = '*';
014
015  private char[][] grid;
016  private int nextUnpopulatedRow;
017  private final int rowCount;
018  private final int columnCount;
019
020  public GameOfLife(int rowCount, int columnCount) {
021    this.grid = new char[rowCount][columnCount];
022    this.nextUnpopulatedRow = 0;
023    this.rowCount = rowCount;
024    this.columnCount = columnCount;
025  }
026
027  /**
028   * Main program that parses the command line, creates a
029   * <code>Student</code>, and prints a description of the gameoflife to
030   * standard out by invoking its <code>toString</code> method.
031   */
032  public static void main(String[] args) throws InterruptedException, IOException {
033    if (args.length == 0) {
034      System.err.println("** Missing file name");
035      System.exit(1);
036    }
037
038    String fileName = args[0];
039    GameOfLife game = readGameFromFile(new File(fileName));
040
041    while (gameHasLiveCells(game)) {
042      printGame(game);
043      game.computeNextGeneration();
044      Thread.sleep(1000);
045    }
046  }
047
048  @VisibleForTesting
049  static boolean gameHasLiveCells(GameOfLife game) {
050    for (int row = 0; row < game.getRowCount(); row++) {
051      if (rowContainAliveCell(game, row) != -1) {
052        return true;
053      }
054    }
055
056    return false;
057  }
058
059  private static int rowContainAliveCell(GameOfLife game, int row) {
060    return game.getRow(row).indexOf(GameOfLife.ALIVE_CELL);
061  }
062
063  private static void printGame(GameOfLife game) {
064    for (int row = 0; row < game.getRowCount(); row++) {
065      System.out.println(game.getRow(row));
066    }
067
068    System.out.println();
069  }
070
071  private static GameOfLife readGameFromFile(File file) throws IOException {
072    return parseTextFormat(new FileReader(file));
073  }
074
075  void addRow(String row) {
076    this.grid[this.nextUnpopulatedRow] = row.toCharArray();
077    this.nextUnpopulatedRow++;
078  }
079
080  void computeNextGeneration() {
081    char[][] newGrid = new char[this.rowCount][this.columnCount];
082
083    for (int row = 0; row < this.rowCount; row++) {
084      newGrid[row] = new char[this.columnCount];
085
086      for (int column = 0; column < this.columnCount; column++) {
087        newGrid[row][column] = getNextGenerationCell(row, column);
088      }
089    }
090
091    this.grid = newGrid;
092  }
093
094  private char getNextGenerationCell(int row, int column) {
095    char cell = getCell(row, column);
096    int numberOfLiveNeighbors = getNumberOfLiveNeighbors(row, column);
097    if (cell == ALIVE_CELL) {
098      if (numberOfLiveNeighbors < 2) {
099        return DEAD_CELL;
100
101      } else if (numberOfLiveNeighbors > 3) {
102        return DEAD_CELL;
103
104      } else if (numberOfLiveNeighbors == 2 || numberOfLiveNeighbors == 3){
105        return ALIVE_CELL;
106      }
107
108    } else {
109      assert cell == DEAD_CELL;
110
111      if (numberOfLiveNeighbors == 3) {
112        return ALIVE_CELL;
113      }
114    }
115    return DEAD_CELL;
116  }
117
118  @VisibleForTesting
119  int getNumberOfLiveNeighbors(int row, int column) {
120    int numberLiveNeighbors = 0;
121
122    for (int i = row - 1; i <= row + 1; i++) {
123      for (int j = column - 1; j <= column + 1; j++) {
124        if (!(i == row && j == column)) {
125          if (cellIsAlive(i, j)) {
126            numberLiveNeighbors++;
127          }
128        }
129      }
130    }
131
132    return numberLiveNeighbors;
133  }
134
135  private boolean cellIsAlive(int row, int column) {
136    if (row < 0) {
137      return false;
138
139    } else if (column < 0) {
140      return false;
141
142    } else if (column >= this.columnCount) {
143      return false;
144
145    } else if (row >= this.rowCount) {
146      return false;
147
148    } else {
149      return getCell(row, column) == ALIVE_CELL;
150    }
151  }
152
153  @VisibleForTesting
154  char getCell(int row, int column) {
155    return this.grid[row][column];
156  }
157
158  String getRow(int rowIndex) {
159    return new String(this.grid[rowIndex]);
160  }
161
162  @VisibleForTesting
163  int getRowCount() {
164    return rowCount;
165  }
166
167  @VisibleForTesting
168  static GameOfLife parseTextFormat(Reader reader) throws IOException {
169    BufferedReader br = new BufferedReader(reader);
170
171    int rowCount = Integer.parseInt(br.readLine());
172    int columnCount = Integer.parseInt(br.readLine());
173
174    GameOfLife game = new GameOfLife(rowCount, columnCount);
175    for (int row = 0; row < rowCount; row++) {
176      game.addRow(br.readLine());
177    }
178
179    return game;
180  }
181}