My buttons keep getting hidden unless I click on them

47 views Asked by At

My buttons keep disappearing every time my code creates a new screen. I believe it might be due to the buffered image that I am creating, but I am not sure.

So far, I have tried removing that section of the code from the buffered section and trying to add the buttons to make buffered image. The first solution didn't work, likely due to my background also being part of the buffered image. This led to my grey panels also disappearing. As for the second solutions, the buttons I added stopped being interactable, and were instead made with text. I have attached my code below. Please suggest ways to fix my problem, along with any redundancies and inefficiencies in my code.

Main class:

package life;

import javax.swing.*;

public class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            GUIGameOfLife gui = new GUIGameOfLife(25, System.currentTimeMillis());
            gui.setVisible(true);
        });
    }
}

GameOfLifeClass:

package life;

import java.util.Random;
import java.io.Serializable;

public class GameOfLife implements Serializable{
    public static final char ALIVE_CELL = 'O';
    public static final char DEAD_CELL = ' ';

    public char[][] currentGeneration;
    public char[][] nextGeneration;
    public int size;
    public int aliveCells;
    public Random random;

    public GameOfLife(int size, long seed) {
        this.size = size;
        currentGeneration = new char[size][size];
        nextGeneration = new char[size][size];
        random = new Random(seed);
        aliveCells = 0; // Initialize aliveCells

        initializeRandomGeneration();
    }

    public void initializeRandomGeneration() {
        aliveCells = 0; // Reset aliveCells before each simulation
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                currentGeneration[i][j] = (random.nextBoolean()) ? ALIVE_CELL : DEAD_CELL;
                if (currentGeneration[i][j] == ALIVE_CELL) {
                    aliveCells++;
                }
            }
        }
    }

    public void simulateGenerations(int numGenerations) {
        for (int generation = 0; generation <= numGenerations; generation++) {
            System.out.println("Generation: " + generation);
            System.out.println("Alive: " + aliveCells);
            printCurrentGeneration();
            simulateGeneration2(numGenerations);

            // Sleep for a specified duration (e.g., 50 milliseconds)
            sleep(50);
        }
    }

    public char[][] getCurrentGeneration() {
        return currentGeneration;
    }

    public int getSize() {
        return size;
    }

    public int getAliveCells() {
        return aliveCells;
    }

    public void sleep(int milliseconds) {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void printCurrentGeneration() {
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                System.out.print(currentGeneration[i][j]);
            }
            System.out.println();
        }
    }

    public void simulateGeneration2(int generation) {
        aliveCells = 0; // Reset aliveCells before each simulation
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                int liveNeighbors = countLiveNeighbors(i, j);

                if (currentGeneration[i][j] == ALIVE_CELL) {
                    if (liveNeighbors == 2 || liveNeighbors == 3) {
                        nextGeneration[i][j] = ALIVE_CELL; // Cell survives
                        aliveCells++;
                    } else {
                        nextGeneration[i][j] = DEAD_CELL; // Cell dies
                    }
                } else {
                    if (liveNeighbors == 3) {
                        nextGeneration[i][j] = ALIVE_CELL; // Cell is reborn
                        aliveCells++;
                    } else {
                        nextGeneration[i][j] = DEAD_CELL; // Cell remains dead
                    }
                }
            }
        }

        // Swap current and next generations
        char[][] temp = currentGeneration;
        currentGeneration = nextGeneration;
        nextGeneration = temp;
    }

    public int countLiveNeighbors(int x, int y) {
        int liveNeighbors = 0;
        int[] dx = {-1, -1, -1, 0, 0, 1, 1, 1};
        int[] dy = {-1, 0, 1, -1, 1, -1, 0, 1};

        for (int i = 0; i < 8; i++) {
            int nx = (x + dx[i] + size) % size; // Using the size modulo should work.
            int ny = (y + dy[i] + size) % size;

            if (currentGeneration[nx][ny] == ALIVE_CELL) {
                liveNeighbors++;
            }
        }

        return liveNeighbors;
    }
}

GUIGameOfLife:

package life;

// Importing the necessary libraries for the code to work.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.image.BufferedImage;

public class GUIGameOfLife extends JFrame {
    private static final int FIXED_GRID_SIZE = 750;
    private GameOfLife game;
    private Timer timer;
    private int generation;
    private JToggleButton pauseResumeButton;
    private JButton restartButton;
    private static final int textSize = 30;
    private BufferedImage offScreenImage;
    private JSlider speedSlider;
    private JTextField sizeTextField;

    private Color aliveCellColor = Color.GREEN;
    private Color deadCellColor = Color.RED;

