So, I have three classes: GUIBuilder, ConwayLife, and Updater. The GUI Builder builds a GUI that gives you a grid with dimensions that the user sets and the ability to select each grid element to pick if it is "alive" or "dead". The ConwayLife class is an algorithm to run Conway's game of life for a user-set number of generations. The Updater class is where I put the Platform.runlater code, although this came about after trying several different ideas to get it to work so may be a part of the problem.
The objective is for the Conway class to update the grid to reflect the current generation's state
Start State:
End State:
I've been reading that the JavaFX application thread should be pretty much left alone and so in trying to follow that logic created the Conway class in a new thread when the start simulation button is pressed. (Also ignore what is in the X and Y Textfields)
startSimulationButton.setOnAction(new EventHandler<ActionEvent>()
{
public void handle(ActionEvent event)
{
if(validateTextBox(genTextField))
{
int[][] universe = convertTo2DArray(simulationPane);
int generations = Integer.parseInt(genTextField.getText());
Runnable simStart = new ConwayLife(universe, generations, window);
new Thread(simStart).start();
}
else
{
instructionLabel.setText("Didn't put numbers in ya silly billy");
}
}
});
Here is the class that will be called to update the grid either from within the GUIbuiler object or from an updater object.
public void updateSimulationPane(int[][] universe, int xUpdate, int yUpdate) throws InterruptedException
{
clearSimulationGrid(simulationPane);
int[] flatUniverse = Stream.of(universe).flatMapToInt(IntStream::of).toArray();
for(int counter = 0; counter < (xUpdate*yUpdate); counter++)
{
Rectangle rect = new Rectangle(0, 0, squareDimensions, squareDimensions);
if(flatUniverse[counter] == 1)
{
rect.setFill(Color.DARKGREEN);
}
else
{
rect.setFill(Color.GREY);
}
simulationPane.add(rect, (counter) % xInput, (counter) / xInput);
}
double leftAnchor = (xWindow / 2) - ((squareDimensions * xInput) / 2);
gridAnchorPane.setTopAnchor(simulationPane, 100d);
gridAnchorPane.setLeftAnchor(simulationPane, leftAnchor);
//Thread.sleep(5000);
}
Here is the start of the Conway class and thread, I'm not too confident using threads so I'm including this in case I've messed something up here.
private final int[][] universe;
private final int generations;
private final GUIBuilder window;
public ConwayLife(int[][] universePassed, int generationsPassed, GUIBuilder windowPassed)
{
universe = universePassed;
generations = generationsPassed;
window = windowPassed;
}
public void run()
{
try
{
getGeneration(universe, generations, window);
}
catch(Exception e)
{
System.out.println("Exception caught");
}
}
public static void getGeneration(int[][] cells, int generations, GUIBuilder window) throws InterruptedException
{
int [][] oldGeneration = cells; //new array to not alter original dataset
int [][] newGeneration = new int[oldGeneration.length + 2][oldGeneration[0].length + 2]; //next generation will always have more columns and rows to simulate infinite universe
int deadCells = 0; //counting the deadcells in the dataset to stop redundant generations
int i = 0;
int j = 0;
int bounds = 2;
boolean boundsChange = false;
Next here is where is where the generation has finished, is going to update the GUI then loop to apply the algorithim to the new generation.
Updater changer = new Updater(window, trim(oldGeneration), oldGeneration.length, oldGeneration[0].length);
changer.run();
oldGeneration = newGeneration; //make the new generation the old generation to show passing of a generation
newGeneration = new int[oldGeneration.length + bounds][oldGeneration[0].length + bounds]; //em
}
Finally here is the updater class where the method call back to the GUIBuilder object is located.
public class Updater implements Runnable
{
private final GUIBuilder window;
private final int[][] unvierse;
private final int xAxis;
private final int yAxis;
public Updater(GUIBuilder passedWindow, int[][] passed2DArray,int passedXAxis,int passedYAxis)
{
this.window = passedWindow;
this.unvierse = passed2DArray;
this.xAxis = passedXAxis;
this.yAxis = passedYAxis;
}
public void run()
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
try
{
window.updateSimulationPane(unvierse, xAxis, yAxis);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
This is the error message I get:
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
at moduleInfo/ConwayGUIOfLife.GUIBuilder.clearSimulationGrid(GUIBuilder.java:174)
at moduleInfo/ConwayGUIOfLife.GUIBuilder.updateSimulationPane(GUIBuilder.java:216)
at moduleInfo/ConwayGUIOfLife.Updater$1.run(Updater.java:29)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:389)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
at java.base/java.lang.Thread.run(Thread.java:835)
EDIT 1: Here is the clearSimulationPane method. Sorry, it was just a single line so thought it must be something else causing it:
private void clearSimulationGrid(GridPane simulationPane)
{
simulationPane.getChildren().clear();
}
Edit 2: Okay, I see the problem where the GUIBuilder window object has been getting passed between classes by value rather than by reference as I'd been assuming I'm pretty sure but I still don't know how to fix it at this point.