Game Of Life

This project is a Java-coded implementation of Conway's Game of Life. I developed an algorithm that follows the rules of cellular automaton to create interesting emergent patterns.

Interactive Demo

Click on the grid to toggle cells between alive and dead states.

Algorithm Explanation

Conway's Game of Life operates on a grid of cells, each of which is either alive or dead. The state of each cell in the next generation is determined by these rules:

  1. Underpopulation: A live cell with fewer than 2 live neighbors dies
  2. Survival: A live cell with 2 or 3 live neighbors survives
  3. Overpopulation: A live cell with more than 3 live neighbors dies
  4. Reproduction: A dead cell with exactly 3 live neighbors becomes alive

The core algorithm from my implementation that calculates the next generation is:

public void step() {
    boolean[][] gen = new boolean[grid.length][grid[0].length];
    
    for(int x = 1; x < grid.length -1; x ++) {
        for(int y = 1; y < grid[0].length - 1; y ++) {
            
            int neighbors = 0;
            
            // Count the 8 surrounding neighbors
            for(int i = -1; i <= 1; i++) {
                for(int j = -1; j <= 1; j++) {
                    if(grid[x+i][y+j]) {
                        neighbors ++;
                    }
                }
            }
            
            // Don't count the cell itself
            if(grid[x][y]) {
                neighbors --;
            }
            
            // Apply the Game of Life rules
            if(neighbors < 2 && grid[x][y]) {
                gen[x][y] = false;              // Rule 1: Underpopulation
            } else if(neighbors > 3 && grid[x][y]) {
                gen[x][y] = false;              // Rule 3: Overpopulation
            } else if(neighbors == 3 && !grid[x][y]) {
                gen[x][y] = true;               // Rule 4: Reproduction
            } else {
                gen[x][y] = grid[x][y];         // Rule 2: Survival or remain dead
            }
        }
    }
    
    // Update the grid to the new generation
    grid = gen;
}

This algorithm works by creating a new grid for the next generation, calculating the state of each cell based on its neighbors, and then replacing the current grid with the new one.

Source Code

Main.java
Life.java
DrawingSurface.java
import processing.core.PApplet;

public class Main {

    public static void main(String[] args) {
        DrawingSurface drawing = new DrawingSurface();
        PApplet.runSketch(new String[]{""}, drawing);
    }

}
import java.awt.Point;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;

import processing.core.PApplet;

/**

Represents a Game Of Life grid.

Coded by: Nodoka Shibasaki
Modified on: 3.29.2021

*/

public class Life {

    // Add a 2D array field to represent the Game Of Life grid.
    
    boolean[][] grid;
    
    
    /**
     * Initialized the Game of Life grid to an empty 20x20 grid.
     */
    public Life() {
        grid = new boolean[20][20];
    }

    
    
    /**
     * Initializes the Game of Life grid to a 20x20 grid and fills it with data from the file given.
     * 
     * @param filename The path to the text file.
     */
    public Life(String filename) {
        grid = new boolean[20][20];
        readData(filename, grid);
    }
    
    /**
     * Runs a single step within the Game of Life by applying the Game of Life rules on
     * the grid.
     */
    public void step() {
        
        boolean[][] gen = new boolean[grid.length][grid[0].length];
        
        for(int x = 1; x < grid.length -1; x ++) {
            for(int y = 1; y < grid[0].length - 1; y ++) {
                
                int neighbors = 0;
                
                for(int i = -1; i <= 1; i++) {
                    for(int j = -1; j <= 1; j++) {
                        if(grid[x+i][y+j]) {
                            neighbors ++;
                        }
                    }
                }
                
                if(grid[x][y]) {
                    neighbors --;
                }
                
                if(neighbors < 2 && grid[x][y]) {
                    gen[x][y] = false;
                }else if(neighbors > 3 && grid[x][y]) {
                    gen[x][y] = false;
                }else if(neighbors == 3 && !grid[x][y]) {
                    gen[x][y] = true;
                }else {
                    gen[x][y] = grid[x][y];
                }
                
            }
        }
        
        grid = gen;
    }

    
    
    /**
     * Runs n steps within the Game of Life.
     * @param n The number of steps to run.
     */
    public void step(int n) {
        for(int i = n; i > 0; i--) {
            step();        
        }
    }
    
    
    
