Waldbrandsimulation/ SimForestModel

/*
Source file: SimForestModel.java
Author: Markus Bautsch
Licence: public domain
Date: 31 October 2022
Version: 1.0
Programming language: Java
 */

/*
Modell fuer die Simulation von Waldbraenden in einer rechteckigen Flaeche
 */

public class SimForestModel
{
	// Klassenkonstanten fuer die Klassenvariablen currentGround und nextGround
	public final static long SOIL  = 0;
	public final static long TREE  = 1;
	public final static long WATER = 2;
	public final static long FIRE  = 3;

	// Instanzvariablen
	private int sizeX = 200;
	private int sizeY = 200;
	private long annualNumberOfLightnings = 3; // Anzahl der Blitzschlaege innerhalb des Gebiets in einem Jahr
	private double probabilitySpreadOfForestFire = 0.7; // nach Blitzschlag
	private double fireExtinctionIndex = 0.9; // Wahrscheinlichkeit, dass ein Feuer erlischt
	private double treeGrowthIndex = 0.02; // Wahrscheinlichkeit, dass ein Baum waechst
	private long [] [] currentGround;
	private long [] [] nextGround;

	/* Konstruktor fuer die Initialisierung von Instanzen der Klasse SimForestModel
	 * @param x: Fuer die horizontale Groesse in Pixeln
	 * @param y: Fuer die vertikale Groesse in Pixeln
	 */
	public SimForestModel (int x, int y)
	{
		this.sizeX = x;
		this.sizeY = y;
		this.currentGround = new long [x] [y];
		this.nextGround    = new long [x] [y];
	}

	/*
	 * Gibt die Breite des Waldbodenmodells zurueck
	 */
	public int getWidth ()
	{
		return sizeX;
	}

	/*
	 * Gibt die Hoehe des Waldbodenmodells zurueck
	 */
	public int getHeight ()
	{
		return sizeY;
	}

	/*
	 * Gibt den Status des Waldbodenmodells an der Stelle (x,y) zurueck
	 */
	public long getGroundState (int x, int y)
	{
		return currentGround [x] [y];
	}

	/*
	 * Setzt den Status des Waldbodenmodells an der Stelle (x,y)
	 */
	public void setGroundState (int x, int y, long state)
	{
		currentGround [x] [y] = state;
	}

	/*
	 * Setzt die Anzahl der Blitzschlaege in einem Jahr
	 */
	public void setAnnualNumberOfLightnings (long number)
	{
		if (number < 0)
		{
			number = 0;
		}
		annualNumberOfLightnings = number;
	}

	/*
	 * Prueft den Wertebereich fuer eine Wahrscheinlichkeit zwischen Null und Eins
	 */
	private double checkProbability (double probability)
	{
		probability = java.lang.Math.abs (probability);
		if (probability > 1)
		{
			probability = 1;
		}
		return probability;
	}

	/*
	 * Setzt die Wahrscheinlichkeit fuer die Ausbreitung eines Brandes nach einem Blitzschlag
	 */
	public void setProbabilitySpreadOfForestFire (double probability)
	{
		probabilitySpreadOfForestFire = checkProbability (probability);
	}

	/*
	 * Setzt die Wahrscheinlichkeit fuer das Erloeschen eines Feuers an einer Stelle des Waldbodens
	 */
	public void setFireExtinctionIndex (double probability)
	{
		fireExtinctionIndex = checkProbability (probability);
	}

	/*
	 * Setzt die Wahrscheinlichkeit fuer das Wachsen eines neuen Baumes an einer Stelle des Waldbodens
	 */
	public void setTreeGrowthIndex (double probability)
	{
		treeGrowthIndex = checkProbability (probability);
	}

	/*
	 *  Methode fuer die Simulation eines Blitzschlags
	 *  Gibt zurueck, ob ein Baum entzuendet wurde
	 */
	private boolean simulateLightning ()
	{
		int locationX = (int) PseudoRandom.nextInteger (sizeX);
		int locationY = (int) PseudoRandom.nextInteger (sizeY);
		boolean forestIsBurning = false;
		if (currentGround [locationX] [locationY] == TREE)
		{
			currentGround [locationX] [locationY] = FIRE;
			forestIsBurning = true;
		}
		return forestIsBurning;
	}

