I am fairly new to GUI programming and I am interested in improving my code from every aspect. Performance, security, readability, conciseness, and the look-and-feel aspects are all important to me.
Here is the non-GUI part of the code:
import java.util.Random;
public class Universe
{
private int generation;
private int alive;
private boolean[][] currentGeneration;
private boolean[][] nextGeneration;
private Random random;
public Universe(int height, int width, int seed, String pattern)
{
this.currentGeneration = new boolean[height][width];
if (pattern.equalsIgnoreCase("Random"))
{
random = new Random(seed);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
currentGeneration[i][j] = random.nextBoolean();
}
}
}
else if (pattern.equalsIgnoreCase("glider"))
{
getGlider(currentGeneration);
}
//to add more cases here
nextGeneration = generateNextGeneration(currentGeneration);
generation = 1;
alive = calculateAlive(currentGeneration);
}
//Getters and instance methods
int getGeneration()
{
return generation;
}
int getAlive()
{
return alive;
}
boolean[][] getCurrentGeneration()
{
return currentGeneration;
}
boolean[][] getNextGeneration()
{
return nextGeneration;
}
void moveToNextState()
{
boolean[][] temp = generateNextGeneration(nextGeneration);
currentGeneration = nextGeneration;
nextGeneration = temp;
alive = calculateAlive(currentGeneration);
generation++;
}
void reset(int h, int w, int seed)
{
this.currentGeneration = new boolean[h][w];
random = new Random(seed);
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
this.currentGeneration[i][j] = random.nextBoolean();
}
}
nextGeneration = generateNextGeneration(currentGeneration);
generation = 1;
alive = calculateAlive(currentGeneration);
}
//Utility methods
static int calculateNeighbours(boolean[][] grid, int row, int column)
{
int neighbours = 0, r, c;
int N = grid.length;
int M = grid[0].length;
for (int p = -1; p <= 1; p++)
{
for (int m = -1; m <= 1; m++)
{
r = row + p;
c = column + m;
if (r < 0)
r = N - 1;
if (r > N - 1)
r = 0;
if (c < 0)
c = M - 1;
if (c > M - 1)
c = 0;
if (grid[r][c] && (p != 0 || m != 0))
neighbours++;
}
}
return neighbours;
}
static int calculateAlive(boolean[][] grid)
{
int alive = 0;
for (int i = 0; i < grid.length; i++)
{
for (int j = 0; j < grid[0].length; j++)
{
if (grid[i][j])
alive++;
}
}
return alive;
}
static boolean[][] generateNextGeneration(boolean[][] currentGeneration)
{
int N = currentGeneration.length;
int M = currentGeneration[0].length;
boolean[][] nextGeneration = new boolean[N][M];
int neighbours;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
neighbours = calculateNeighbours(currentGeneration, i, j);
if (neighbours == 3 || (currentGeneration[i][j] && neighbours == 2))
nextGeneration[i][j] = true;
else
nextGeneration[i][j] = false;
}
}
return nextGeneration;
}
static boolean[][] generateNthGeneration(boolean[][] currentGeneration, int X)
{
if (X == 0)
return currentGeneration;
else
return generateNthGeneration(generateNextGeneration(currentGeneration), X - 1);
}
static void printGeneration(boolean[][] generation)
{
for (int i = 0; i < generation.length; i++)
{
for (int j = 0; j < generation[0].length; j++)
System.out.print(generation[i][j]? "O" : " ");
System.out.println();
}
}
static void getGlider(boolean currentGeneration[][])
{
for(int i = 0; i < 60; i++)
{
for (int j =0; j < 90; j++)
currentGeneration[i][j] = false;
}
currentGeneration[1][3] = true;
currentGeneration[2][3] = true;
currentGeneration[3][3] = true;
currentGeneration[2][1] = true;
currentGeneration[3][2] = true;
}
}
And here is the GUI part of the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
class Cells extends JPanel
{
boolean[][] grid;
int h, w;
Cells(boolean[][] grid)
{
this.grid = grid;
h = grid.length;
w = grid[0].length;
}
{
setBounds(50, 20, 961, 620);
setBackground(Color.DARK_GRAY);
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//g2.setColor(Color.BLUE);
for (int x = 0; x < w * 10; x+=10)
{
for (int y = 0; y < h * 10; y+=10)
{
if (grid[y/10][x/10])
{
g2.setColor(Color.BLUE);
g2.fillRect(x, y, 10, 10);
}
else
{
g2.setColor(Color.gray);
g2.drawRect(x, y, 10, 10);
}
}
}
}
}
public class GameOfLife extends JFrame
{
public final int height = 60;
public final int width = 90;
Universe universe = new Universe(height, width, (int) Math.random(), "Random");
Cells cells = new Cells(universe.getCurrentGeneration());
JLabel generationLabel = new JLabel("Generation#" + universe.getGeneration());
JLabel aliveLabel = new JLabel("Alive: " + universe.getAlive());
JButton resetButton, speedUpButton, slowDownButton;
JToggleButton playToggleButton;
String[] items = {"random", "Glider", "Gun", "Spaceship", "Beacon", "Pulsar"}; //to be added
JComboBox patterns = new JComboBox(items); //to be added
ActionListener repaint = e ->
{
universe.moveToNextState();
generationLabel.setText("Generation #" + universe.getGeneration());
aliveLabel.setText("Alive: " + universe.getAlive());
cells.grid = universe.getCurrentGeneration();
repaint();
setVisible(true);
};
int speed = 100;
Timer timer = new Timer(speed, repaint);
public GameOfLife()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000, 700);
setResizable(false);
setLocationRelativeTo(null);
setLayout(null);
setBackground(Color.darkGray);
getContentPane().setBackground(Color.darkGray);
generationLabel.setName("GenerationLabel");
aliveLabel.setName("AliveLabel");
resetButton = new JButton("Reset");
resetButton.setName("ResetButton");
playToggleButton = new JToggleButton("Pause");
playToggleButton.setName("PlayToggleButton");
speedUpButton = new JButton("Speed+");
slowDownButton = new JButton("Speed-");
add(cells);
addLabels();
addButtons();
addFunctionality();
timer.start();
setVisible(true);
}
void addLabels()
{
JPanel labels = new JPanel()
{
{
setBounds(50, 636, 200, 40);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
setBackground(Color.DARK_GRAY);
generationLabel.setForeground(Color.LIGHT_GRAY);
aliveLabel.setForeground(Color.LIGHT_GRAY);
add(generationLabel);
add(aliveLabel);
}
};
add(labels);
}
void addButtons()
{
JPanel buttons = new JPanel()
{
{
setBounds(250, 636, 500, 40);
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setBackground(Color.DARK_GRAY);
resetButton.setForeground(Color.darkGray);
playToggleButton.setForeground(Color.darkGray);
speedUpButton.setForeground(Color.darkGray);
slowDownButton.setForeground(Color.darkGray);
resetButton.setBackground(Color.LIGHT_GRAY);
playToggleButton.setBackground(Color.LIGHT_GRAY);
speedUpButton.setBackground(Color.LIGHT_GRAY);
slowDownButton.setBackground(Color.LIGHT_GRAY);
add(resetButton);
add(playToggleButton);
add(speedUpButton);
add(slowDownButton);
}
};
add(buttons);
}
void addFunctionality()
{
playToggleButton.addActionListener(e ->
{
if (playToggleButton.getText().equals("Play") && !timer.isRunning())
{
timer.restart();
playToggleButton.setText("Pause");
}
else if (playToggleButton.getText().equals("Pause") && timer.isRunning())
{
timer.stop();
playToggleButton.setText("Play");
}
});
speedUpButton.addActionListener(e ->
{
if (speed == 0)
{}
else
timer.setDelay(speed -= 50);
});
slowDownButton.addActionListener(e -> timer.setDelay(speed += 50));
resetButton.addActionListener(e -> universe.reset(height, width, (int) Math.random()));
}
public static void main(String[] args)
{
new GameOfLife();
}
}
Any comment, even if it was on one small issue with my code will be appreciated. The purpose of the code is to simulate Conway's game of life based on an initial random state, though I am planning to add a JComponent that allows the user to choose an initial state. The evolution of the universe is displayed as an animation with options for the user to pause, resume, reset the universe, and adjust the speed of the animation. The code does all of that, but I am not sure about it's performance, look-and-feel, conciseness, or readability.