Predator, Prey interactions

Predator and Prey Automata Javascript

In this example, we'll look at a more sophisticated (though still very simple) Cellular Automata example dealing with predator / prey interactions. Cells will follow these simple rules:

Prey

  • Each turn, produce new prey cells in all empty neighbouring cells
  • After each turn, the cell has a chance to die depending on the prey death rate

Predators

  • Each turn, consume any neighbouring prey and replace the cells with new predators
  • If this cell has not consumed prey, die
  • After each turn, the cell has a random chance to die depending on predator death rate
Predator, Prey Cellular Automata example

cell.js

We begin by creating two lists; one for all currently active cells, allCells, and one for all cells newly created during this update, newCells. We also use a flag, updatePrey, to tell the simulation whether we're currently updating prey or predators. The different cell types take it in turns during update steps of the simulation. Additionally, we have variables to determine the random death rates of predator and prey cells.


var allCells = [];
var newCells = [];

var updatePrey = true;

var drPred = 0.3;
var drPrey = 0.2;

The cell class begins with a constructor, similar to previous examples, that keeps track of the cells position, whether or not it is alive, and whether or not it is prey:


class Cell
{
	constructor(cx, cy, cp)
	{
		this.x		= cx;
		this.y		= cy;
		this.tile	= (cy * Map.width) + cx;
		this.prey	= cp;
		this.alive	= true;
		
		newCells.push(this);
		Map.tiles[this.tile].cell = this;
	}

The cell class also has a method to fetch a list of all valid neighbouring tiles in both cardinal and diagonal directions, and a method to kill the cell when it is consumed or has starved:


	getNeighbours()
	{
		var n = [];
		
		for(var y = (this.y - 1); y<= (this.y + 1); y++)
		{
			for(var x = (this.x - 1); x <= (this.x + 1); x++)
			{
				if(y < 0 || x < 0 || x >= Map.width || y >= Map.height) { continue; }
				
				n.push((y * Map.width) + x);
			}
		}
		
		return n;
	}
	
	kill()
	{
		this.alive = false;
		Map.tiles[this.tile].cell = null;
	}

The update method begins by getting the list of neighbouring tiles. If the cell is prey, it will then create new cells in all empty neighbours:


	update()
	{
		var n = this.getNeighbours();
		
		if(this.prey)
		{
			for(x in n)
			{
				if(Map.tiles[n[x]].cell==null)
				{
					new Cell(Map.tiles[n[x]].x, Map.tiles[n[x]].y, true);
				}
			}
		}

If the cell is a predator, however, it will destroy all neighbouring prey and create new predator cells in their place. If the cell was unable to consume prey, we kill it.


		else
		{
			var prey = [];
			
			for(var x in n)
			{
				if(Map.tiles[n[x]].cell!=null && Map.tiles[n[x]].cell.prey)
				{
					prey.push(n[x]);
					
					Map.tiles[n[x]].cell.kill();
					new Cell(Map.tiles[n[x]].x, Map.tiles[n[x]].y, false);
				}
			}
			
			if(!prey.length) { this.kill(); }
		}

This is the end of the update method for our cells, and we also close the cell class:


		return false;
	}
};

In the updateCells method, to update all cells, we begin by calling the update method for every cell that is either predator or prey, depending on which we're currently updating:


function updateCells()
{
	for(var x in allCells)
	{
		if(allCells[x].prey==updatePrey) { allCells[x].update(); }
	}

We then add in new cells created during this update:


	for(var x in newCells) { if(newCells[x].alive) { allCells.push(newCells[x]); } }
	newCells.splice(0, newCells.length);

We then loop through cells again, and if they are of the type currently being updated, we give them a random chance to die.


	for(var x in allCells)
	{
		if((updatePrey && allCells[x].prey && Math.random()<drPrey) ||
			(!updatePrey && !allCells[x].prey && Math.random()<drPred)) { allCells[x].kill(); }
	}

Finally, we'll remove any cells that are no longer alive, and flip the updatePrey boolean value so the other type of cell will get updated next.


	var toRemove = [];
	for(var x in allCells)
	{
		if(!allCells[x].alive) { toRemove.push(allCells[x]); }
	}
	while(toRemove.length > 0)
	{
		allCells.splice( allCells.indexOf(toRemove[0]), 1 );
		toRemove.shift();
	}
	
	updatePrey = !updatePrey;
}
Page loaded in 0.009 second(s).