Code in Processing, its javascript variant p5

var typeColor = [];
var blockSize = 100;
var grid = [];
var treated = false; // to allow treatment

var INERT = 0;
var FIBROBLAST = 1;
var CANCER = 2;

var NONE = 0;
var BASIC = 0.5;
var STIMULATED = 1.0;

// inert = healthy normal tissue, not fibroblast
// fibroblast = fibroblast stimulated by cancer to reproduce
// cancer
// stabilised = cancer + fibroblast

//inert, fibroblast, cancer -- neighbour
var game = [
  [NONE, NONE, NONE], // healthy tissue
  [NONE, NONE, STIMULATED], // fibroblast
  [BASIC, BASIC, BASIC], // cancer
];

function setup() {
  var xGrid = round(0.9 * windowWidth / blockSize);
  var yGrid = round(0.9 * windowHeight / blockSize);
  var canvas = createCanvas(xGrid * blockSize, yGrid * blockSize, WEBGL);

  typeColor[INERT] = color(255, 0);
  typeColor[FIBROBLAST] = color(111, 143, 190); // sky
  typeColor[CANCER] = color(234, 201, 154); // beige // color(176, 47, 28); // red

  makeGrid(xGrid, yGrid, min(xGrid, yGrid));
  setNeighbors();
}

function makeGrid(xGrid, yGrid, zGrid) {
  for (var x = 0; x <= xGrid; x++) {
    grid[x] = [];
    for (var y = 0; y <= yGrid; y++) {
      grid[x][y] = [];
      for (var z = 0; z <= zGrid; z++) {
        var type = INERT // most of the tissue is healthy

        if (random(50) < 1)
          type = FIBROBLAST; // loose scattering of fibroblasts

        if (dist(x, y, z, xGrid / 2, yGrid / 2, zGrid / 2) < 3) { // blob of cancer cells in the middle
          type = CANCER;
        }
        grid[x][y][z] = new Element(x, y, z, type);
      }
    }
  }
}

function setNeighbors() {
  for (var x = 0; x < grid.length; x++) {
    for (var y = 0; y < grid[x].length; y++) {
      for (var z = 0; z < grid[x][y].length; z++) {
        grid[x][y][z].setNeighbors();
      }
    }
  }
}

function draw() {
  translate(-width / 2, -height / 2, -max(width, height) / 1.4);

  for (var i = 0; i < grid.length * grid[0].length * grid[0][0].length; i++) update();
  treat();

  for (var x = 0; x < grid.length; x++) {
    for (var y = 0; y < grid[x].length; y++) {
      for (var z = 0; z < grid[x][y].length; z++) {
        grid[x][y][z].draw();
      }
    }
  }
  ambientLight(100, 80, 80);
  pointLight(500, 200, 200, mouseX / width - 0.5, 0.5 - mouseY / height, 0);

}

function update() {
  var element = random(random(random(grid)));

  var fitness = 0;
  for (var i = 0; i < element.neighbors.length; i++) {
    fitness += game[element.type][element.neighbors[i].type] / element.neighbors.length;
  }

  if (fitness > random(10)) { // if sufficiently fit, assimilate a neighbour
    random(element.neighbors).change(element.type);
  }
}

function treat() {
  if (treated) return;

  var count = 0;
  for (var x = 0; x < grid.length; x++) {
    for (var y = 0; y < grid[x].length; y++) {
      for (var z = 0; z < grid[x][y].length; z++) {
        if (grid[x][y][z].type == CANCER) {
          count++;
        }
      }
    }
  }
  if (count < grid.length * grid[0].length * grid[0][0].length / 4) return;

  for (x = 0; x < grid.length; x++) {
    for (y = 0; y < grid[x].length; y++) {
      for (z = 0; z < grid[x][y].length; z++) {
        switch (grid[x][y][z].type) {
          case INERT:
            break;
          case FIBROBLAST:
            break;
          case CANCER:
            if (random(grid[x][y][z].neighbors).type == FIBROBLAST) {
              break;
            }
            grid[x][y][z].type = INERT;
            break;
        }
      }
    }
  }
  treated = true;
}

function Element(X, Y, Z, t) {
  this.x = X;
  this.y = Y;
  this.z = Z;
  this.neighbors = [];
  this.type = t;

  this.setNeighbors = function() {
    if (this.x > 0) this.neighbors.push(grid[this.x - 1][this.y][this.z]); // left
    if (this.y > 0) this.neighbors.push(grid[this.x][this.y - 1][this.z]); // down
    if (this.z > 0) this.neighbors.push(grid[this.x][this.y][this.z - 1]); // in
    if (this.x < grid.length - 1) this.neighbors.push(grid[this.x + 1][this.y][this.z]); // right
    if (this.y < grid[0].length - 1) this.neighbors.push(grid[this.x][this.y + 1][this.z]); // up
    if (this.z < grid[0][0].length - 1) this.neighbors.push(grid[this.x][this.y][this.z + 1]); // out
  };

  this.draw = function() {
    push();
    translate(this.x * blockSize, this.y * blockSize, this.z * blockSize);
    ambientMaterial(typeColor[this.type]);
    sphere(blockSize / 2);
    pop();
  };

  this.change = function(t) {
    // if (this.type == FIBROBLAST) {
    //   return;
    // }
    this.type = t;
  };
}