	/*
	 *  Methode fuer die Ausbreitung des Waldbrands
	 *  Gibt zurueck, ob ein benachbarter Baum brennt
	 */
	private boolean fireNextToTree (int x, int y)
	{
		boolean fireNextToTree = false;
		double probability = PseudoRandom.nextProbability ();
		if (probability < probabilitySpreadOfForestFire) // fuer rechts/links, oben/unten
		{
			fireNextToTree = ((x > 0) && (currentGround [x - 1] [y] == FIRE))
					|| ((x < (sizeX - 1)) && (currentGround [x + 1] [y] == FIRE))
					|| ((y > 0) && (currentGround [x] [y - 1] == FIRE))
					|| ((y < (sizeY - 1)) && (currentGround [x] [y + 1] == FIRE));
		}
		if (!fireNextToTree && (probability < (probabilitySpreadOfForestFire / 4))) // fuer die vier Diagonalen
		{
			fireNextToTree = ((x > 0) && (y > 0) && (currentGround [x - 1] [y - 1] == FIRE))
					|| ((x < (sizeX - 1)) && (y > 0) && (currentGround [x + 1] [y - 1] == FIRE))
					|| ((x > 0) && (y < (sizeY - 1)) && (currentGround [x - 1] [y + 1] == FIRE))
					|| ((x < (sizeX - 1)) && (y < (sizeY - 1)) && (currentGround [x + 1] [y + 1] == FIRE));
		}
		return fireNextToTree;
	}

	/*
	 * Methode zum Kopieren des neu berechneten Waldbodens zum aktuellen Wandboden
	 */
	private void copyNextToCurrentGround ()
	{
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				currentGround [x] [y] = nextGround [x] [y];
			}
		}
	}

	/*
	 * 	Methode fuer die Simulation eines Waldbrands
	 *  Gibt zurueck, wie viele Baeume des Waldes brennen
	 */
	public long simulateBurning ()
	{
		long numberOfTreesBurning = 0;
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				long currentState = currentGround [x] [y];
				nextGround [x] [y] = currentState;
				if (currentState == TREE)
				{
					if (fireNextToTree (x, y))
					{
						nextGround [x] [y] = FIRE;
						numberOfTreesBurning++;
					}
				}
				else if (currentState == FIRE)
				{
					double probability = PseudoRandom.nextProbability ();
					if (probability < fireExtinctionIndex)
					{
						nextGround [x] [y] = SOIL;
					}
					else
					{
						numberOfTreesBurning++;
					}
				}
			}
		}

		copyNextToCurrentGround ();

		return numberOfTreesBurning;
	}

	/*
	 *  Methode zum Simulieren des Wachstums eines neuen Baumes
	 */
	private void newTree (int x, int y)
	{
		double probability = PseudoRandom.nextProbability ();
		if (probability < treeGrowthIndex)
		{
			nextGround [x] [y] = TREE;
		}
	}

	/*
	 *  Methode fuer die Simulation des jaehrlichen Baumwuchses
	 */
	private void simulateTreeGrowth ()
	{
		for (int y = 0; y < sizeY; y++)
		{
			for (int x = 0; x < sizeX; x++)
			{
				long currentState = currentGround [x] [y];
				nextGround [x] [y] = currentState;
				if (currentState == SOIL)
				{
					newTree (x, y);
				}
			}
		}
	}

	/*
	 *  Methode fuer die Simulation eines Jahres
	 *  Gibt zurueck, ob der Wald brennt
	 */
	private boolean simulateLightnings ()
	{
		boolean forestIsBurning = false;
		for (long i = 0; i < annualNumberOfLightnings; i++)
		{
			boolean forestIsIgnited = simulateLightning ();
			forestIsBurning = forestIsBurning || forestIsIgnited;
		}
		return forestIsBurning;
	}

	/*
	 *  Methode fuer die Simulation eines Jahres
	 *  Gibt zurueck, ob der Wald brennt
	 */
	public boolean simulateYear ()
	{
		simulateTreeGrowth ();
		copyNextToCurrentGround ();
		boolean forestIsBurning = simulateLightnings ();
		return forestIsBurning;
	}
}