// 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); } }