Home
|
Course Outline
|
Lectures
|
Homework
|
Files
The following applet is based on the program GameOfLife.java, which is a fancier version of PROGRAM ca2 on pages 506-509 of the textbook. The user interface is modeled after Michael Creutz's xautomalab.c program from his XToys collection.
Here is the code which generates this applet:
// GameOfLife.java
import comphys.graphics.*;
import java.awt.*;
import java.awt.event.*;
public class GameOfLife extends Animation {
boolean[][] neighborhood = new boolean[3][3];
boolean[] birthRule = new boolean[9];
boolean[] deathRule = new boolean[9];
void setConway () {
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
neighborhood[i][j] = true;
neighborhood[1][1] = false;
for (int i = 0; i < 9; i++) {
deathRule[i] = true;
birthRule[i] = false;
}
birthRule[3] = true;
deathRule[2] = deathRule[3] = false;
}
int maxL = 198;
int L = 50;
boolean[][] cell = new boolean[maxL + 2][maxL + 2];
boolean[][] newCell = new boolean[maxL + 2][maxL + 2];
boolean[][] fossilCell = new boolean[maxL + 2][maxL + 2];
static final int DEAD = 0;
static final int ALIVE = DEAD + 1;
static final int PERIODIC = ALIVE + 1;
int boundary = PERIODIC;
void updateCells () {
if (boundary == PERIODIC) {
for (int x = 1; x <= L; x++) {
newCell[x][0] = newCell[x][L];
newCell[x][L+1] = newCell[x][1];
}
for (int y = 1; y <= L; y++) {
newCell[0][y] = newCell[L][y];
newCell[L+1][y] = newCell[1][y];
}
newCell[0][0] = newCell[0][L];
newCell[0][L+1] = newCell[0][1];
newCell[L+1][0] = newCell[1][0];
newCell[L+1][L+1] = newCell[L+1][1];
} else if (boundary == DEAD) {
for (int i = 0; i <= L+1; i++)
newCell[0][i] = newCell[i][0]
= newCell[L+1][i] = newCell[i][L+1]
= false;
} else if (boundary == ALIVE) {
for (int i = 0; i <= L+1; i++)
newCell[0][i] = newCell[i][0]
= newCell[L+1][i] = newCell[i][L+1]
= true;
}
for (int y = 0; y <= L + 1; y++)
for (int x = 0; x <= L + 1; x++) {
if (newCell[x][y])
fossilCell[x][y] = false;
else if (cell[x][y])
fossilCell[x][y] = true;
cell[x][y] = newCell[x][y];
}
}
static final int RANDOM = ALIVE + 1;
int initialConfiguration = RANDOM;
static final int GOSPER = RANDOM + 1;
String[] GosperGliderGun = {
"000000000000000000000000100000000000",
"000000000000000000000010100000000000",
"000000000000110000001100000000000011",
"000000000001000100001100000000000011",
"110000000010000010001100000000000000",
"110000000010001011000010100000000000",
"000000000010000010000000100000000000",
"000000000001000100000000000000000000",
"000000000000110000000000000000000000",
};
void setConfiguration () {
if (initialConfiguration == RANDOM) {
for (int y = 1; y <= L; y++) {
for (int x = 1; x <= L; x++) {
if (Math.random() < 0.5)
newCell[x][y] = true;
else
newCell[x][y] = false;
}
}
} else if (initialConfiguration == ALIVE) {
for (int y = 1; y <= L; y++) {
for (int x = 1; x <= L; x++) {
newCell[x][y] = true;
}
}
} else { // DEAD or prepare for pattern
for (int y = 1; y <= L; y++) {
for (int x = 1; x <= L; x++) {
newCell[x][y] = false;
}
}
}
if (initialConfiguration == GOSPER) {
String[] s = GosperGliderGun;
int rows = s.length;
int cols = s[0].length();
int x0 = (L - cols) / 2;
int y0 = (L + rows) / 2;
for (int ix = 0; ix < cols; ix++) {
for (int iy = 0; iy < rows; iy++) {
if (s[iy].charAt(ix) - '0' == ALIVE) {
int x = x0 + ix;
int y = y0 - iy;
if (x >= 0 && x <= L && y >= 0 && y <= L)
newCell[y][x] = true;
}
}
}
}
for (int y = 0; y <= L + 1; y++)
for (int x = 0; x <= L + 1; x++)
cell[x][y] = fossilCell[x][y] = false;
updateCells();
takeCensus();
}
int births;
int adults;
int deaths;
boolean fossil;
int fossils;
void takeCensus () {
births = adults = deaths = fossils = 0;
for (int y = 1; y <= L; y++) {
for (int x = 1; x <= L; x++) {
if (newCell[x][y]) {
if (cell[x][y])
++adults;
else
++births;
} else {
if (cell[x][y])
++deaths;
else
if (fossilCell[x][y])
++fossils;
}
}
}
}
int t;
boolean initialize;
boolean userHasChangedCells;
void timeStep () {
if (initialize)
initial();
if (userHasChangedCells) {
updateCells();
userHasChangedCells = false;
}
++t;
for (int y = 1; y <= L; y++) {
for (int x = 1; x <= L; x++) {
int sumOfNeighbors = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (neighborhood[i][j])
if (cell[x + j - 1][y + i - 1])
++sumOfNeighbors;
if (cell[x][y]) {
if (deathRule[sumOfNeighbors])
newCell[x][y] = false;
} else {
if (birthRule[sumOfNeighbors])
newCell[x][y] = true;
}
}
}
takeCensus();
}
boolean stillLife () {
for (int y = 0; y <= L + 1; y++)
for (int x = 0; x <= L + 1; x++)
if (cell[x][y] != newCell[x][y])
return false;
return true;
}
void initial () {
t = 0;
setConfiguration();
initialize = false;
}
boolean needToClear;
boolean small;
boolean grow = true;
boolean grid;
boolean userHasChangedL;
class Ecosystem extends Plot implements MouseListener, MouseMotionListener {
int pixels = 400;
Ecosystem () {
setSize(pixels, pixels);
addMouseListener(this);
addMouseMotionListener(this);
}
int x0;
int y0;
int d;
boolean scribble;
int xScribble;
int yScribble;
public void paint () {
setWindow(0, pixels, 0, pixels);
d = (int) (pixels / (double) (L + 2));
if (d < 1)
d = 1;
int dRect = d - 1;
if (small)
x0 = y0 = (pixels - L - 2) / 2;
else
x0 = y0 = (pixels - (L + 2) * d) / 2;
if (grid) {
osg.clearRect(0, 0, pixels, pixels);
setColor("gray");
int x1 = x0 + (L + 2) * d;
if (small)
x1 = x0 + (L + 2);
for (int i = 0; i <= L + 2; i++) {
int x = x0 + i * d;
if (small)
x = x0 + i;
osg.drawLine(x, x0, x, x1);
osg.drawLine(x0, x, x1, x);
}
grid = false;
return;
}
if (scribble) {
if (grow)
setColor("red");
else
setColor("black");
if (small) {
osg.drawLine(x0 + xScribble, y0 + yScribble,
x0 + xScribble, y0 + yScribble);
} else {
int x = x0 + xScribble * d;
int y = y0 + yScribble * d;
osg.fillRect(x, y, dRect, dRect);
}
scribble = false;
return;
}
if (needToClear) {
osg.clearRect(0, 0, pixels, pixels);
needToClear = false;
}
// outline boundary
if (!small) {
if (fossil)
setColor("magenta");
else
setColor("yellow");
osg.drawRect(x0 - 1, y0 - 1, (L + 2) * d, (L + 2) * d);
osg.drawRect(x0 + d - 1, y0 + d - 1, L * d, L * d);
}
for (int i = 0; i <= L + 1; i++) {
for (int j = 0; j <= L + 1; j++) {
if (newCell[i][j]) {
if (cell[i][j] || i == 0 || i == L + 1
|| j == 0 || j == L + 1)
setColor("green");
else
setColor("red");
} else {
if (cell[i][j])
setColor("black");
else if (fossil && fossilCell[i][j])
setColor("lightGray");
else
setColor("white");
}
if (small) {
osg.drawLine(x0 + j, y0 + i, x0 + j, y0 + i);
} else {
int x = x0 + j * d;
int y = y0 + i * d;
osg.fillRect(x, y, dRect, dRect);
}
}
}
}
int mouseX;
int mouseY;
boolean toggle;
void editCell () {
int x = mouseX - x0;
int y = mouseY - y0;
if (!small) {
x = (int) (x / (double) d);
y = (int) (y / (double) d);
}
if (x > 0 && x <= L && y > 0 && y <= L) {
boolean newValue = grow;
if (toggle)
newValue = !newCell[y][x];
toggle = false;
newCell[y][x] = newValue;
if (boundary == PERIODIC) {
if (x == 1)
newCell[y][L + 1] = newValue;
if (x == L)
newCell[y][0] = newValue;
if (y == 1)
newCell[L + 1][x] = newValue;
if (y == L)
newCell[0][x] = newValue;
if (y == 1 && x == 1)
newCell[L + 1][L + 1] = newValue;
if (y == 1 && x == L)
newCell[L + 1][0] = newValue;
if (y == L && x == 1)
newCell[0][L + 1] = newValue;
if (y == L && x == L)
newCell[0][0] = newValue;
}
xScribble = x;
yScribble = y;
scribble = true;
repaint();
}
}
public void mouseClicked (MouseEvent me) { }
public void mouseEntered (MouseEvent me) {
if (userHasChangedL) {
initial();
needToClear = true;
userHasChangedL = false;
repaint();
}
}
public void mouseExited (MouseEvent me) { }
public void mousePressed (MouseEvent me) {
mouseX = me.getX();
mouseY = me.getY();
toggle = true;
editCell();
}
public void mouseReleased (MouseEvent me) {
repaint();
}
public void mouseMoved (MouseEvent me) { }
public void mouseDragged (MouseEvent me) {
mouseX = me.getX();
mouseY = me.getY();
editCell();
}
}
class RuleWindow extends Plot implements MouseListener {
int xPixels = 400;
int yPixels = 150;
Color bgColor = new Color(255, 250, 245);
RuleWindow () {
setSize(xPixels, yPixels);
addMouseListener(this);
}
// locations of clickable squares of side unit pixels
int unit, maxNeighbors;
int xNeighborhood, xBirth, xDeath;
int yNeighborhood, yBirth, yDeath;
public void paint () {
setWindow(0, xPixels, 0, yPixels);
osg.setColor(bgColor);
osg.fillRect(0, 0, xPixels, yPixels);
int d = unit = 20;
int dy = d;
int y = yNeighborhood = d / 2;
int x = xNeighborhood = xPixels - d / 2 - 3 * d;
maxNeighbors = 0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (neighborhood[i][j]) {
setColor("blue");
++maxNeighbors;
} else
setColor("white");
osg.fillRect(x + j * d, y + i * d, d - 1, d - 1);
}
}
setColor("cyan");
osg.fillRect(x + d, y + d, d - 1, d - 1);
xBirth = x = xPixels - 9 * d - d / 2;
yBirth = y += 4 * d - d / 2;
for (int i = 0; i <= maxNeighbors; i++) {
setColor("blue");
osg.drawRect(x + i * d, y, d, d);
if (birthRule[i]) {
setColor("red");
osg.fillRect(x + i * d + 1, y + 1, d - 1, d - 1);
} else if (fossil) {
setColor("lightGray");
osg.fillRect(x + i * d + 1, y + 1, d - 1, d - 1);
}
}
xDeath = x;
yDeath = y += 2 * d;
for (int i = 0; i <= maxNeighbors; i++) {
setColor("yellow");
osg.drawRect(x + i * d, y, d, d);
if (deathRule[i]) {
setColor("black");
osg.fillRect(x + i * d + 1, y + 1, d - 1, d - 1);
} else {
setColor("green");
osg.fillRect(x + i * d + 1, y + 1, d - 1, d - 1);
}
}
x = d / 2;
y = 2 * dy - d / 2 - 4;
setColor("blue");
osg.drawString("Time step = " + t, x, y);
y += dy;
setColor("green");
osg.drawString("Adults: " + adults, x, y);
setColor("gray");
if (fossil)
osg.drawString("Fossils: " + fossils, x + 100, y);
y += dy;
setColor("red");
osg.drawString("Births: " + births, x, y);
setColor("black");
osg.drawString("Deaths: " + deaths, x + 100, y);
y += dy + d / 2;
setColor("magenta");
osg.drawString("Dead cell comes alive if", x, y);
y += dy;
setColor("blue");
osg.drawString("Number of neighbors =", x, y);
x = xPixels - 9 * d - d / 2 + 8;
for (int i = 0; i <= maxNeighbors; i++)
osg.drawString("" + i, x + i * d, y);
x = d / 2;
y += dy;
setColor("magenta");
osg.drawString("Live cell dies or survives", x, y);
}
public void mouseClicked (MouseEvent me) { }
public void mouseEntered (MouseEvent me) { }
public void mouseExited (MouseEvent me) { }
public void mousePressed (MouseEvent me) {
int x = me.getX();
int y = me.getY();
if (y > yNeighborhood && y < yNeighborhood + 3 * unit) {
if (x > xNeighborhood && x < xNeighborhood + 3 * unit) {
int i = (int) ( (y - yNeighborhood) / (double) unit );
int j = (int) ( (x - xNeighborhood) / (double) unit );
if (!(i == 1 && j == 1))
neighborhood[i][j] = !neighborhood[i][j];
repaint();
}
}
if (y > yBirth && y < yBirth + unit) {
if (x > xBirth && x < xBirth + (maxNeighbors + 1) * unit) {
int j = (int) ( (x - xBirth) / (double) unit );
birthRule[j] = !birthRule[j];
repaint();
}
}
if (y > yDeath && y < yDeath + unit) {
if (x > xDeath && x < xDeath + (maxNeighbors + 1) * unit) {
int j = (int) ( (x - xDeath) / (double) unit );
deathRule[j] = !deathRule[j];
repaint();
}
}
}
public void mouseReleased (MouseEvent me) { }
}
Ecosystem ecosystem;
RuleWindow ruleWindow;
Reader LReader;
Checkbox growBox, smallBox, fossilBox;
Choice boundaryChooser, initialChooser;
public void init () {
setConway();
initial();
needToClear = true;
add(ecosystem = new Ecosystem());
add(ruleWindow = new RuleWindow());
add(LReader = new Reader("L = ", L));
add(growBox = new Checkbox("Create", grow));
growBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
grow = growBox.getState();
}
});
add(smallBox = new Checkbox("Small", small));
smallBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
small = smallBox.getState();
needToClear = true;
}
});
add(fossilBox = new Checkbox("Fossils", fossil));
fossilBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
fossil = fossilBox.getState();
}
});
add(boundaryChooser = new Choice());
boundaryChooser.add("Periodic bndry");
boundaryChooser.add("Dead boundary");
boundaryChooser.add("Live boundary");
boundaryChooser.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
String boundaryType = boundaryChooser.getSelectedItem();
if (boundaryType.equals("Periodic bndry"))
boundary = PERIODIC;
else if (boundaryType.equals("Dead boundary"))
boundary = DEAD;
else
boundary = ALIVE;
updateCells();
}
});
add(initialChooser = new Choice());
initialChooser.add("Start random");
initialChooser.add("Start dead");
initialChooser.add("Start alive");
initialChooser.add("Gosper gun");
initialChooser.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ie) {
String initialType = initialChooser.getSelectedItem();
if (initialType.equals("Start random"))
initialConfiguration = RANDOM;
else if (initialType.equals("Start dead"))
initialConfiguration = DEAD;
else if (initialType.equals("Gosper gun"))
initialConfiguration = GOSPER;
else
initialConfiguration = ALIVE;
}
});
addControlPanel();
}
public void step () {
updateCells();
timeStep();
ecosystem.repaint();
ruleWindow.repaint();
}
public void reset () {
L = LReader.readInt();
if (L > 198) {
L = 198;
LReader.setInt(198);
}
initial();
needToClear = true;
ecosystem.repaint();
ruleWindow.repaint();
}
public static void main (String[] args) {
GameOfLife gameOfLife = new GameOfLife();
gameOfLife.frame("Conway's Game of Life", 430, 800);
}
}