I have received a "personal mail" asking me for help about the Game of Life
I usually do not answer to personal mails because, if I did, I would not have time to post in the forum, but this time the problem was really interesting and I also think that my solution was interesting
Wikipedia defines the Game of Life that way
The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells,
each of which is in one of two possible states, live or dead.
Every cell interacts with its eight neighbors, which are the cells that are directly horizontally,
vertically, or diagonally adjacent. At each step in time, the following transitions occur:
1.Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
2.Any live cell with more than three live neighbours dies, as if by overcrowding.
3.Any live cell with two or three live neighbours lives on to the next generation.
4.Any dead cell with exactly three live neighbours becomes a live cell.
The initial pattern constitutes the seed of the system. The first generation is created by applying the
above rules simultaneously to every cell in the seed—births and deaths happen simultaneously,
and the discrete moment at which this happens is sometimes called a tick
(in other words, each generation is a pure function of the one before).
The rules continue to be applied repeatedly to create further generations.
This is the author original code (I have removed and added some comments in the code)
import java.awt.*;
import javax.swing.*;
public class GameOfLife extends JFrame {
// size in pixel of every label
static final int size = 15;
public static final int NB_ROWS = 30, NB_COLUMNS = 50;
private static final int DEAD = 0, ALIVE = 1;
// the cells labels
private JLabel[][] label;
// weneed 2 states one which is the actual one and one for the next run
private int[][] state, newState;
GameOfLife() {
super("GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels
label = new JLabel[NB_ROWS][NB_COLUMNS];
state = new int[NB_ROWS][NB_COLUMNS];
newState = new int[NB_ROWS][NB_COLUMNS];
for(int i = 0; i < NB_ROWS; i++) {
for(int j = 0; j < NB_COLUMNS; j++) {
state[i][j] = DEAD;
newState[i][j] = DEAD;
label[i][j] = new JLabel();
label[i][j].setOpaque(true);
label[i][j].setBackground(Color.LIGHT_GRAY);
add(label[i][j]);
}
}
// setup a few cells on to start
pack();
}
// play the game
public void run() {
while(true) {
// this is where it is too complicated
for(int i = 0; i < NB_ROWS; i++) {
for(int j = 0; j < NB_COLUMNS; j++) {
int nbDead = 0;
int nbAlive = 0;
// test if there is a cell left of me
if(i > 0) {
if(state[i-1][j] == ALIVE)
nbAlive++;
else
nbDead++;
}
// test if there is a cell right of me
if(i < NB_ROWS -1) {
if(state[i+1][j] == ALIVE)
nbAlive++;
else
nbDead++;
}
// test if a cell over me...
// test if a cell below me...
// test if a cell up left...
// test if a cell up right...
// test if a cell down left..
if(i > 0 && j < 0) {
}
// test if a cell down right...
// now perform test if the cell should stay alive or not
// newState
if(state[i][j] == 1) { // if alive
if(nbAlive < 2) // 1.Any live cell with fewer than two live neighbours dies
newState[i][j] = DEAD;
if(nbAlive > 3) // 2.Any live cell with more than three live neighbours dies
newState[i][j] = DEAD;
}
else {
if(nbAlive == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState[i][j] = ALIVE;
}
}
}
// update the status
for(int i = 0; i < NB_ROWS; i++) {
for(int j = 0; j < NB_COLUMNS; j++) {
state[i][j] = newState[i][j];
if(state[i][j] == DEAD) {
label[i][j].setBackground(Color.LIGHT_GRAY);
label[i][j].repaint();
}
if(state[i][j] == ALIVE) {
label[i][j].setBackground(Color.BLUE);
label[i][j].repaint();
}
}
}
try {
Thread.sleep(2000);
}
catch(Exception e) {
System.out.println("Error in sleep");
}
public static void main(String[] arg) {
GameOfLife gameOfLife = new GameOfLife();
gameOfLife.setVisible(true);
gameOfLife.run();
}
}
The problems he/she was encountering:
- logic was very complicated when testing the around cells where you are on the first/last row or column
- how to start randomly the life cell when starting the game ?
I fixed these 2 problems in 3 interesting ways (I think)
- first I added 2 additionnal columns and rows, not showed by the GUI, at the left, right, top bottom so no need to check if at a border
- second I delegate to a class that extends JLabel the role of checking if a cell should stay alive or not with the addNeighbour() method
- third I add a MouseListener to the cells so you can select, on the fly, which cells you want alive
For the fun of it, I added:
- buttons to stop/clear the process
- a Slider to select the speed at which each generation are produced
- a label displaying the generation number
I used a Swing timer to control the speed at which each generation is created
To do: a way to figure out if the system is frozen
To do: a way to figure out if the system oscillated to infinity
I have tried to respect original author variables names but parameterized the size of the grid in the main() method
Here is the code: enjoy.... I will post it in the Code Snippet
import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;
import javax.swing.*;
/*
The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells,
each of which is in one of two possible states, live or dead.
Every cell interacts with its eight neighbors, which are the cells that are directly horizontally,
vertically, or diagonally adjacent. At each step in time, the following transitions occur:
1.Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
2.Any live cell with more than three live neighbours dies, as if by overcrowding.
3.Any live cell with two or three live neighbours lives on to the next generation.
4.Any dead cell with exactly three live neighbours becomes a live cell.
The initial pattern constitutes the seed of the system. The first generation is created by applying the
above rules simultaneously to every cell in the seed—births and deaths happen simultaneously,
and the discrete moment at which this happens is sometimes called a tick
(in other words, each generation is a pure function of the one before).
The rules continue to be applied repeatedly to create further generations.
*/
public class GameOfLife2 extends JFrame implements ActionListener {
static final Color[] color = {Color.LIGHT_GRAY, Color.BLUE};
// size in pixel of every label
static final int size = 15;
static final Dimension dim = new Dimension(size, size);
// the cells labels
private LifeLabel[][] label;
// timer that fires the next feneration
private Timer timer;
// generation counter
private int generation = 0;
private JLabel generationLabel = new JLabel("Generation: 0");
// the 3 buttons
private JButton bClear = new JButton("Clear"),
bPause = new JButton("Pause"),
bGo = new JButton("Go");
// the slider for the speed
JSlider slider = new JSlider(0, 5000); // 0 to 5000 milliseconds (5 seconds)
// state of the game (running or pause)
private boolean gameRunning = false;
// if the mouse is down or not
private boolean mouseDown = false;
GameOfLife2(int nbRow, int nbCol) {
super("GameOfLife");
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// create the labels (2 more on each size) these wont be shown
// but will be used in calculating the cells alive around
label = new LifeLabel[nbRow+2][nbCol+2];
for(int r = 0; r < nbRow+2; r++) {
for(int c = 0; c < nbCol+2; c++) {
label[r][c] = new LifeLabel();
}
}
// panel in the center with the labels
JPanel panel = new JPanel(new GridLayout(nbRow, nbCol, 1, 1));
panel.setBackground(Color.BLACK);
panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
// add each label (not the one on the border) to the panel and add to each of them its neighbours
for(int r = 1; r < nbRow+1; r++) {
for(int c = 1; c < nbCol+1; c++) {
panel.add(label[r][c]);
label[r][c].addNeighbour(label[r-1][c]); // North
label[r][c].addNeighbour(label[r+1][c]); // South
label[r][c].addNeighbour(label[r][c-1]); // West
label[r][c].addNeighbour(label[r][c+1]); // East
label[r][c].addNeighbour(label[r-1][c-1]); // North West
label[r][c].addNeighbour(label[r-1][c+1]); // North East
label[r][c].addNeighbour(label[r+1][c-1]); // South West
label[r][c].addNeighbour(label[r+1][c+1]); // South East
}
}
// now the panel can be added
add(panel, BorderLayout.CENTER);
// the bottom panel with the buttons the generation label and the slider
// this panel is formed grid panels
panel = new JPanel(new GridLayout(1,3));
// another panel for the 3 buttons
JPanel buttonPanel = new JPanel(new GridLayout(1,3));
bClear.addActionListener(this);
buttonPanel.add(bClear);
bPause.addActionListener(this);
bPause.setEnabled(false); // game is pause the pause button is disabled
buttonPanel.add(bPause);
bGo.addActionListener(this);
buttonPanel.add(bGo);
// add the 3 buttons to the panel
panel.add(buttonPanel);
// the generation label
generationLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(generationLabel);
// the slider
slider.setMajorTickSpacing(1000);
slider.setMinorTickSpacing(250);
slider.setPaintTicks(true);
// the labels for the Slider
Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
for(int i = 0; i <= 5; i++) {
labelTable.put( new Integer( i * 1000 ), new JLabel("" + i) );
}
slider.setLabelTable( labelTable );
slider.setPaintLabels(true);
panel.add(slider);
// in the JFrame
add(panel, BorderLayout.SOUTH);
// put the frame on
setLocation(20, 20);
pack();
setVisible(true);
// start the thread that run the cycles of life
timer = new Timer(5000 - slider.getValue(), this);
}
// called by the Timer and the JButtons
public synchronized void actionPerformed(ActionEvent e) {
// test the JButtons first
Object o = e.getSource();
// the clear button
if(o == bClear) {
timer.stop(); // stop timer
gameRunning = false; // flag gamme not running
bPause.setEnabled(false); // disable pause button
bGo.setEnabled(true); // enable go button
// clear all cells
for(int r = 1; r < label.length -1; r++) {
for(int c = 1; c < label[r].length -1; c++) {
label[r][c].clear();
}
}
// reset generation number and its label
generation = 0;
generationLabel.setText("Generation: 0");
return;
}
// the pause button
if(o == bPause) {
timer.stop(); // stop timer
gameRunning = false; // flag not running
bPause.setEnabled(false); // disable myself
bGo.setEnabled(true); // enable go button
return;
}
// the go button
if(o == bGo) {
bPause.setEnabled(true); // enable pause button
bGo.setEnabled(false); // disable myself
gameRunning = true; // flag game is running
timer.setDelay(5000 - slider.getValue());
timer.start();
return;
}
// not a JButton so it is the timer
// set the delay for the next time
timer.setDelay(5000 - slider.getValue());
// if the game is not running wait for next time
if(!gameRunning)
return;
++generation;
generationLabel.setText("Generation: " + generation);
for(int r = 0; r < label.length; r++) {
for(int c = 0; c < label[r].length; c++) {
label[r][c].checkState();
}
}
for(int r = 0; r < label.length; r++) {
for(int c = 0; c < label[r].length; c++) {
label[r][c].updateState();
}
}
}
// to start the whole thing as a Java application
public static void main(String[] arg) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GameOfLife2(30, 50);
}
});
}
// A class that extends JLabel but also check for the neigbour
// when asked to do so
class LifeLabel extends JLabel implements MouseListener {
private int state, newState;
private int nbNeighbour;
private LifeLabel[] neighbour = new LifeLabel[8];
LifeLabel() {
state = newState = 0; // Dead
setOpaque(true); // so color will be showed
setBackground(color[0]);
addMouseListener(this); // to select new LIVE cells
this.setPreferredSize(dim);
}
// to add a neibour
void addNeighbour(LifeLabel n) {
neighbour[nbNeighbour++] = n;
}
// to see if I should live or not
void checkState() {
// number alive around
int nbAlive = 0;
// see the state of my neighbour
for(int i = 0; i < nbNeighbour; i++)
nbAlive += neighbour[i].state;
// newState
if(state == 1) { // if alive
if(nbAlive < 2) // 1.Any live cell with fewer than two live neighbours dies
newState = 0;
if(nbAlive > 3) // 2.Any live cell with more than three live neighbours dies
newState = 0;
}
else {
if(nbAlive == 3) // 4.Any dead cell with exactly three live neighbours becomes a live cell
newState = 1;
}
}
// after the run switch the state to new state
void updateState() {
if(state != newState) { // do the test to avoid re-setting same color for nothing
state = newState;
setBackground(color[state]);
}
}
// called when the game is reset/clear
void clear() {
if(state == 1 || newState == 1) {
state = newState = 0;
setBackground(color[state]);
}
}
@Override
public void mouseClicked(MouseEvent arg0) {
}
// if the mouse enter a cell and it is down we make the cell alive
public void mouseEntered(MouseEvent arg0) {
if(mouseDown) {
state = newState = 1;
setBackground(color[1]);
}
}
@Override
public void mouseExited(MouseEvent arg0) {
}
// if the mouse is pressed on a cell you register the fact that it is down
// and make that cell alive
public void mousePressed(MouseEvent arg0) {
mouseDown = true;
state = newState = 1;
setBackground(color[1]);
}
// turn off the fact that the cell is down
public void mouseReleased(MouseEvent arg0) {
mouseDown = false;
}
}
}

New Topic/Question
Reply




MultiQuote






|