Game of Life/ GameOfLifeModel

/*
Source file: GameOfLifeModel.java
Author: Markus Bautsch
Licence: public domain
Date: 13 October 2024
Version: 1.0
Programming language: Java
 */

/*
Model for the simulation of Conway's Game of Life
 */

public class GameOfLifeModel
{
	// Class constants for state of fields
	/**
	 * For dead cell state
	 */
	public final static boolean DEAD = false;
	/**
	 * For live cell state
	 */
	public final static boolean LIVE = true;

	// Class constants for mode of the borders
	/**
	 * Border mode is always dead
	 */
	public final static boolean ALWAYS_DEAD = false;
	/**
	 * Border mode is continued at opposite side of the board
	 */
	public final static boolean TORODIAL = true;

	// Instance variables
	private int sizeX = 200;
	private int sizeY = 200;
	private boolean borderMode = ALWAYS_DEAD;
	private boolean [] [] currentBoard;
	private boolean [] [] nextBoard;

	/**
	 * Constructor for the initialisation of game board instances of the class GameOfLifeModel
	 * @param width: for the horizontal size
	 * @param height: for the vertical size
	 * @param borderMode: for the mode of the border, either ALWAYS_DEAD or TORODIAL
	 */
	public GameOfLifeModel (int width, int height, boolean borderMode)
	{
		this.sizeX = width;
		this.sizeY = height;
		this.borderMode = borderMode;
		this.currentBoard = new boolean [width] [height];
		this.nextBoard    = new boolean [width] [height];
	}

	/**
	 * Returns the width of the game board
	 * @return width of the game board
	 */
	public int getWidth ()
	{
		return sizeX;
	}

	/**
	 * Returns the height of the game board
	 * @return height of the game board
	 */
	public int getHeight ()
	{
		return sizeY;
	}

	/**
	 * Returns the border mode of the game board
	 * @return border mode of the game board
	 */
	public boolean getBorderMode ()
	{
		return borderMode;
	}

	/**
	 * Checking the validity of the cell parameters
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if all parameters are valid
	 */
	private boolean parametersValid (int x, int y)
	{
		boolean xValid = (x >= 0) && (x < this.sizeX);
		boolean yValid = (y >= 0) && (y < this.sizeY);
		return xValid && yValid;
	}

	/**
	 * Sets cell (x,y) to state
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @param state: cell state
	 */
	public void setCellState (int x, int y, boolean state)
	{
		if (parametersValid (x, y))
		{
			currentBoard [x] [y] = state;
		}
	}

	/**
	 * Gets state of cell (x,y)
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return state of the cell (x, y)
	 */
	public boolean getCellState (int x, int y)
	{
		boolean state = currentBoard [x] [y];
		return state;
	}

