patternjavaMinor
Simple Cellular Automata (think Conway's Game of Life) simulator
Viewed 0 times
simplecellularautomataconwaygamelifesimulatorthink
Problem
Firstly, let me say that this code works, as posted. It fills a 10x10 grid with a random color (black/white) then runs a cellular automata simulation on the grid, starting or pausing when the button is pressed.
```
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.ConcurrentModificationException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CA_DriverV2 extends JFrame{
private static final Color white = Color.WHITE, black = Color.BLACK;
private Board board;
private JButton start_pause;
public CA_DriverV2(){
board = new Board();
board.setBackground(white);
start_pause = new JButton("Start");
start_pause.addActionListener(board);
this.add(board, BorderLayout.NORTH);
this.add(start_pause, BorderLayout.SOUTH);
//this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
}
public static void main(String args[]){
new CA_DriverV2();
}
private class Board extends JPanel implements ActionListener{
private final Dimension DEFAULT_SIZE = new Dimension(10, 10);
private final int DEFAULT_CELL = 10, DEFAULT_INTERVAL = 100, DEFAULT_RATIO = 50;
private Dimension board_size;
private int cell_size, interval, fill_ratio;
private boolean run;
private Timer timer;
private Color[][] grid;
public Board(){
board_size = DEFAULT_SIZE;
cell_size = DEFAULT_CELL;
interval = DEFAULT_INTERVAL;
fill_ratio = DEFAULT_RATIO;
run = false;
//Initialize grid with random values
//NOTE: Add JOptionPane for option to define fill rate and board size?
```
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.ConcurrentModificationException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CA_DriverV2 extends JFrame{
private static final Color white = Color.WHITE, black = Color.BLACK;
private Board board;
private JButton start_pause;
public CA_DriverV2(){
board = new Board();
board.setBackground(white);
start_pause = new JButton("Start");
start_pause.addActionListener(board);
this.add(board, BorderLayout.NORTH);
this.add(start_pause, BorderLayout.SOUTH);
//this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
}
public static void main(String args[]){
new CA_DriverV2();
}
private class Board extends JPanel implements ActionListener{
private final Dimension DEFAULT_SIZE = new Dimension(10, 10);
private final int DEFAULT_CELL = 10, DEFAULT_INTERVAL = 100, DEFAULT_RATIO = 50;
private Dimension board_size;
private int cell_size, interval, fill_ratio;
private boolean run;
private Timer timer;
private Color[][] grid;
public Board(){
board_size = DEFAULT_SIZE;
cell_size = DEFAULT_CELL;
interval = DEFAULT_INTERVAL;
fill_ratio = DEFAULT_RATIO;
run = false;
//Initialize grid with random values
//NOTE: Add JOptionPane for option to define fill rate and board size?
Solution
When you are ready to worry about making the engine fast, read Michael Abrash. His Black Book has a chapter dedicated to fast life engines.
In your current implementation, you have your life engine tangled with your swing UI. That's probably a mistake; at a minimum, it will be a lot easier to check your engine for correctness if they are decoupled from the UI. For example, one easy way to test a new fast algorithm is to verify that it produces the exact same answers as a slow reliable approach. That's trivial to do with a unit test, if you have a direct way to read the state of the cells of the board.
But your basic arrangement to start should likely resemble: a representation of the game state, an engine that knows how to compute the next state from the current state, and broadcasts events describing the changes, and a view that knows how to represent the changes in the UI.
The view will need to know how to talk to Spring, but the engine doesn't care - as far as the engine is concerned, you could be drawing the results with ASCII characters.
That's your game state. I'm not too keen on using
That's your engine - it's really got two different pieces to it; a factory that creates a new state, and then a calculator that figures out what the state looks like. The only thing that is missing (because in your design, it is implicit), is a way to notify the rest of the application of what the changes are. It's completely fine to broadcast the entire grid as the "new state", but you will eventually discover faster alternatives.
There's your view. He needs to know how to draw live and dead cells, but doesn't need to know anything about the rules that choose cells which are alive or dead.
Make separate classes for each of those, and wire them together.
Notes on style: your code here is difficult to read, because of some of the choices you are making. For readability among a large, respectful audience... make different choices.
Where possible, you should prefer to inject your dependencies.
A better alternative would be to pass in a correctly initialized Board
Oh my goodness - never drop the "optional" braces when there is any chance of confusion. Better still, never drop the braces -- we have plenty to spare.
In your current implementation, you have your life engine tangled with your swing UI. That's probably a mistake; at a minimum, it will be a lot easier to check your engine for correctness if they are decoupled from the UI. For example, one easy way to test a new fast algorithm is to verify that it produces the exact same answers as a slow reliable approach. That's trivial to do with a unit test, if you have a direct way to read the state of the cells of the board.
But your basic arrangement to start should likely resemble: a representation of the game state, an engine that knows how to compute the next state from the current state, and broadcasts events describing the changes, and a view that knows how to represent the changes in the UI.
The view will need to know how to talk to Spring, but the engine doesn't care - as far as the engine is concerned, you could be drawing the results with ASCII characters.
private Color[][] grid;That's your game state. I'm not too keen on using
Color as the type, rather than boolean, or an enum of your own. Were you planning to do something clever if one of the cells turns red?Color[][] newGrid = new Color[board_size.height][board_size.width];
for (int h = 0; h = 0) && (h + i = 0) && (w + j < board_size.width)
&& !(i == 0 && j == 0) && (grid[h + i][w + j] == black))
surrounding++;
//Generate next iteration
if (grid[h][w] == black){
if (surrounding == 2 || surrounding == 3)
newGrid[h][w] = black;
else newGrid[h][w] = white;
}
else if(surrounding == 3)
newGrid[h][w] = black;
}
for (int h = 0; h < board_size.height; h++){
for (int w = 0; w < board_size.width; w++){
grid[h][w] = newGrid[h][w];
}
}
}That's your engine - it's really got two different pieces to it; a factory that creates a new state, and then a calculator that figures out what the state looks like. The only thing that is missing (because in your design, it is implicit), is a way to notify the rest of the application of what the changes are. It's completely fine to broadcast the entire grid as the "new state", but you will eventually discover faster alternatives.
for (int h = 0; h < board_size.height; h++)
for (int w = 0; w < board_size.width; w++){
try{
if (grid[h][w] == black)
g.setColor(black);
else g.setColor(white);
g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size);
} catch (ConcurrentModificationException cme){}
}There's your view. He needs to know how to draw live and dead cells, but doesn't need to know anything about the rules that choose cells which are alive or dead.
Make separate classes for each of those, and wire them together.
Notes on style: your code here is difficult to read, because of some of the choices you are making. For readability among a large, respectful audience... make different choices.
private static final Color white = Color.WHITE, black = Color.BLACK;- One variable definition per line.
- Static constants are normally spelled with UPPER_CASE names. Notice the example provided by java.awt.Color
Where possible, you should prefer to inject your dependencies.
public CA_DriverV2(){
board = new Board();
board.setBackground(white);A better alternative would be to pass in a correctly initialized Board
public CA_DriverV2(Board board){
this.board = board;CA_DriverV2 is an opaque class name; much better to spell out CellularAutomataDriverV2. Better still, admit that it's ConwaysLifeDriver.for (int i = -1; i = 0) && (h + i = 0) && (w + j < board_size.width)
&& !(i == 0 && j == 0) && (grid[h + i][w + j] == black))
surrounding++;Oh my goodness - never drop the "optional" braces when there is any chance of confusion. Better still, never drop the braces -- we have plenty to spare.
Code Snippets
private Color[][] grid;Color[][] newGrid = new Color[board_size.height][board_size.width];
for (int h = 0; h < board_size.height; h++)
for (int w = 0; w < board_size.width; w++) {
int surrounding = 0;
//Count black neighbors
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if((h + i >= 0) && (h + i < board_size.height)
&& (w + j >= 0) && (w + j < board_size.width)
&& !(i == 0 && j == 0) && (grid[h + i][w + j] == black))
surrounding++;
//Generate next iteration
if (grid[h][w] == black){
if (surrounding == 2 || surrounding == 3)
newGrid[h][w] = black;
else newGrid[h][w] = white;
}
else if(surrounding == 3)
newGrid[h][w] = black;
}
for (int h = 0; h < board_size.height; h++){
for (int w = 0; w < board_size.width; w++){
grid[h][w] = newGrid[h][w];
}
}
}for (int h = 0; h < board_size.height; h++)
for (int w = 0; w < board_size.width; w++){
try{
if (grid[h][w] == black)
g.setColor(black);
else g.setColor(white);
g.fillRect(h * cell_size, w * cell_size, cell_size, cell_size);
} catch (ConcurrentModificationException cme){}
}private static final Color white = Color.WHITE, black = Color.BLACK;public CA_DriverV2(){
board = new Board();
board.setBackground(white);Context
StackExchange Code Review Q#64207, answer score: 2
Revisions (0)
No revisions yet.