We are about to switch to a new forum software. Until then we have removed the registration on this forum.
After like half a year of not programming with Processing at all, I got back into it to see if I'm still good at it.
I tried to make Conway's game of life. Look at the code and suggest improvements. I'm really not happy with a few things in it, for example the code that checks the state of neighboring cells for each one:
class Cell {
PVector dim, pos;
boolean state, change;
int[] id = new int[2];
Cell(int x, int y) {
id[0] = x;
id[1] = y;
dim = new PVector(width/float(cols), height/float(rows));
pos = new PVector(x*dim.x, y*dim.y);
}
boolean mouseInside() {
return mouseX > pos.x && mouseX < pos.x+dim.x && mouseY > pos.y && mouseY < pos.y+dim.y;
}
void iterate() {
int count = 0;
if (id[0]-1 > -1 && id[1]-1 > -1 && grid[id[0]-1][id[1]-1].state) {
count++;
}
if (id[1]-1 > -1 && grid[id[0]][id[1]-1].state) {
count++;
}
if (id[0]+1 < rows && id[1]-1 > -1 && grid[id[0]+1][id[1]-1].state) {
count++;
}
if (id[0]-1 > -1 && grid[id[0]-1][id[1]].state) {
count++;
}
if (id[0]+1 < rows && grid[id[0]+1][id[1]].state) {
count++;
}
if (id[0]-1 > -1 && id[1]+1 < cols && grid[id[0]-1][id[1]+1].state) {
count++;
}
if (id[1]+1 < cols && grid[id[0]][id[1]+1].state) {
count++;
}
if (id[0]+1 < rows && id[1]+1 < cols && grid[id[0]+1][id[1]+1].state) {
count++;
}
if (state && (count < 2 || count > 3) || !state && count == 3) {
change = true;
}
}
void display() {
if (change) {
state = !state;
change = false;
}
fill(state? color(255, 255, 0) : 200);
stroke(175);
rect(pos.x, pos.y, dim.x, dim.y);
}
}
int rows = 40;
int cols = 40;
boolean drawMode;
Cell[][] grid = new Cell[cols][rows];
void setup() {
size(800, 800);
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j] = new Cell(i, j);
}
}
}
void draw() {
background(0);
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
if (mousePressed && mouseButton != CENTER && grid[i][j].mouseInside()) {
grid[i][j].state = drawMode;
}
grid[i][j].display();
}
}
}
void mousePressed() {
switch (mouseButton) {
case LEFT:
drawMode = true;
break;
case RIGHT:
drawMode = false;
break;
case CENTER:
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j].state = false;
}
}
}
}
void keyPressed() {
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
if (key == ' ') {
grid[i][j].iterate();
}
}
}
}
Comments
Code would be a lot more readable, and much less typing, if you didn't use an array to store the class's x and y.
Well this
can be written as
your whole
iterate()
method could be a nested for loop, both from -1 to 1if not both are 0, hand it over to a function that performs the checks
Recent very concise Conway's Game of Life example, possibly of interest:
In the game of life this is the most time consuming bit. The problem is not so much how to code it but how to avoid doing it at all ;)
Two in-depth code review discussions about optimizing Conway's Game of Life in Java:
These include some good tips for refactoring (an array of offsets), optimization (an empty border and no special bounds checking), and advanced algorithms for high-performance life computing, if you are going in that direction (like hashlife, which uses quadtrees).
These articles are OK but the first is still counting neighbours (every frame) :( and the second is using a 3D boolean array to buffer the intermediate calculations :(
The first is expensive in CPU time and the second in memory.
A much better solution would to have a single 2D byte array for the grid and never have to count neighbours and avoid array bounds checking. All these are easily achievable allowing large boards with good frame rates.
The trick here is in the design of the life board. Assume that we want a board size [w, h] we use a byte array
byte[w+2][h+2]
with the usable/displayable elements being[1-w][1-h]
. The extra 2 columns and 2 rows mean we don't have to do array bounds checking when we are stepping the animation.NOTE we still need to check the x,y coordinates fit inside the usable part of the array when we are setting up the initial board.
Now we come to the cell, there are only two things we need to know
1) Does the cell contain life?
2) How many neighbouring cells contain life?
The second has a value in the range 0-8 so can be encoded in 4 bits, the first is a simple boolean so can be encoded with one bit. The code below uses the four least significant bits (0-3) for the number of neighbours (this simplifies and speeds up the code) and the next bit (4) to say if there is life or not. So the following bytes mean
Now consider what happens when a cell gets new life, all the neighbouring cells have an extra neighbour at the next step. If a cell empties then all the neighbouring cells have one less neighbour at the next step. So what happens is that as we iterate over the board and note the positions of any changes i.e. 'lifr > empty' and 'empty > life`. At the end of the iteration we simply process the changes by incrementing or decrementing the number of neighbours for neighbouring celss.
Anyway the sketch below demonstrates what I mean and shows a 160x120 grid with a Gosper Gun running in it. If any of the code needs further explanation just ask.