Rock, Paper, Scissors

Cell class

Now we'll create our Cell class and some associated information in the cell.js file. This file begins with two arrays, a list of active cells, and a list of new cells which have been created during this update loop (once the loop is completed, the new cells are added to the allCells list, and the newCells list is cleared):


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

We also use a global flag in this example, biasDir, which states whether or not we want cells to prefer to "hunt" by prioritizing their target in a clockwise order. If the value is false, the target of the cell is selected at random from neighbouring cells.


var biasDir	= false;

We also create a list of possible cell types. Each type specifies its colour (for vizualisation purposes), and the type of prey it consumes:


var cellTypes = {
	"rock"		: { "colour" : "#00cc00", "prey" : "scissors" },
	"paper"		: { "colour" : "#0000cc", "prey" : "rock" },
	"scissors"	: { "colour" : "#cc0000", "prey" : "paper" }
};

In our cell class itself, we create the constructor for the Cell, which takes the x, y position at which the cell will be created, as well as the cells type (ct) and its prey type (cp). The new cell is added to the newCells list, and the tile at the position where the cell is being created is given a pointer to this cell:


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

Our Cell class also has getNeighbours method, which simply gets the tile index of each neighbouring tile to the cell in the cardinal directions, providing the tile is not outside of map bounds:


	getNeighbours()
	{
		var n = [];
		
		if(this.y > 0) { n.push( ((this.y - 1) * Map.width) + this.x ); }
		if(this.x > 0) { n.push( (this.y * Map.width) + this.x - 1); }
		if(this.y < (Map.height - 1)) { n.push( ((this.y + 1) * Map.width) + this.x ); }
		if(this.x < (Map.width - 1)) { n.push( (this.y * Map.width) + this.x + 1); }
		
		return n;
	}

The kill method for our Cell sets the alive flag to false, and removes the pointer from the cells tile to the cell itself:


	kill()
	{
		this.alive = false;
		Map.tiles[this.tile].cell = null;
	}

We'll now begin the update function for the Cell class, that is called for every cell every update tick. The method begins by getting a list of valid neighbouring tiles, n, a flag ate, that keeps track of whether or not the cell has consumed a prey type cell, and a list of possible prey on neighbour tiles to select from at random:


	update()
	{
		var n = this.getNeighbours();
		var ate = false;
		
		var prey = [];

We then loop through all of the neighbouring tiles, looking for prey for this cell. If a prey type cell is found and biasDir (directional biasing) is on, we consume the first suitable cell we found. Otherwise, a random suitable prey cell is selected at random. If the cell has eaten, it has completed the update function and we return true:


		for(var x in n)
		{
			if(Map.tiles[n[x]].cell!=null && Map.tiles[n[x]].cell.type == this.prey)
			{
				if(biasDir)
				{
					Map.tiles[n[x]].cell.kill();
					new Cell(Map.tiles[n[x]].x, Map.tiles[n[x]].y, this.type, this.prey);
					
					ate = true;
					break;
				}
				else { prey.push(n[x]); }
			}
		}
		
		if(prey.length)
		{
			var p = prey[Math.floor(Math.random() * prey.length)];
			
			Map.tiles[p].cell.kill();
			new Cell(Map.tiles[p].x, Map.tiles[p].y, this.type, this.prey);
			
			ate = true;
		}
		
		if(ate) { return true; }

Finally, if the cell hasn't eaten, we select a random empty cell to move in to if one is available:


		var open = [];
		
		for(var x in n)
		{
			if(Map.tiles[n[x]].cell == null)
			{
				open.push(n[x]);
			}
		}
		
		if(open.length)
		{
			var d = open[Math.floor(Math.random() * open.length)];
			
			Map.tiles[this.tile].cell = null;
				
			this.x = Map.tiles[d].x;
			this.y = Map.tiles[d].y;
			this.tile = d;
			
			Map.tiles[d].cell = this;
			
			return true;
		}
		
		return false;
	}
};

Finally, after the Cell class, we'll create an update function, which will call the update for each of the cells in turn in the allCells list:


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

After updating, we add all the newCells to the allCells list, clear the newCells list, and remove all cells that have had their alive flag changed to false:


	for(var x in newCells) { if(newCells[x].alive) { allCells.push(newCells[x]); } }
	newCells.splice(0, newCells.length);
	
	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();
	}
}
Page loaded in 0.01 second(s).