    public GUIGameOfLife(int size, long seed) {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Calculate the cell size based on the fixed grid size
        int cellSize = FIXED_GRID_SIZE / size;

        // Set the size of the JFrame based on the fixed grid size
        setSize(FIXED_GRID_SIZE, FIXED_GRID_SIZE + 100); // Increased height for the different objects. 50 on the top and 50 on the bottom.


        setUndecorated(true); //(This would remove the bar obstructing the elements, but it would be hard to close or move the window, and it would also require me to change the locations of the buttons.

        // This initializes the game with an original size of 25.
        game = new GameOfLife(25, seed);
        generation = 0;

        // This is the timer.
        timer = new Timer(2000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (game.getSize() > 0) {
                    game.simulateGenerations(1);
                    generation++;
                    repaint();
                }
            }
        });

        //This is the code for the Pause/Resume button
        pauseResumeButton = new JToggleButton("Pause");
        pauseResumeButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (pauseResumeButton.isSelected()) {
                    timer.stop(); // Pause the timer
                    pauseResumeButton.setText("Resume");
                } else {
                    timer.start(); // Resume the timer
                    pauseResumeButton.setText("Pause");
                }
            }
        });

        //This is the Restart button
        restartButton = new JButton("Restart");
        restartButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                game.initializeRandomGeneration();
                generation = 0;
                repaint();
            }
        });

        //The "Speed Slider" to control the time between generations.
        speedSlider = new JSlider(JSlider.HORIZONTAL, 100, 2000, 1000); // Initial speed set to 1000 ms
        speedSlider.setMajorTickSpacing(500);
        speedSlider.setMinorTickSpacing(100);
        speedSlider.setPaintTicks(true);
        speedSlider.setSnapToTicks(true);
        speedSlider.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                timer.setDelay(speedSlider.getValue());
            }
        });

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(null);


        pauseResumeButton.setBounds(650, 15, 80, 25);

        restartButton.setBounds(575, 15, 70, 25);

        speedSlider.setBounds(405, 15, 165, 25);


        JButton setSizeButton = new JButton("Set Size");
        setSizeButton.setBounds(15, 810, 80, 25);
        setSizeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    int newSize = Integer.parseInt(sizeTextField.getText());

                    // Tests to make sure that the user input is a valid size.
                    if (newSize <= 0) {
                        JOptionPane.showMessageDialog(GUIGameOfLife.this, "Grid size cannot be 0 or negative. Setting grid size to 25.");
                        newSize = 25;
                    } else if (newSize > 375) {
                        JOptionPane.showMessageDialog(GUIGameOfLife.this, "Too big, the maximum size is 375. Setting grid size to 25.");
                        newSize = 25;
                    }

                    game = new GameOfLife(newSize, seed);
                    generation = 0; // Resets the generation counter after the user inputs the new size.
                    repaint();
                } catch (NumberFormatException ex) {
                    JOptionPane.showMessageDialog(GUIGameOfLife.this, "Invalid input. Please enter a valid number. Setting grid size to 25.");
                }
            }
        });

        sizeTextField = new JTextField();
        sizeTextField.setBounds(105, 810, 80, 25);
        buttonPanel.add(sizeTextField);

        //Allows the user to choose the color of the alive cell.
        JButton aliveCellColorButton = new JButton("Alive Cell Color");
        aliveCellColorButton.setBounds(195, 810, 140, 25);
        aliveCellColorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                aliveCellColor = JColorChooser.showDialog(GUIGameOfLife.this, "Choose Alive Cell Color", aliveCellColor);
                repaint();
            }
        });

        //Allows the user to choose the color of the dead cell.
        JButton deadCellColorButton = new JButton("Dead Cell Color");
        deadCellColorButton.setBounds(345, 810, 140, 25);
        deadCellColorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                deadCellColor = JColorChooser.showDialog(GUIGameOfLife.this, "Choose Dead Cell Color", deadCellColor);
                repaint();
            }
        });

        //This allows the user to save the game.
        JButton saveButton = new JButton("Save Game");
        saveButton.setBounds(495, 810, 110, 25);
        saveButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                saveGame();
            }
        });

        //This allows the user to save the game.
        JButton loadButton = new JButton("Load Game");
        loadButton.setBounds(615, 810, 120, 25);
        loadButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                loadGame();
            }
        });

        buttonPanel.add(pauseResumeButton);
        buttonPanel.add(restartButton);
        buttonPanel.add(speedSlider);
        buttonPanel.add(setSizeButton);
        buttonPanel.add(aliveCellColorButton);
        buttonPanel.add(deadCellColorButton);
        buttonPanel.add(saveButton);
        buttonPanel.add(loadButton);


        //all the earlier "buttonPanel.add" add the buttons to this buttonPanel, and this buttonPanel adds them to the GUI.
        add(buttonPanel);

        timer.start();
    }

    //New method to save the user's progress if they click the save button.
    private void saveGame() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("Save Game State");
        int userSelection = fileChooser.showSaveDialog(this);

        if (userSelection == JFileChooser.APPROVE_OPTION) {
            File fileToSave = fileChooser.getSelectedFile();
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream(fileToSave));
                oos.writeObject(game);
                oos.writeObject(aliveCellColor);
                oos.writeObject(deadCellColor);
                oos.writeInt(generation);
                JOptionPane.showMessageDialog(this, "Game state saved successfully!");
                //catches any IO (Input/Output) exceptions that could occur when reading or writing a file.
            } catch (IOException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(this, "Error saving game state.", "Error", JOptionPane.ERROR_MESSAGE);
            } finally {
                if (oos != null) { //Makes sure that no values are null.
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace(); //Prints a StackTrace (Error Report).
                    }
                }
            }
        }
    }

    // New method to load a previously saved state of the game
    private void loadGame() {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("Load Game State");

        int userSelection = fileChooser.showOpenDialog(this);
        if (userSelection == JFileChooser.APPROVE_OPTION) {
            File fileToLoad = fileChooser.getSelectedFile();
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream(fileToLoad));
                game = (GameOfLife) ois.readObject();
                aliveCellColor = (Color) ois.readObject(); //They are from the "Color" class, so by using (Color) you can use casting to explicitly cast object to a color. This allows me to use color class methods and properties.
                deadCellColor = (Color) ois.readObject();
                generation = ois.readInt();
                repaint();
                JOptionPane.showMessageDialog(this, "Game state loaded successfully!");
                //Catches any IO (Input/Output) exceptions that could occur when reading or writing a file.
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(this, "Error loading game state.", "Error", JOptionPane.ERROR_MESSAGE);
            } finally {
                if (ois != null) { //Makes sure that no values are null.
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace(); //Prints a StackTrace (Error Report).
                    }
                }
            }
        }
    }

    public char[][] getCurrentGeneration() {
        return game.getCurrentGeneration();
    }

    public int getGameSize() {
        return game.getSize();
    }

    @Override
    public void paint (Graphics g){
        // Create an off-screen image if it doesn't exist or if its size has changed. This will allow for my code to transition smoothly.
        if (offScreenImage == null || offScreenImage.getWidth() != getWidth() || offScreenImage.getHeight() != getHeight()) {
            offScreenImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
        }

        // Creates a graphics context for the off-screen image, so that the final result can be easily displayed without flickering.
        Graphics offScreenGraphics = offScreenImage.getGraphics();

        char[][] currentGeneration = getCurrentGeneration();

        int gameSize = getGameSize();

        // Checks if the game size is greater than 0.
        if (gameSize > 0) {
            // Calculates the cell size based on the fixed grid size.
            int cellSize = FIXED_GRID_SIZE / gameSize;

            // Calculates the offset to center the grid in the middle of the GUI.
            int offsetX = (getWidth() - gameSize * cellSize) / 2;
            int offsetY = (getHeight() - gameSize * cellSize) / 2;

            // Draws the background in black to fill in the extra space in the offset. (If 750 divided by the grid size doesn't return a whole number.)
            offScreenGraphics.setColor(Color.BLACK);
            offScreenGraphics.fillRect(0, 0, getWidth(), getHeight());

            // Draws two gray boxes with a black border at the edges on the top, each using half of the GUI's width. Then it draws another gray box at the bottom using all of the GUI's width.
            int boxWidth = getWidth() / 2;
            offScreenGraphics.setColor(Color.GRAY);
            offScreenGraphics.fillRect(0, 0, boxWidth, 50);
            offScreenGraphics.fillRect(boxWidth, 0, boxWidth, 50);
            offScreenGraphics.fillRect(0, getHeight() - 50, getWidth(), 50); // Gray box at the bottom
            offScreenGraphics.setColor(Color.BLACK);  // Set the color to black for the border

            // Draws the borders for the gray boxes on the bottom and the top.
            offScreenGraphics.drawRect(0, 0, boxWidth, 50);
            offScreenGraphics.drawRect(boxWidth, 0, boxWidth, 50);
            offScreenGraphics.drawRect(0, getHeight() - 50, getWidth(), 50); // Border for the bottom gray box

            offScreenGraphics.setColor(Color.WHITE);
            offScreenGraphics.setFont(new Font("Arial", Font.PLAIN, Math.min(gameSize * cellSize / 4, 20))); // Gives the specifications of the font, and makes sure that the font size doesn't exceed 20 by taking the minimum value of (gameSize * cellSize / 4) and 20.

            // Printing the generation and alive cells values.
            String generationText = "Generation: " + generation + " | Alive Cells: " + game.getAliveCells();
            int textX = (boxWidth - offScreenGraphics.getFontMetrics().stringWidth(generationText)) / 2;
            offScreenGraphics.drawString(generationText, textX, 50 - textSize / 2);

            //Draws each cell in the array in an off-screen image.
            for (int i = 0; i < gameSize; i++) {
                for (int j = 0; j < gameSize; j++) {
                    Color cellColor = (currentGeneration[i][j] == GameOfLife.ALIVE_CELL) ? aliveCellColor : deadCellColor;
                    offScreenGraphics.setColor(cellColor);
                    offScreenGraphics.fillRect(j * cellSize + offsetX, i * cellSize + offsetY, cellSize, cellSize);
                    offScreenGraphics.setColor(Color.BLACK);
                    offScreenGraphics.drawRect(j * cellSize + offsetX, i * cellSize + offsetY, cellSize, cellSize);
                }
            }
        }

        //Removes the information after it has been used to generate an array.
        offScreenGraphics.dispose();

        // This draws the off-screen image on the GUI;
        g.drawImage(offScreenImage, 0, 0, this);
    }
}
0

There are 0 answers