We have treatments where the cancer is initially responsive, but then resistant. Robert Gatenby suggests we could manage the cancer by pausing treatment now and then. Without treatment, the tumour becomes sensitive again. Resuming treatment, the tumour shrinks.

In the simulation, click or tap to toggle treatment on or off. A blue background shows treatment being applied. Yellow cancer cells are sensitive to the treatment; red are resistant.

 
Untreated growth dynamics

Untreated growth dynamics

The simulation is built on game theory rules. The foreground colour is the cell type, and the background is the neighbour type. The signs: +, -, 0 is the effect of a neighbour on the cell: positive, negative, neutral.

Healthy cells (white) suffer from cancer cell neighbours. Sensitive cancer cells (yellow) do well in healthy tissue. Resistant cells (red) ignore their environment.

Under treatment, the dynamics change — but only a little. Sensitive cells now suffer in healthy tissue, and healthy cells propagate in sensitive tumour.

Growth dynamics under treatment

Growth dynamics under treatment

 

Code in Processing, its javascript variant p5

var blockSize = 10;
var grid = [];

var HEALTHY = 0;
var SENSITIVE = 1;
var RESISTANT = 2;
var typeColor = [];

var LOW = 1;
var NEUTRAL = 2;
var HIGH = 3;

var game = [
  // healthy, sensitive, resistant -- neighbors
  [NEUTRAL, LOW, LOW], // healthy cells
  [HIGH, NEUTRAL, NEUTRAL], // sensitive cells
  [NEUTRAL, NEUTRAL, NEUTRAL] // resistant cells -- individual
];
var drug = [
  // healthy, sensitive, resistant -- neighbors
  [NEUTRAL, HIGH, LOW], // healthy cells
  [LOW, NEUTRAL, NEUTRAL], // sensitive cells
  [NEUTRAL, NEUTRAL, NEUTRAL] // resistant cells -- individual
];

var TREAT = false;

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

  background(255);
  noStroke();
  scale(blockSize);

  typeColor[HEALTHY] = color(255);
  typeColor[SENSITIVE] = color(234, 201, 154);
  typeColor[RESISTANT] = color(176, 47, 28);

  for (var x = 0; x < xGrid; x++) {
    grid[x] = [];
    for (var y = 0; y < yGrid; y++) {
      grid[x][y] = new Element(x, y, HEALTHY); // most of the tissue is healthy
      if (dist(x, y, xGrid / 2, yGrid / 2) < 10) { // blob of mixed cancer cells in the middle
        if (random([0, 1])) {
          grid[x][y].change(SENSITIVE);
        } else {
          grid[x][y].change(RESISTANT);
        }
      }
    }
  }
}

function draw() {
  scale(blockSize);

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

function touchEnded() {
  TREAT = !TREAT;
  refresh();
}

function update() {
  var element = random(random(grid));
  if (element.x === 0 || element.x === grid.length - 1) return;
  if (element.y === 0 || element.y === grid[0].length - 1) return;

  var neighbors = [];
  neighbors.push(grid[element.x - 1][element.y]); // left
  neighbors.push(grid[element.x + 1][element.y]); // right
  neighbors.push(grid[element.x][element.y - 1]); // down
  neighbors.push(grid[element.x][element.y + 1]); // up

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

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

function Element(X, Y, t) {
  this.x = X;
  this.y = Y;

  this.draw = function() {
    fill(typeColor[this.type]);
    if (TREAT && this.type === HEALTHY) {
      fill(111, 143, 190);
    }
    rect(this.x, this.y, 1, 1);
  };

  this.change = function(t) {
    this.type = t;
    this.draw();
  };
  
  this.change(t);
}