	/**
	 * Checking cell (x, y) for upper left live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if upper left neighbour is live
	 */
	private boolean upperLeftNeighbourIsLive (int x, int y)
	{
		boolean upperLeftNeighbourIsLive = false;
		if ((x > 0) && (y > 0))
		{
			upperLeftNeighbourIsLive = (currentBoard [x - 1] [y - 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			if ((x == 0) && (y == 0))
			{
				upperLeftNeighbourIsLive = (currentBoard [sizeX - 1] [sizeY - 1] == LIVE);
			}
			else if (x == 0)
			{
				upperLeftNeighbourIsLive = (currentBoard [sizeX - 1] [y - 1] == LIVE);
			}
			else if (y == 0)
			{
				upperLeftNeighbourIsLive = (currentBoard [x - 1] [sizeY - 1] == LIVE);
			}
		}
		return upperLeftNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for upper live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if upper neighbour is live
	 */
	private boolean upperNeighbourIsLive (int x, int y)
	{
		boolean upperNeighbourIsLive = false;
		if (y > 0)
		{
			upperNeighbourIsLive = (currentBoard [x] [y - 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			upperNeighbourIsLive = (currentBoard [x] [sizeY - 1] == LIVE);
		}
		return upperNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for upper right live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if upper right neighbour is live
	 */
	private boolean upperRightNeighbourIsLive (int x, int y)
	{
		boolean upperRightNeighbourIsLive = false;
		if ((x < (sizeX - 1)) && (y > 0))
		{
			upperRightNeighbourIsLive = (currentBoard [x + 1] [y - 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			if ((x == (sizeX - 1)) && (y == 0))
			{
				upperRightNeighbourIsLive = (currentBoard [0] [sizeY - 1] == LIVE);
			}
			else if (x == (sizeX - 1))
			{
				upperRightNeighbourIsLive = (currentBoard [0] [y - 1] == LIVE);
			}
			else if (y == 0)
			{
				upperRightNeighbourIsLive = (currentBoard [x + 1] [sizeY - 1] == LIVE);
			}
		}
		return upperRightNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for left live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if left neighbour is live
	 */
	private boolean leftNeighbourIsLive (int x, int y)
	{
		boolean leftNeighbourIsLive = false;
		if (x > 0)
		{
			leftNeighbourIsLive = (currentBoard [x - 1] [y] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			leftNeighbourIsLive = (currentBoard [sizeX - 1] [y] == LIVE);
		}
		return leftNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for right live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if right neighbour is live
	 */
	private boolean rightNeighbourIsLive (int x, int y)
	{
		boolean rightNeighbourIsLive = false;
		if (x < (sizeX - 1))
		{
			rightNeighbourIsLive = (currentBoard [x + 1] [y] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			rightNeighbourIsLive = (currentBoard [0] [y] == LIVE);
		}
		return rightNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for lower left live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if lower left neighbour is live
	 */
	private boolean lowerLeftNeighbourIsLive (int x, int y)
	{
		boolean lowerLeftNeighbourIsLive = false;
		if ((x > 0) && (y < (sizeY - 1)))
		{
			lowerLeftNeighbourIsLive = (currentBoard [x - 1] [y + 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			if ((x == 0) && (y == (sizeY - 1)))
			{
				lowerLeftNeighbourIsLive = (currentBoard [sizeX - 1] [0] == LIVE);
			}
			else if (x == 0)
			{
				lowerLeftNeighbourIsLive = (currentBoard [sizeX - 1] [y + 1] == LIVE);
			}
			else if (y == (sizeY - 1))
			{
				lowerLeftNeighbourIsLive = (currentBoard [x - 1] [0] == LIVE);
			}
		}
		return lowerLeftNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for lower live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if lower neighbour is live
	 */
	private boolean lowerNeighbourIsLive (int x, int y)
	{
		boolean lowerNeighbourIsLive = false;
		if (y < (sizeY - 1))
		{
			lowerNeighbourIsLive = (currentBoard [x] [y + 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			lowerNeighbourIsLive = (currentBoard [x] [0] == LIVE);
		}
		return lowerNeighbourIsLive;
	}

	/**
	 * Checking cell (x, y) for lower right live neighbour 
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return true, if lower right neighbour is live
	 */
	private boolean lowerRightNeighbourIsLive (int x, int y)
	{
		boolean lowerRightNeighbourIsLive = false;
		if ((x < (sizeX - 1)) && (y < (sizeY - 1)))
		{
			lowerRightNeighbourIsLive = (currentBoard [x + 1] [y + 1] == LIVE);
		}
		else if (borderMode == TORODIAL)
		{
			if ((x == (sizeX - 1)) && (y == (sizeY - 1)))
			{
				lowerRightNeighbourIsLive = (currentBoard [0] [0] == LIVE);
			}
			else if (x == (sizeX - 1))
			{
				lowerRightNeighbourIsLive = (currentBoard [0] [y + 1] == LIVE);
			}
			else if (y == (sizeY - 1))
			{
				lowerRightNeighbourIsLive = (currentBoard [x + 1] [0] == LIVE);
			}
		}
		return lowerRightNeighbourIsLive;
	}

	/**
	 * Counting all live neighbours of cell (x, y)
	 * @param x: horizontal position
	 * @param y: vertical position
	 * @return number of live neighbours
	 */
	private long countLiveNeighbours (int x, int y)
	{
		long numberOfLiveNeighbours = 0;
		if (upperLeftNeighbourIsLive (x, y))  {numberOfLiveNeighbours++;}
		if (upperNeighbourIsLive (x, y))      {numberOfLiveNeighbours++;}
		if (upperRightNeighbourIsLive (x, y)) {numberOfLiveNeighbours++;}
		if (leftNeighbourIsLive (x, y))       {numberOfLiveNeighbours++;}
		if (rightNeighbourIsLive (x, y))      {numberOfLiveNeighbours++;}
		if (lowerLeftNeighbourIsLive (x, y))  {numberOfLiveNeighbours++;}
		if (lowerNeighbourIsLive (x, y))      {numberOfLiveNeighbours++;}
		if (lowerRightNeighbourIsLive (x, y)) {numberOfLiveNeighbours++;}
		return numberOfLiveNeighbours;
	}

	/**
	 * Copies all cells of next board to current board
	 */
	private void copyNextToCurrentBoard ()
	{
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				currentBoard [x] [y] = nextBoard [x] [y];
			}
		}
	}

	/**
	 * Computing next board of the game
	 */
	public void computeNextBoard ()
	{
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				long numberOfLiveNeighbours = countLiveNeighbours (x, y);
				if (numberOfLiveNeighbours == 2)
				{
					nextBoard [x] [y] = currentBoard [x] [y];
				}
				else if (numberOfLiveNeighbours == 3)
				{
					nextBoard [x] [y] = LIVE;
				}
				else
				{
					nextBoard [x] [y] = DEAD;
				}
			}
		}
		copyNextToCurrentBoard ();
	}
}