0

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:

GUI Start State

End State:

GUI 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.

Chubbs
  • 39
  • 4
  • 2
    The NPE occurs in `clearSimulationGrid()` which you didn't post. Check the line indicated in the stack trace (GUIBuilder.java:174). – Axel Apr 09 '20 at 18:14
  • Ah sorry, I edited the post to include it. The clear button worked fine which is essentially just a call to the clearSimulationGrid() so didn't think to post it. – Chubbs Apr 09 '20 at 19:55
  • Check if simulationPane is null, the stack trace says you call it with the parameter simulationPane at updateSimulationPane(). As a little side note: 1) use the printStackTrace() of Exceptions, they don't bite and are very helpful for debugging problems instead of printing some generic "exception caught" text; 2) check out the javafx.concurrent package, especially the classes Task, Service and ScheduledService instead of using Platform.runLater; 3) Use more lambdas to write less code, all SAM interfaces can be transformed into lambdas. – fireandfuel Apr 09 '20 at 20:33
  • 2
    Ditch `Thread` and use `Timeline`. Get some ideas from https://github.com/sedj601/ConwaysGameOfLife – SedJ601 Apr 09 '20 at 20:39
  • To you edit 2: Only primitive data types are passed by value at Java, everything else is passed by reference. It can be a synchronization problem too, have a look at the **synchronized** and **volatile** keyword at Java. The debugger is your friend, breakpoints and use them to debug your code. – fireandfuel Apr 09 '20 at 20:40
  • @fireandfuel er, sorry, that’s not correct. Java is not pass by reference. It is pass by value. It just looks like it’s pass by reference because references are passed by value. – Axel Apr 10 '20 at 16:18
  • @Axel That's really confusing, thanks for pointing me at this. Everyone I know told it me wrong – fireandfuel Apr 10 '20 at 19:26

0 Answers0