com.brianscottrussell.gameoflife.GameGrid.java Source code

Java tutorial

Introduction

Here is the source code for com.brianscottrussell.gameoflife.GameGrid.java

Source

package com.brianscottrussell.gameoflife;

/*
 * Copyright (c) 2016 Brian Scott Russell
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;

/**
 * The 2-dimensional grid of Cells used by the game
 *
 * @author brussell
 */
public class GameGrid {

    private static final char DEAD_SYMBOL = '.';
    private static final char ALIVE_SYMBOL = '*';

    private CellStatus[][] grid = new CellStatus[0][0];
    private int rowCount = 0;
    private int colCount = 0;
    private int generation = 1;

    /**
     * Constructor for a grid of size (rowCount x colCount) where all cells are Dead
     *
     * @param rowCount int as # of rows in the grid
     * @param colCount int as # of cols in the grid
     */
    public GameGrid(int rowCount, int colCount) {
        // initialize the grid with the given row & column counts
        initializeGrid(rowCount, colCount);
        this.generation = 1;
    }

    /**
     * Constructor which builds a GameGrid object from the given String
     *
     * if there is an invalid header format then this will initialize the grid as 0x0, empty grid
     * if there is an invalid body format then this will initialize the grid as the header size, all Dead cells
     *
     * @param gridAsString String
     */
    public GameGrid(String gridAsString) {
        // initialize empty grid
        initializeGrid(0, 0);

        if (StringUtils.isNotBlank(gridAsString)) {
            // default the row & col counts to 0
            int rowCount = 0;
            int colCount = 0;

            // get row/col counts from input
            try {
                rowCount = parseRowCountFromInputString(gridAsString);
            } catch (InvalidGameGridInputException e) {
                // TODO: handle error?
                // System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }
            try {
                // get the character(s), before the 1st carriage return as the rowCount
                colCount = parseColumnCountFromInputString(gridAsString);
            } catch (InvalidGameGridInputException e) {
                // TODO: handle error?
                // System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }

            // only need to move forward if we retrieved valid row & col counts
            if (rowCount > 0 && colCount > 0) {
                // initialize grid
                initializeGrid(rowCount, colCount);

                // split the input string so that we're getting all lines of the string as rows, after the first line.
                //   this is the input Grid that will give us the starting point of each cell as Alive or Dead
                String[] gridRows = StringUtils.split(StringUtils.substringAfter(gridAsString, GameOfLife.LF),
                        GameOfLife.LF);
                // iterate over the rows of the grid
                // todo: improve from O(n^2)
                int rowIndex = 0;
                for (String row : gridRows) {
                    // iterate over the chars of the row String
                    int colIndex = 0;
                    for (char symbol : row.toCharArray()) {
                        // check if a valid CellStatus symbol, otherwise skip it.
                        if (CellStatus.isValidCellStatusSymbol(symbol)) {
                            // if the cell is "alive", make it Alive in the CellStatus grid
                            this.grid[rowIndex][colIndex] = CellStatus.getCellStatusBySymbol(symbol);
                            colIndex++;
                        }
                    }
                    rowIndex++;
                }
            }
        }

        // start the generation at 1
        this.generation = 1;
    }

    public int getGeneration() {
        return generation;
    }

    private enum CellStatus {
        Dead(DEAD_SYMBOL), Alive(ALIVE_SYMBOL);

        private char symbol;

        CellStatus(char symbol) {
            this.symbol = symbol;
        }

        public char getSymbol() {
            return symbol;
        }

        /**
         * Simply checks the symbol to see if it matches the Alive symbol.
         *   if so, returns Alive
         *   otherwise returns Dead
         *
         * @param symbol as the symbol to test
         * @return CellStatus as Dead by default
         */
        public static CellStatus getCellStatusBySymbol(char symbol) {
            return symbol == Alive.getSymbol() ? Alive : Dead;
        }

        /**
         * Helper method to test if the symbol is one for the Dead enum
         *
         * @param symbol char as the symbol to test
         * @return boolean true if is the symbol for Dead
         */
        public static boolean isDeadSymbol(char symbol) {
            return symbol == CellStatus.Dead.getSymbol();
        }

        /**
         * Helper method to test if the symbol is one for the Alive enum
         *
         * @param symbol char as the symbol to test
         * @return boolean true if is the symbol for Alive
         */
        public static boolean isAliveSymbol(char symbol) {
            return symbol == CellStatus.Alive.getSymbol();
        }

        /**
         * Helper method to test if the symbol is valid as either a Dead or Alive symbol
         *
         * @param symbol char as the symbol to test
         * @return boolean true if is the symbol for Dead or Alive
         */
        public static boolean isValidCellStatusSymbol(char symbol) {
            return isDeadSymbol(symbol) || isAliveSymbol(symbol);
        }

    }

    /**
     * Sets up the grid as the given size (rowCount * colCount) with all Dead cells
     *
     * @param rowCount int as number of rows in the grid
     * @param colCount int as nunber of columns in the grid
     */
    private void initializeGrid(int rowCount, int colCount) {
        // initialize the grid with the given row & column counts
        this.rowCount = rowCount > 0 ? rowCount : 0;
        this.colCount = colCount > 0 ? colCount : 0;
        // create grid using rowCount & colCount
        this.grid = new CellStatus[this.rowCount][this.colCount];
        // start with a Dead Grid
        killGrid();
    }

    /**
     *  Sets all cells in this GameGrid's grid to the CellStatus Dead
     */
    private void killGrid() {
        if (null != this.grid && grid.length > 0 && this.rowCount > 0 && this.colCount > 0) {
            // iterate through the rows and set each cell to Dead
            for (int y = 0; y < this.rowCount; y++) {
                Arrays.fill(this.grid[y], CellStatus.Dead);
            }
        }
    }

    /**
     * given the grid input String, parses the row count if this is a valid input String
     *
     * Sample of expected format of input String where rowCount = 4:
     *
    4 8
    ......../n
    ....*.../n
    ...**.../n
    ........
     *
     * @param input String
     * @return int as the row count
     * @throws InvalidGameGridInputException
     */
    private int parseRowCountFromInputString(String input) throws InvalidGameGridInputException {
        try {
            // get the character(s), before the 1st space as the rowCount
            return Integer.valueOf(StringUtils.substring(input, 0, StringUtils.indexOf(input, ' ')));
        } catch (NumberFormatException e) {
            throw new InvalidGameGridInputException(e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }

    /**
     * given the grid input String, parses the column count if this is a valid input String
     *
     * Sample of expected format of input String where columnCount = 8:
     *
    4 8
    ......../n
    ....*.../n
    ...**.../n
    ........
     *
     * @param input String
     * @return int as the column count
     * @throws InvalidGameGridInputException
     */
    private int parseColumnCountFromInputString(String input) throws InvalidGameGridInputException {
        try {
            // get the character(s), before the 1st carriage return as the rowCount
            return Integer.valueOf(StringUtils.substring(input, StringUtils.indexOf(input, ' ') + 1,
                    StringUtils.indexOf(input, GameOfLife.LF)));
        } catch (NumberFormatException e) {
            throw new InvalidGameGridInputException(e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  return true if the coordinates exist in this grid
     *  return false if the coordinates do not exist in this grid
     *
     * @param row int
     * @param col int
     * @return boolean
     */
    private boolean isCellInGrid(int row, int col) {
        return (row >= 0 && col >= 0) && (row < this.rowCount && col < this.colCount);
    }

    /**
     * Retrieves the Alive/Dead status of the cell indicated by the coordinates.
     *  if the coordinates are not found in the grid, Dead is returned
     *
     * @param row int
     * @param col int
     * @return CellStatus
     */
    private CellStatus getCellStatus(int row, int col) {
        return isCellInGrid(row, col) ? grid[row][col] : CellStatus.Dead;
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  return true if the current cell is Dead
     *
     * @param row int
     * @param col int
     * @return boolean true if this cell is Dead
     */
    private boolean isCellDead(int row, int col) {
        return getCellStatus(row, col).equals(CellStatus.Dead);
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  return true if the current cell is Alive
     *
     * @param row int
     * @param col int
     * @return boolean true if this cell is Alive
     */
    private boolean isCellAlive(int row, int col) {
        return getCellStatus(row, col).equals(CellStatus.Alive);
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  checks the adjacent cells to count how many are alive
     *  this takes into account that the given coordinate might be at the edge of the grid
     *
     * @param row int
     * @param col int
     * @return int as count of living neighbors
     */
    private int countLivingNeighbors(int row, int col) {

        int livingNeighborCount = 0;

        /**
         * Checks if the upper left cell is alive (O = current cell, # = cell to check)
         #xx
         xOx
         xxx
        */
        if (isCellInGrid(row - 1, col - 1) && isCellAlive(row - 1, col - 1)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the upper cell is alive (O = current cell, # = cell to check)
         x#x
         xOx
         xxx
        */
        if (isCellInGrid(row - 1, col) && isCellAlive(row - 1, col)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the upper right cell is alive (O = current cell, # = cell to check)
         xx#
         xOx
         xxx
        */
        if (isCellInGrid(row - 1, col + 1) && isCellAlive(row - 1, col + 1)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the left cell is alive (O = current cell, # = cell to check)
         xxx
         #Ox
         xxx
        */
        if (isCellInGrid(row, col - 1) && isCellAlive(row, col - 1)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the right cell is alive (O = current cell, # = cell to check)
         xxx
         xO#
         xxx
        */
        if (isCellInGrid(row, col + 1) && isCellAlive(row, col + 1)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the lower left cell is alive (O = current cell, # = cell to check)
         xxx
         xOx
         #xx
        */
        if (isCellInGrid(row + 1, col - 1) && isCellAlive(row + 1, col - 1)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the lower cell is alive (O = current cell, # = cell to check)
         xxx
         xOx
         x#x
        */
        if (isCellInGrid(row + 1, col) && isCellAlive(row + 1, col)) {
            livingNeighborCount++;
        }
        /**
         * Checks if the lower right cell is alive (O = current cell, # = cell to check)
         xxx
         xOx
         xx#
        */
        if (isCellInGrid(row + 1, col + 1) && isCellAlive(row + 1, col + 1)) {
            livingNeighborCount++;
        }

        return livingNeighborCount;
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  run the 1st rule outlined in the game on the cell during a generation change
     *
     *   1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
     *
     * @param row int
     * @param col int
     */
    private boolean matchesRuleOne(int row, int col) {
        return isCellAlive(row, col) && countLivingNeighbors(row, col) < 2;
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  run the 2nd rule outlined in the game on the cell during a generation change
     *
     *   2. Any live cell with more than three live neighbours dies, as if by overcrowding.
     *
     * @param row int
     * @param col int
     */
    private boolean matchesRuleTwo(int row, int col) {
        return isCellAlive(row, col) && countLivingNeighbors(row, col) > 3;
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  run the 3rd rule outlined in the game on the cell during a generation change
     *
     *   3. Any live cell with two or three live neighbours lives on to the next generation.
     *
     * @param row int
     * @param col int
     */
    private boolean matchesRuleThree(int row, int col) {
        int livingNeighbors = countLivingNeighbors(row, col);
        return isCellAlive(row, col) && (livingNeighbors == 2 || livingNeighbors == 3);
    }

    /**
     * Given the grid coordinates indicated by row & col:
     *  run the 4th rule outlined in the game on the cell during a generation change
     *
     *   4. Any dead cell with exactly three live neighbours becomes a live cell.
     *
     * @param row int
     * @param col int
     */
    private boolean matchesRuleFour(int row, int col) {
        return isCellDead(row, col) && countLivingNeighbors(row, col) == 3;
    }

    @Override
    public String toString() {
        return "GameGrid{" + "grid=" + Arrays.deepToString(grid) + ", rowCount=" + rowCount + ", colCount="
                + colCount + '}';
    }

    /**
     * Builds a String representation of the GameGrid
     */
    public String asString() {
        StringBuilder output = new StringBuilder();

        if (null != this.grid) {
            // TODO: can do better than O(n^2)?
            for (int row = 0; row < this.rowCount; row++) {
                for (int col = 0; col < this.colCount; col++) {
                    // if this is not the 1st row, and is the 1st column of the row, add return carriage
                    if (row != 0 && col == 0) {
                        output.append(GameOfLife.LF);
                    }
                    output.append(isCellInGrid(row, col) ? this.grid[row][col].getSymbol() : '_');
                }
            }
        }

        return output.toString();
    }

    /**
     * runs the rules on the grid to move to the next generation
     *  the result is an updated grid and an incremented generation
     */
    public void incrementGeneration() {
        CellStatus[][] nextGenerationGrid = new CellStatus[rowCount][colCount];
        // TODO: can do better than O(n^2)?
        for (int row = 0; row < this.rowCount; row++) {
            for (int col = 0; col < this.colCount; col++) {
                // enforce rules on this cell
                if (isCellInGrid(row, col)) {
                    //   1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
                    if (matchesRuleOne(row, col)) {
                        // kill cell
                        nextGenerationGrid[row][col] = CellStatus.Dead;
                    }
                    //   2. Any live cell with more than three live neighbours dies, as if by overcrowding.
                    else if (matchesRuleTwo(row, col)) {
                        // kill cell
                        nextGenerationGrid[row][col] = CellStatus.Dead;
                    }
                    //   3. Any live cell with two or three live neighbours lives on to the next generation.
                    else if (matchesRuleThree(row, col)) {
                        // resurrect cell
                        nextGenerationGrid[row][col] = CellStatus.Alive;
                    }
                    //   4. Any dead cell with exactly three live neighbours becomes a live cell.
                    else if (matchesRuleFour(row, col)) {
                        // resurrect cell
                        nextGenerationGrid[row][col] = CellStatus.Alive;
                    } else {
                        // just copy cell
                        nextGenerationGrid[row][col] = this.grid[row][col];
                    }
                }
            }
        }

        this.grid = nextGenerationGrid;
        this.generation++;
    }
}