Last active
November 26, 2021 00:13
-
-
Save gene-ressler/99c4a8ca13fa07b5eea6 to your computer and use it in GitHub Desktop.
Simple Life in Java Swing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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