12
\$\begingroup\$

I wrote John Conway's Game of Life in Java:

class GameOfLife {
 static int countSurrounding(int[][] board, int a, int b) {
 int count = 0;
 int[][] surrounding = {{a - 1, b - 1},
 {a - 1, b },
 {a - 1, b + 1},
 {a , b - 1},
 {a , b + 1},
 {a + 1, b - 1},
 {a + 1, b },
 {a + 1, b + 1}};
 for (int i[]: surrounding) {
 try {
 if (board[i[0]][i[1]] == 1) {
 count++;
 }
 }
 catch (ArrayIndexOutOfBoundsException e) {}
 }
 return count;
 }
 static void printBoard(int[][] board) {
 for (int[] i: board) {
 for (int j: i) {
 if (j == 1) {
 System.out.print("#");
 }
 else {
 System.out.print(".");
 }
 }
 System.out.println();
 }
 System.out.println();
 }
 public static void main(String[] args) {
 int[][] nextBoard = {{0, 0, 0, 0, 0, 0, 0, 0, 0},
 {0, 0, 0, 1, 0, 0, 0, 0, 0},
 {0, 1, 0, 1, 0, 0, 0, 0, 0},
 {0, 0, 1, 1, 0, 0, 0, 0, 0},
 {0, 0, 0, 0, 0, 0, 0, 0, 0},
 {0, 0, 0, 0, 0, 0, 0, 0, 0},
 {0, 0, 0, 0, 0, 0, 0, 0, 0},
 {0, 0, 0, 0, 0, 0, 0, 0, 0},
 {0, 0, 0, 0, 0, 0, 0, 0, 0}};
 int[][] board = new int[nextBoard.length][nextBoard[0].length];
 for (int gen = 0; gen < 25; gen++) {
 for (int i = 0; i < nextBoard.length; i++) {
 for (int j = 0; j < nextBoard[i].length; j++) {
 board[i][j] = nextBoard[i][j];
 }
 }
 printBoard(board);
 for (int i = 0; i < board.length; i++) {
 for (int j = 0; j < board[i].length; j++) {
 if (board[i][j] == 1 && !(countSurrounding(board, i, j) == 2 || countSurrounding(board, i, j) == 3)) {
 nextBoard[i][j] = 0;
 }
 else if (board[i][j] == 0 && countSurrounding(board, i, j) == 3) {
 nextBoard[i][j] = 1;
 }
 }
 }
 }
 }
}

How can I improve and optimize it?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked May 10, 2013 at 15:42
\$\endgroup\$

1 Answer 1

14
\$\begingroup\$

A few suggestions:

  • Each cell in the Game of Life has exactly two states: dead or alive. Unless you plan to implement -1 as undead, I'd suggest you use booleans instead of integers to indicate the state.

  • The int i[] is a bit odd; i is normally used for indices, not for whole rows.

  • In countSurrounding you are using Exceptions to count the surrounding living cells. This is wasteful and unnecessary; we can determine the correct bounds ourself.

    Given an index i in an array row, we can get the lower index min and the upper index max by

    int minRow = i == 0 ? 0 : i - 1;
    int maxRow = i == (row.length - 1) ? (row.length - 1) : i + 1;
    

    The same thing for columns (j index in an array column):

    int minCol = j == 0 ? 0 : j - 1;
    int maxCol = j == (column.length - 1) ? (column.length - 1) : j + 1;
    

    This itself assumes that i and j always are inside the bounds. We can then loop over the surrounding cells like

    for (int row_idx = minRow; row_idx <= maxRow; row_idx++) {
     for (int idx = minCol; idx <= maxCol; idx++) {
     if (board[row_idx][idx] && !(row_idx == i && idx == j)) { // assumes board is implemented with booleans
     count++
     }
     }
    }
    

    While this is longer, this doesn't abuse exceptions for something totally non-exceptional.

  • The printBoard is fine, except for my first two criticisms. The if/else can be condensed to a ternary; I'd find this easier to read:

    for (boolean row[] : board) {
     for (boolean cell : row) {
     System.out.print( cell ? "#" : "." );
     }
     System.out.println();
    }
    
  • In main, you initialize nextBoard with the initial board, then copy it over into board. This is slightly useless. I'd propose to refactor your main into a method boolean[][] nextGeneration(boolean[][] board). Your main would then look like

    int[][] initialBoard = ...;
    public static void main(String[] args) {
     int[][] board = new int[nextBoard.length][nextBoard[0].length];
     // copy over the initial board
     for (int i = 0; i < initialBoard.length; i++) {
     for (int j = 0; j < initalBoard[i].length; j++) {
     board[i][j] = initialBoard[i][j];
     }
     }
     // loop over 25 generations
     printBoard(board);
     for (int gen = 0; gen < 25; gen++) {
     System.out.println("\nGeneration " + gen);
     board = nextGeneration(board);
     printBoard(board);
     }
    }
    
  • Now something is becoming obvious: We have collected a few static methods that operate on our board array. This is a sign that we should create a seperate class:

    Board
    ========
    - board: boolean[][]
    --------
    - countSurrounding(i: int, j: int): int
    + toString(): String
    + nextGeneration(): Board
    

    The class could be easily extended to have a static default board, or a constructor that creates a random board.

  • Each "Game of Life" can be described by a ruleset like 23/3. The digits before the slash determine when a living cell stays alive, the digits after the slash determine when a new cell is born. Generalizing nextGeneration to be able to play with all possible rules is a nice exercise.

    private boolean nextCellState(int i, int j) {
     int count = countSurrounding(i, j);
     int rules[] = board[i][j] ? stayAlive : birthNew;
     for (int rule : rules) {
     if (rule == count) {
     return true;
     }
     }
     return false;
    }
    

    then

    public Board nextGeneration() {
     boolean next[][] = new boolean[board.length][board[0].length];
     int rows = board.length;
     int cols = board.[0].length;
     for (int i = 0; i < rows; i++) {
     for (int j = 0; j < cols; j++) {
     next[i][j] = nextCellState(i, j);
     }
     }
     return new Board(next);
    }
    
answered May 11, 2013 at 11:09
\$\endgroup\$
1
  • \$\begingroup\$ You had my +1 at "undead". Great example of excessive value spaces. \$\endgroup\$ Commented Jul 24, 2020 at 14:10

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.