Skip to content

Instantly share code, notes, and snippets.

@gene-ressler
Last active November 26, 2021 00:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gene-ressler/99c4a8ca13fa07b5eea6 to your computer and use it in GitHub Desktop.
Save gene-ressler/99c4a8ca13fa07b5eea6 to your computer and use it in GitHub Desktop.
Simple Life in Java Swing
package life;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Life {
protected LifePane lifePane;
protected JToggleButton goButton;
protected JSlider speedSlider;
protected JSpinner sizeSpinner;
public class LifePane extends JComponent {
private int rows, cols;
private byte[][] thisGen, nextGen;
private final Timer timer;
private TimerTask task;
private final Object lock = new Object();
public LifePane(int rows, int cols) {
timer = new Timer("Life", true);
setGenSize(rows, cols);
}
public final void setGenSize(int rows, int cols) {
// Lock to prevent painting from seeing change.
synchronized (lock) {
this.rows = rows;
this.cols = cols;
thisGen = new byte[rows][cols];
nextGen = new byte[rows][cols];
Random gen = new Random();
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
thisGen[i][j] = toByte(gen.nextBoolean());
}
}
}
}
@Override
protected void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
int y0 = 0;
// Lock to prevent gen buffer swap while we're painting.
synchronized (lock) {
for (int i = 1; i < rows; i++) {
int y1 = i * height / (rows - 1);
int x0 = 0;
for (int j = 1; j < cols; j++) {
int x1 = j * width / (cols - 1);
if (thisGen[i][j] != 0) {
g.fillRect(x0, y0, x1 - x0, y1 - y0);
}
x0 = x1;
}
y0 = y1;
}
}
}
private byte toByte(boolean booleanVal) {
return booleanVal ? (byte) 1 : (byte) 0;
}
private void updateCell(int x0, int x, int x1, int y0, int y, int y1) {
int n = thisGen[y0][x0] + thisGen[y0][x] + thisGen[y0][x1]
+ thisGen[y][x0] + thisGen[y][x1]
+ thisGen[y1][x0] + thisGen[y1][x] + thisGen[y1][x1];
nextGen[y][x] = toByte(thisGen[y][x] == 0 ? n == 3 : n >> 1 == 1);
}
private void updateRow(int y0, int y, int y1) {
updateCell(cols - 1, 0, 1, y0, y, y1);
for (int j = 1; j < cols - 1; ++j) {
updateCell(j - 1, j, j + 1, y0, y, y1);
}
updateCell(cols - 2, cols - 1, 0, y0, y, y1);
}
public void update() {
updateRow(rows - 1, 0, 1);
for (int i = 1; i < rows - 1; i++) {
updateRow(i - 1, i, i + 1);
}
updateRow(rows - 2, rows - 1, 0);
// Swap generation buffers. Lock to avoid painting in progress.
synchronized (lock) {
byte[][] tmp = thisGen;
thisGen = nextGen;
nextGen = tmp;
}
}
/**
* Run the life instance with given update interval.
*
* @param updateInterval interval in milliseconds, <= 0 to stop
* @return this
*/
public LifePane run(int updateInterval) {
if (task != null) {
task.cancel();
task = null;
}
if (updateInterval > 0) {
task = new TimerTask() {
@Override
public void run() {
update();
repaint();
}
};
timer.schedule(task, updateInterval, updateInterval);
}
return this;
}
}
public void run(int width, int height, int updateInterval) {
JFrame frame = new JFrame("Life");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
lifePane = new LifePane(width, height).run(updateInterval);
frame.add(lifePane, BorderLayout.CENTER);
goButton = new JToggleButton("Go");
goButton.setPreferredSize(new Dimension(200, 0));
goButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (goButton.isSelected()) {
lifePane.run(speedSlider.getValue());
goButton.setText("Pause");
} else {
lifePane.run(0);
goButton.setText("Go");
}
}
});
speedSlider = new JSlider(25, 500, 200);
speedSlider.setInverted(true);
speedSlider.setMajorTickSpacing(25);
speedSlider.setPaintTicks(true);
speedSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (goButton.isSelected() && !speedSlider.getValueIsAdjusting()) {
lifePane.run(speedSlider.getValue());
}
}
});
sizeSpinner = new JSpinner();
sizeSpinner.setModel(new SpinnerNumberModel(width, 50, 1000, 50));
sizeSpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int size = (Integer) sizeSpinner.getValue();
lifePane.setGenSize(size, size);
lifePane.repaint();
}
});
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.X_AXIS));
controlPanel.add(goButton);
controlPanel.add(new JSeparator());
controlPanel.add(new JLabel("Speed: "));
controlPanel.add(speedSlider);
controlPanel.add(new JSeparator());
controlPanel.add(new JLabel("Grid size: "));
controlPanel.add(sizeSpinner);
controlPanel.add(new JSeparator());
frame.add(controlPanel, BorderLayout.NORTH);
frame.setPreferredSize(new Dimension(800, 800));
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Life().run(100, 100, 0);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment