PHY 411-506 Home Home  |  Course Outline  |  Lectures  |  Homework  |  Files

Lecture 35: April 16


Conway's Game of Life

For an introduction to the Game of Life, see Wonders of Math: What is the Game of Life?.

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


UB Physics Home Questions or comments: phygons@acsu.buffalo.edu