Conway's Game of Life with Dynamic Environmental Noise

Castle Walls: A Custom Twist on Conway’s Game of Life

This is a generative animation built with Processing.

The resulting visuals evoke a sense of continuous transformation—almost like ancient stone walls being consumed by fire and then slowly rebuilding themselves.

Larger Cell Grid Example

I wanted to experiment with custom rules for Conway’s Game of Life. Specifically, I wondered: What if the underlying rules of the system evolved over time, much like a changing physical environment?

To implement this, I introduced 2D Perlin noise to act as a global "environmental factor" that continuously alters the conditions for a cell’s birth and death. Shifting climates, sudden wildfires, droughts, or fertile rainfall—these environmental waves dynamically seal the fate of every cell on the grid!

Processing Source Code

Please feel free to use this Processing example code under the terms of the GPL.
I’d be honored and thrilled to see any works you create based on this code!

Note: This script renders frames directly to the /frames directory rather than relying only on real-time playback. You can stitch these image files together to create a smooth, high-resolution video animation.

/**
 * Castle Walls.
 * an animation with the game of life of my own rule.
 * 
 * @author @deconbatch
 * @version 0.1
 * Processing 3.2.1
 * created : 2019/09/23
 */

void setup() {
  size(720, 720);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noStroke();
  noLoop();
}

void draw() {
  int   frmMax   = 24 * 16; // for 24fps x 16s animation
  float cellSize = 4.0;
  
  int canvasW = floor(width / cellSize);
  int canvasH = floor(height / cellSize);
  Cell[][] cells = new Cell[canvasW][canvasH];

  // cells constraction
  for (int x = 0; x < canvasW; x++) {
    for (int y = 0; y < canvasH; y++) {
      cells[x][y] = new Cell();
      cells[x][y].setLife(random(90.0));
    }
  }

  // set neighbor cells
  for (int x = 0; x < canvasW; x++) {
    for (int y = 0; y < canvasH; y++) {
      int mX = getMinus(x, canvasW);
      int mY = getMinus(y, canvasH);
      int pX = getPlus(x, canvasW);
      int pY = getPlus(y, canvasH);
      
      cells[x][y].setNeighbor(new Cell[] {
          // 8 directions
          cells[mX][mY],
          cells[pX][mY],
          cells[mX][pY],
          cells[pX][pY],
          cells[x][mY],
          cells[x][pY],
          cells[mX][y],
          cells[pX][y]
        });
    }
  }

  for (int frmCnt = 0; frmCnt < frmMax; frmCnt++) {
    background(0.0, 0.0, 100.0, 100.0);
    float frmRatio  = map(frmCnt, 0, frmMax, 0.0, 1.0);

    // game of life calculation
    for (int x = 0; x < canvasW; x++) {
      for (int y = 0; y < canvasH; y++) {
        cells[x][y].sumLife();
      }
    }
    
    for (int x = 0; x < canvasW; x++) {
      for (int y = 0; y < canvasH; y++) {
        float envFactor = noise(frmRatio * 2.5, x * 0.05, y * 0.05);
        // game of life calculation
        cells[x][y].liveOrDie(envFactor);
        
        // draw cells
        float eHue = map(envFactor, 0.0, 1.0, 300.0, 360.0 + 300.0) % 360.0;
        float eSat = cells[x][y].getLife() * 40.0;
        float eBri = 100.0 - cells[x][y].getLife();
        float eSiz = cellSize * (100.0 - cells[x][y].getLife() * 0.5) * 0.01;
        fill(eHue, eSat, eBri, 100.0);
        ellipse(x * cellSize, y * cellSize, eSiz, eSiz);
      }
    }

    saveFrame("frames/" + String.format("%04d", frmCnt) + ".png");

  }

  exit();
  
}

/**
 * getMinus
 * calculate the coordinates of the point. take overflow into account.
 * @param  _a      : coordinates of the point, x or y
 * @param  _border : canvas width or height
 */
private int getMinus(int _a, int _border) {
  int ret = _a - 1;
  if (ret < 0) {
    ret = _border - 1;
  }
  return ret;
}

/**
 * getPlus
 * calculate the coordinates of the point. take overflow into account.
 * @param  _a      : coordinates of the point, x or y
 * @param  _border : canvas width or height
 */
private int getPlus(int _a, int _border) {
  int ret = _a + 1;
  if (ret >= _border) {
    ret = 0;
  }
  return ret;
}

/**
 * Cell
 * manage one cell.
 */
private class Cell {

  private float  myLife;
  private float  neighborLives;
  private Cell[] neighbor;

  Cell() {
    myLife = 0.0;
  }

  /**
   * setNeighbor
   * set the neighbor cells.
   * @param  _neighbor : the neighbor cells array
   */
  public void setNeighbor(Cell[] _neighbor) {
    neighbor = new Cell[_neighbor.length];
    for (int i = 0; i < _neighbor.length; i++) {
      neighbor[i] = _neighbor[i];
    }
  }

  /**
   * setLife
   * set the life value of this cell.
   * @param  _life : life value
   */
  public void setLife(float _life) {
    myLife = _life;
  }

  /**
   * getLife
   * get the life value of this cell.
   * @return : life value 0.0 - 100.0
   */
  public float getLife() {
    return constrain(myLife, 0.0, 100.0);
  }

  /**
   * sumLife
   * sum up the life value of neighbor cells.
   */
  public void sumLife() {
    neighborLives = 0.0;
    for (int i = 0; i < neighbor.length; i++) {
      neighborLives += neighbor[i].getLife();
    }
  }

  /**
   * liveOrDie
   * calculate the life value of this cell with deconbatch's game of life rule.
   * @param  _environment : the value that have an impact on calculation
   */
  public void liveOrDie(float _environment) {
    float lifeBorder = 50.0;
    float paramA = map(_environment, 0.0, 1.0, 2.5, 3.5);
    float paramB = map(_environment, 0.0, 1.0, 3.5, 4.5);
    float paramC = map(_environment, 0.0, 1.0, 1.5, 0.5);

    if (myLife < lifeBorder) {
      // I'm dead
      if (neighborLives > lifeBorder * paramA && neighborLives < lifeBorder * paramB) {
        // inject vitality
        myLife += 15.0;
      } else {
        // suck
        myLife -= 15.0;
      }
    } else {
      // I'm alive
      if (neighborLives < lifeBorder * paramC || neighborLives > lifeBorder * paramB) {
        // suck
        myLife -= 20.0;
      }
    }
    myLife = constrain(myLife, 0.0, 100.0);
  }
}


/*
Copyright (C) 2019- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/


 

Next Post Previous Post
No Comment
Add Comment
comment url