    /**
     * Formats this Life grid as a String to be printed (one call to this method returns the whole multi-line grid)
     * 
     * @return The grid formatted as a String.
     */
    public String toString() {
        
        String output = "";
        
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[i].length; j++) {
                if(grid[i][j]) {
                    output += "*";
                }else {
                    output += "-";
                }
            }
            output += '\n';
        }
        
        return output;
    }
    
    
    
    /**
     * (Graphical UI)
     * Draws the grid on a PApplet.
     * The specific way the grid is depicted is up to the coder.
     * 
     * @param marker The PApplet used for drawing.
     * @param x The x pixel coordinate of the upper left corner of the grid drawing. 
     * @param y The y pixel coordinate of the upper left corner of the grid drawing.
     * @param width The pixel width of the grid drawing.
     * @param height The pixel height of the grid drawing.
     */
    public void draw(PApplet marker, float x, float y, float width, float height) {
        marker.noFill();
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                float w = width/grid[0].length;
                float h = height/grid.length;
                float dx = x + j * w;
                float dy = y + i * h;
                if(grid[i][j]) {
                    marker.fill(0);
                }else {
                    marker.fill(255);
                }
                marker.rect(dx, dy, w, h);
            }
        }
    }
    
    
    
    /**
     * (Graphical UI)
     * Determines which element of the grid matches with a particular pixel coordinate.
     * This supports interaction with the grid using mouse clicks in the window.
     * 
     * @param p A Point object containing a graphical pixel coordinate.
     * @param x The x pixel coordinate of the upper left corner of the grid drawing. 
     * @param y The y pixel coordinate of the upper left corner of the grid drawing.
     * @param width The pixel width of the grid drawing.
     * @param height The pixel height of the grid drawing.
     * @return A Point object representing a coordinate within the game of life grid.
     */
    public Point clickToIndex(Point p, float x, float y, float width, float height) {
        
        double px = p.getX();
        double py = p.getY();
        
        double w = width/grid[0].length;
        double h = height/grid.length;
        double dx = (int)((px-x)/w);
        double dy = (int)((py-y)/h);
                
        if(dx >= 0 && dx < grid.length && dy >= 0 && dy < grid[0].length) {
             Point p2 = new Point();
             p2.setLocation(dy, dx);
             return p2;
        }
                
        return null;
    }
    
    /**
     * (Graphical UI)
     * Toggles a cell in the game of life grid between alive and dead.
     * This allows the user to modify the grid.
     * 
     * @param i The x coordinate of the cell in the grid.
     * @param j The y coordinate of the cell in the grid.
     */
    public void toggleCell(int i, int j) {
        grid[i][j] = !grid[i][j];
    }

    

    // Reads in array data from a simple text file containing asterisks (*)
    public void readData (String filename, boolean[][] gameData) {
        File dataFile = new File(filename);

        if (dataFile.exists()) {
            int count = 0;

            FileReader reader = null;
            Scanner in = null;
            try {
                reader = new FileReader(dataFile);
                in = new Scanner(reader);

                while (in.hasNext()) {
                    String line = in.nextLine();
                    for(int i = 0; i < line.length(); i++)
                        if (count < gameData.length && i < gameData[count].length && line.charAt(i)=='*')
                            gameData[count][i] = true;

                    count++;
                }
            } catch (IOException ex) {
                throw new IllegalArgumentException("Data file " + filename + " cannot be read.");
            } finally {
                if (in != null)
                    in.close();
            }

        } else {
            throw new IllegalArgumentException("Data file " + filename + " does not exist.");
        }
    }
}
import java.awt.Point;
import java.awt.event.KeyEvent;

import processing.core.PApplet;

public class DrawingSurface extends PApplet {
	
	private Life board;
	private int runCount;
	private int speed;
	private Point lastToggle;
	
	private final int MAX_SPEED = 480;
	private final int MIN_SPEED = 15;
	
	public DrawingSurface() {
		board = new Life();
		runCount = -1;
		speed = 120;
		lastToggle = null;
	}
	
	// The statements in the setup() function 
	// execute once when the program begins
	public void setup() {
		//size(0,0,PApplet.P3D);
	}
	
	// The statements in draw() are executed until the 
	// program is stopped. Each statement is executed in 
	// sequence and after the last line is read, the first 
	// line is executed again.
	public void draw() {
		background(255);   // Clear the screen with a white background
		
		textAlign(CENTER);
		fill(0);
		text("Game of Life simulation. Press space to start/pause.", width/2, 20);
		text("Click on a cell to toggle whether it is alive.", width/2, 40);
		text("Press 'c' to clear the board.", width/2, 60);
		text("Press 'g' to create a glider.", width/2, 80);
		
		
		if (runCount == 0) {
			board.step();
		}
		
		if (runCount >= 0 && frameCount % speed == 0)
			runCount++;

		stroke(0);
		
		board.draw(this, 0, 100, width, height-100);
		
	}
	
	
	public void mousePressed() {
		if (mouseButton == LEFT) {
			Point click = new Point(mouseX,mouseY);
			Point cellCoord = board.clickToIndex(click,0,100,width,height-100);
			if (cellCoord != null) {
				board.toggleCell((int)cellCoord.getX(), (int)cellCoord.getY());
				lastToggle = cellCoord;
			}
		}
	}
	
	
	public void mouseDragged() {
		if (mouseButton == LEFT) {
			Point click = new Point(mouseX,mouseY);
			Point cellCoord = board.clickToIndex(click,0,100,width,height-100);
			if (cellCoord != null && !cellCoord.equals(lastToggle)) {
				board.toggleCell((int)cellCoord.getX(), (int)cellCoord.getY());
				lastToggle = cellCoord;
			}
		}
	}
	
	
	public void keyPressed() {
		if (keyCode == 32) { // SPACE
			if (runCount <= 0)
				runCount = 0;
			else
				runCount = -1;
		} else if (keyCode == KeyEvent.VK_DOWN) {
			speed = Math.min(MAX_SPEED, speed*2);
		} else if (keyCode == KeyEvent.VK_UP) {
			speed = Math.max(MIN_SPEED, speed/2);
		} else if (keyCode == KeyEvent.VK_C) {
			board = new Life();
		} else if (keyCode == KeyEvent.VK_G) {
			board = new Life();
			// make a glider
			board.toggleCell(1, 1);
			board.toggleCell(2, 2);
			board.toggleCell(2, 3);
			board.toggleCell(1, 3);
			board.toggleCell(0, 3);
		}
	}

}