Game of Life/ GameOfLifeFrame

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

/*
Frame for the graphical representation of instances of GameOfLifeModel
 */

public final class GameOfLifeFrame extends javax.swing.JFrame implements java.awt.event.ActionListener
{
	// serialVersionUID for serialisation, implemented in classe javax.swing.JFrame
	private final static long serialVersionUID = 1L;

	// Class constants
	/**
	 * All game board frames will be saved to PNG files
	 */
	public static final boolean Save = true;
	/**
	 * No game board frame will be saved to a PNG file
	 */
	public static final boolean DontSave = false;

	final private static int [] DeadColour = {0, 0, 0};
	final private static int [] LiveColour = {255, 255, 255};

	final private static java.lang.String title = "Conway's Game of Life";
	final private static int offset = 10;
	final private static int offsetTop = 30;

	private static long fileCounter = 100000;

	// Instance variables
	private int sizeX = 200;
	private int sizeY = 200;
	private long currentStepNumber = 0;
	private long numberOfIterations = 1;
	private boolean save; /* is true, if simulation shall be saved to PNG-files */
	private GameOfLifeModel model;
	private java.awt.image.BufferedImage bufferedImage;
	private java.awt.image.WritableRaster imageRaster;
	private javax.swing.Timer timer = new javax.swing.Timer (1, this); // for the delay between two subsequent JFrames

	/**
	 * Constructor for the initialisation of instances of the class GameOfLifeFrame
	 * @param width: horizontal board size in pixel
	 * @param height: vertical board size in pixel
	 * @param mode: border handling, either GameOfLifeModel.ALWAYS_DEAD or GameOfLifeModel.TORODIAL
	 * @param count: number of steps for the game board iterations
	 * @param saveBoard: save all iterated boards to PNG files, either Save oder DontSave
	 */
	public GameOfLifeFrame (int width, int height, boolean mode, long count, boolean saveBoard)
	{
		super (title);
		this.model = new GameOfLifeModel (width, height, mode);
		this.sizeX = width;
		this.sizeY = height;
		this.numberOfIterations = count;
		this.save = saveBoard;

		// frame size, background and layout
		this.setSize (this.sizeX + 2 * offset, this.sizeY + 2 * offset + offsetTop);
		this.setBackground (java.awt.Color.WHITE);
		this.bufferedImage = new java.awt.image.BufferedImage (this.sizeX, this.sizeY, java.awt.image.BufferedImage.TYPE_INT_RGB);
		this.imageRaster = bufferedImage.getWritableTile (0, 0);
		this.setDefaultCloseOperation (javax.swing.WindowConstants.EXIT_ON_CLOSE);
	}

	/**
	 * Returns the model of the instance GameOfLifeModel of GameOfLifeFrame
	 * @return model of the instance GameOfLifeModel
	 */
	public GameOfLifeModel getModel ()
	{
		return model;
	}

	/**
	 * Standard method for painting a rectangular board of the Game of Life
	 * @param graphics: instance of java.awt.Graphics
	 */
	public void paint (java.awt.Graphics graphics)
	{
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				boolean cellState = model.getCellState (x, y);
				if (cellState == GameOfLifeModel.DEAD)
				{
					imageRaster.setPixel (x, y, DeadColour);
				}
				else
				{
					imageRaster.setPixel (x, y, LiveColour);
				}
			}
		}
		graphics.drawImage (bufferedImage, offset, offset + offsetTop, null);
		java.awt.Toolkit.getDefaultToolkit ().sync (); // This method call ensures that the display is up-to-date
	}

	/**
	 *  Method for the initialisation of an instance of GameOfLifeFrame
	 *  Starts timer instance of the class javax.swing.Timer for updating frames
	 */
	public void init ()
	{
		java.awt.Graphics2D graphics2D = bufferedImage.createGraphics ();
		this.paint (graphics2D);
		this.setVisible (true);
		this.setAlwaysOnTop (true);
		this.timer.start ();
	}

	/**
	 * Method save for saving the current board frame to a graphical PNG file
	 * @param bufferedImage: instance of java.awt.image.BufferedImage
	*/
	private void saveFile (java.awt.image.BufferedImage bufferedImage)
	{
		final java.lang.String fileExtension = "png";
		java.util.Iterator <javax.imageio.ImageWriter> iter = javax.imageio.ImageIO.getImageWritersByFormatName (fileExtension);
		javax.imageio.ImageWriter imageWriter = (javax.imageio.ImageWriter) iter.next ();
		javax.imageio.ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam ();
		imageWriteParam.setCompressionMode (javax.imageio.ImageWriteParam.MODE_DISABLED);
		try
		{
			java.lang.String fileNameCounter = java.lang.Long.toString (fileCounter);
			java.lang.String fileName = "GameOfLife" + fileNameCounter + "." + fileExtension;
			javax.imageio.stream.FileImageOutputStream fileImageOutputStream = new javax.imageio.stream.FileImageOutputStream (new java.io.File (fileName));
			imageWriter.setOutput (fileImageOutputStream);
			javax.imageio.IIOImage iIOimg = new javax.imageio.IIOImage ((java.awt.image.RenderedImage) bufferedImage, null, null);
			java.lang.System.out.println ("Save " + fileName);
			imageWriter.write (null, iIOimg, imageWriteParam);
			fileCounter++;
		}
		catch (java.lang.Exception exception)
		{
			exception.printStackTrace ();
		}
	}

	/**
	 * Overwritten standard method actionPerformed for the synchronised update of an instance GameOfLifeFrame
	 */
	public void actionPerformed (java.awt.event.ActionEvent event)
	{
		java.lang.String currentTitel = title + " / step " + currentStepNumber;
		if (currentStepNumber < numberOfIterations)
		{
			currentStepNumber++;
			model.computeNextBoard ();
			if (this.save)
			{
				this.saveFile (bufferedImage);
			}
			repaint ();
		}
		else
		{
			currentTitel = currentTitel + " / game over";
			timer.stop ();
		}
		setTitle (currentTitel);
	}
}