// Cloned by Enhanced on 27 Jun 2018 from World "Cloned Cells" by SinfulSalad
// Please leave this clone trail here.
// Cloned by SinfulSalad on 25 Jun 2018 from World "Cells" by SinfulSalad
// Please leave this clone trail here.
// ============ Cells ===============================================================================================
// 'Cells' is a program that displays 4 team of cells trying to infect each other, until total domination is
// accomplished. The rules are simple : once a cell is possessed by a team, it starts generating value, until a maximum
// value is reached. Each cell can, at any given time, split and send one of its halves to another cell to infect it.
// If the value of this half is higher than the value of the cell it is trying to infect, then the infection will
// succeed. The team that wins is the team that controls all cells
// ========== Possible Improvements ==================================================================================
// The score and the chart is designed to keep track of only 4 teams. Maybe code one that works for any number
// of teams.
//
// With this version, each cell behaves individually, there is no 'teamwork' strategy. Maybe implement a
// a 'hivemind' AI that can control all its cells.
//
// One could code a version of this where the user could actually control one of the team
// ===================================================================================================================
// === Start of tweaker's box ========================================================================================
// ===================================================================================================================
// The easiest things to modify are in this box.
// You should be able to change things in this box without being a JavaScript programmer.
// Go ahead and change some of these. What's the worst that could happen?
// These 3 have default values, so this section is optional:
AB.clockTick = 33;
// Speed of run: Step every n milliseconds. Default 100.
AB.maxSteps = 20000;
// Length of run: Maximum length of run in steps. Default 1000.
AB.screenshotStep = 1000;
// Take screenshot on this step. (All resources should have finished loading.) Default 50.
const SKYCOLOR = "#000000"; // this usage will be of color as a string, not as a number
//You can import your own music here
const MUSIC_BACK = '/uploads/sinfulsalad/Forest.mp3' ;
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
// ------------------------------ MUSIC ----------------------------------------
var backmusic = new Audio ( MUSIC_BACK );
function initMusic()
{
backmusic.loop = true; // loop forever
backmusic.play();
$("#w2m_audio1").html( " <img class=audiobutton onclick='backmusic.play();' width=25 src='images/audio.on.1.png' > " );
$("#w2m_audio2").html( " <img class=audiobutton onclick='backmusic.pause();' width=25 src='images/audio.off.1.png' > " );
}
//---------------------------USEFUL FUNCTIONS-----------------------------------
//take the hexadecimal value of a color 'col', and make it brighter or darker
//depending on 'amt'
function lightenDarkenColor(col, amt) {
var usePound = false;
if (col[0] == "#") {
col = col.slice(1);
usePound = true;
}
var num = parseInt(col,16);
var r = (num >> 16) + amt;
if (r > 255) r = 255;
else if (r < 0) r = 0;
var b = ((num >> 8) & 0x00FF) + amt;
if (b > 255) b = 255;
else if (b < 0) b = 0;
var g = (num & 0x0000FF) + amt;
if (g > 255) g = 255;
else if (g < 0) g = 0;
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}
//calculate the unsigned value of the angle between two 2D vector
function angle(vect1, vect2)
{
rst = vect1.x * vect2.x + vect1.y * vect2.y;
rst /= (Math.sqrt(Math.pow(vect1.x,2)+Math.pow(vect1.y,2)))*(Math.sqrt(Math.pow(vect2.x,2)+Math.pow(vect2.y,2)));
rst = Math.acos(rst);
return rst;
}
//Search for an intem in an array, and returns its index
//If the item is present muliple times in the array, it will return the first
//occurence of that item
//Returns -1 if the item is missing from the array
function getIndexOfItem(item, array)
{
var index = 0;
while (array[index] != item && index < array.length) index++;
if (index == array.length) index = -1;
return index;
}
//generate a random float between [ A ; B [
function randomfloatAtoB ( A, B )
{
return ( A + ( Math.random() * (B-A) ) );
}
//returns a whole number in from the [A,B[ range
function randomintAtoB ( A, B )
{
return ( Math.floor ( randomfloatAtoB ( A, B ) ) );
}
//---------------end useful functions-----------------------------------------------
function World() {
//Speed of the simulation when it starts
var SPEED = 1;
var timer = 0;
var canvas;
var context;
//define the middle of the drawing area
var centerX;
var centerY;
//contains all the cells from the simulation
var cells = [];
//contains all the "ships" that travels from one cell to an other
var cellShips = [];
//4 instances of the object Team
var teamPink;
var teamGreen;
var teamBlue;
var teamOrange;
//will keep track of the score of each team
var score;
//------------------------ CLASSES DECLARATION -----------------------------
//constructor of Team objects
function Team(name, color, startingCell)
{
var team = Object.create(teamPrototype);
team.name = name;
//hexadecimal value of the color of its cells
team.color = color;
//contains all the cells possessed by the team
team.cells = [];
//give a starting sell to the team, and paint it the right color
startingCell.color = team.color;
team.cells.push(startingCell);
//create an array that contains only the cells that need to be conquered
team.possibleTargets = cells.slice();
team.possibleTargets.splice(getIndexOfItem(startingCell, team.possibleTargets), 1);
return team;
}
var teamPrototype = {
//no functions to be defined at the moment
}
function getTeamByColor(color)
{
switch (color) {
case "pink" :
return teamPink;
case "green" :
return teamGreen;
case "blue" :
return teamBlue;
case "orange" :
return teamOrange;
case "#FE2E9A" :
return teamPink;
case "#80FF00" :
return teamGreen;
case "#58FAF4" :
return teamBlue;
case "#FE642E" :
return teamOrange;
default :
console.log("it failed");
return undefined;
}
}
//constructor of cells
function Cell(x,y, type, color = '#555555', destination = cells)
{
var cell = Object.create(cellPrototype);
//the position (x,y) described here are relative to centerX and centerY,
//which define the center of the drawing area
cell.x = x;
cell.y = y;
//the type of a cell is either 'small', 'average(avg)' or 'huge'
//its type changes its radius, its maximum capacity, and its production
//rate
cell.type = type;
switch (type)
{
case "small" :
cell.radius = 15;
cell.value = 5;
break;
case "avg" :
cell.radius = 25;
cell.value = 10;
break;
default :
cell.radius = 50;
cell.value = 20;
break;
}
cell.maxVal = cell.value*5;
cell.rate = Math.floor(30/cell.maxVal/AB.clockTick/SPEED*1000);
//the default color of each cell is '#555555'
cell.color = color;
//when the cell is created, it is saved in an array.
//the default array is 'cells[]' (which contains all cells)
destination.push(cell);
return cell;
}
var cellPrototype = {
//draw the cell on the canvas :
//at the right position
//with the color of its team
//with its current value written in its center
draw : function() {
context.beginPath();
context.arc(centerX+this.x, centerY+this.y, this.radius, 0, 2 * Math.PI, false);
context.fillStyle = this.color;
context.fill();
context.lineWidth = 2;
context.fillStyle = '#000000';
strokeRGB = lightenDarkenColor(this.color, +100);
context.strokeStyle = strokeRGB;
context.fillText(Math.floor(this.value), centerX+this.x-1, centerY+this.y+5);
context.stroke();
},
//create cells symmetric to this cells (horizontal AND vertical symmetry),
//add them to the array 'cells', and draw them on the canvas.
drawSymmetry : function() {
var newcell = Cell(-this.x, this.y, this.type, this.color);
newcell.draw();
newcell = Cell(-this.x, -this.y, this.type, this.color);
newcell.draw();
newcell = Cell(this.x, -this.y, this.type, this.color);
newcell.draw();
},
//any number of cells, located evenly around this cell.
//you can change what type of cell you want to produce,
//and how far away they should be from this cell
drawSurround : function(nbCells, radius, type) {
var newCell;
for (var i=0 ; i < 2*Math.PI-0.01 ; i += 2*Math.PI/nbCells)
{
newCell = Cell(this.x+radius*Math.cos(i), this.y+radius*Math.sin(i), type);
newCell.draw();
}
},
//calculate the direction a 'ship' needs to move towards in order to
//reach its destination
setDirection : function(cell) {
var vect0 ={
x : 1,
y : 0
};
var trajectory ={
x : cell.x-this.x,
y : cell.y-this.y
};
var ang = angle(vect0, trajectory);
if (cell.y < this.y) ang = -ang;
this.direction = ang;
},
//create a 'ship' from this cell, give half the value of the cell to
//the 'ship', and give it a destination (a cell)
split : function(dest) {
var newCellShip = Cell(this.x, this.y, "small", this.color, cellShips);
newCellShip.value = Math.floor(this.value/2);
newCellShip.dest = dest;
newCellShip.direction;
newCellShip.setDirection(dest);
this.value = Math.floor(this.value/2);
},
//when this 'ship' has reached its destination, it transfers its value
//to the destination cell. If the value of the ship is superior to the
//value of the destination, the infection will work, and the destination
//will become a part of the ship's team
//the arrays of the concerned teams must be updated
infect : function(){
//if close enough
if (Math.abs(this.x-this.dest.x) < 2*SPEED && Math.abs(this.y-this.dest.y) < 2*SPEED)
{
//remove the ship from the array of all ships
cellShips.splice(getIndexOfItem(this, cellShips), 1);
//calculate the resulting value of the destination
if (this.dest.color != this.color) this.dest.value -= this.value;
else this.dest.value += this.value;
//if the ship was strong enough to bring the value of the destination
//below zero, then change the team of the destination
//and update the arrays accordingly
if (this.dest.value < 0)
{
this.dest.value = -this.dest.value;
if (this.dest.color !== "#555555")
{
//delete cell from original team.cell
getTeamByColor(this.dest.color).cells.splice(getIndexOfItem(this.dest,getTeamByColor(this.dest.color).cells), 1);
//add cell to original team.target
getTeamByColor(this.dest.color).possibleTargets.push(this.dest);
}
this.dest.color = this.color;
//delete cell from new team.target
getTeamByColor(this.color).possibleTargets.splice(getIndexOfItem(this.dest,getTeamByColor(this.dest.color).possibleTargets), 1);
//add cell to new team.cell
getTeamByColor(this.color).cells.push(this.dest);
}
if (this.dest.value > this.dest.maxVal) this.dest.value = this.dest.maxVal;
}
},
//describe how each cell behave
//there is no hivemind in this version of the world
individualMind : function(){
var rand;
//the more a huge cell is close to its max value, the more it is
//likely to split and infect other cells
//all targets are chosen randomly between enemies or neutral cells
if (this.type == "huge")
{
rand = (this.maxVal - this.value);
if (randomintAtoB(0, rand) === 0 && getTeamByColor(this.color).possibleTargets.length !== 0)
{
rand = randomintAtoB(0, getTeamByColor(this.color).possibleTargets.length);
this.split(getTeamByColor(this.color).possibleTargets[rand]);
}
}
//whenever a small or average cell reaches it's max value, it will split
//all targets are chosen randomly between enemies or neutral cells
else if ((this.type == "small" || this.type == "avg") && this.value == this.maxVal)
{
if (getTeamByColor(this.color).possibleTargets.length !== 0)
{
rand = randomintAtoB(0, getTeamByColor(this.color).possibleTargets.length);
this.split(getTeamByColor(this.color).possibleTargets[rand]);
}
}
}
};
//-------------------------- INIT FUNCTIONS --------------------------------
function initContext()
{
context = canvas.getContext('2d');
context.font = "bold 15px Arial";
context.textAlign = 'center';
}
//Use the cell constructor to create the 'battlefield'
//remember that all cells created are added to the array 'cells'
function initStage()
{
var newCell = Cell(200, 200, "huge");
newCell.drawSurround(6, 90, "small");
newCell = Cell(500, 70, "huge");
newCell = Cell(350, 80, "avg");
newCell.drawSurround(3, 60, "small");
newCell = Cell(90*Math.cos(Math.PI/8), 90*Math.sin(Math.PI/8), "avg");
newCell = Cell(90*Math.cos(3*Math.PI/8), 90*Math.sin(3*Math.PI/8), "avg");
var length = cells.length;
for (var i=0 ; i<length ; i++)
{
cells[i].draw();
cells[i].drawSymmetry();
}
newCell = Cell(0, 0, "huge");
newCell.draw();
newCell = Cell(250, 0, "avg");
newCell.draw();
newCell = Cell(-250, 0, "avg");
newCell.draw();
newCell = Cell(0, 250, "avg");
newCell.draw();
newCell = Cell(0, -250, "avg");
newCell.draw();
newCell = Cell(160, 0, "small");
newCell.draw();
newCell = Cell(-160, 0, "small");
newCell.draw();
newCell = Cell(0, 160, "small");
newCell.draw();
newCell = Cell(0, -160, "small");
newCell.draw();
}
//give a name, a color, and a starting cell to all teams
function initTeams()
{
teamPink = Team("pink", "#FE2E9A", cells[0]);
teamOrange = Team("orange", "#FE642E", cells[14]);
teamBlue = Team("blue", "#58FAF4", cells[15]);
teamGreen = Team("green", "#80FF00", cells[16]);
}
//initialise the score variable
function initChart()
{
score ={
pink : 20,
orange : 20,
blue : 20,
green : 20
}
}
//-------------------------- UPDATE FUNCTIONS ------------------------------
function updateChart()
{
score.pink = 0;
score.green = 0;
score.blue = 0;
score.orange = 0;
//TODO : not optimised
for (var i = 0 ; i < cells.length ; i++)
{
switch (cells[i].color){
case "#FE2E9A" :
score.pink += cells[i].value;
break;
case "#FE642E" :
score.orange += cells[i].value;
break;
case "#58FAF4" :
score.blue += cells[i].value;
break;
case "#80FF00" :
score.green += cells[i].value;
break;
}
}
for (i = 0 ; i < cellShips.length ; i++)
{
switch (cellShips[i].color){
case "#FE2E9A" :
score.pink += cellShips[i].value;
break;
case "#FE642E" :
score.orange += cellShips[i].value;
break;
case "#58FAF4" :
score.blue += cellShips[i].value;
break;
case "#80FF00" :
score.green += cellShips[i].value;
break;
}
}
}
//use the data contained in the variable 'score', and use it to draw a
//cute little chart on the left side of the screen
function drawChart()
{
context.font = "12px Arial";
//this position is NOT relative to centerX and centerY
//these are the absolute coordinates of the canvas
posX = 25;
posY = 645;
//starts by drawing a black rectangle, so that the data is not drawn on
//top of the data of the previous step
context.fillStyle = "#000000";
context.fillRect(posX-10, 0, 130, canvas.height);
context.fillStyle = "#FE2E9A";
context.fillRect(posX, posY-score.pink/6, 30, score.pink/6);
context.fillStyle = "#FFFFFF";
context.fillText(Math.floor(score.pink), posX+15, posY-5-score.pink/6);
context.fillStyle = "#FE642E";
context.fillRect(posX+30, posY-score.orange/6, 30, score.orange/6);
context.fillStyle = "#FFFFFF";
context.fillText(Math.floor(score.orange), posX+45, posY-5-score.orange/6);
context.fillStyle = "#58FAF4";
context.fillRect(posX+60, posY-score.blue/6, 30, score.blue/6);
context.fillStyle = "#FFFFFF";
context.fillText(Math.floor(score.blue), posX+75, posY-5-score.blue/6);
context.fillStyle = "#80FF00";
context.fillRect(posX+90, posY-score.green/6, 30, score.green/6);
context.fillStyle = "#FFFFFF";
context.fillText(Math.floor(score.green), posX+105, posY-5-score.green/6);
context.font = "bold 15px Arial";
}
//at each step, draw a black/transparent rectangle on top of everything,
//which creates that fading effect behind the 'ships'
function updateBackground()
{
context.globalAlpha = 0.05;
context.fillRect(0, 0, canvas.width, canvas.height);
context.globalAlpha = 1;
}
//increment the value of all cells that are possessed by each team
//the incrementation depends on the rate of each cell
function grow()
{
for (var i=0 ; i<cells.length ; i++)
{
if (cells[i].color !== "#555555" && cells[i].value < cells[i].maxVal)
{
if (timer%Math.floor(cells[i].rate) === 0 && timer !== 0)
{
cells[i].value++;
cells[i].individualMind();
}
}
else if (cells[i].value == cells[i].maxVal) cells[i].individualMind();
}
}
//changes the position of all the ships, one step closer to their destination
function move()
{
for (var i=0 ; i<cellShips.length ; i++)
{
cellShips[i].x += SPEED*Math.cos(cellShips[i].direction);
cellShips[i].y += SPEED*Math.sin(cellShips[i].direction);
cellShips[i].infect();
}
}
//this function redraws everything at each new step
function update()
{
updateBackground();
updateChart();
drawChart();
for (var i=0 ; i<cells.length ; i++)
{
cells[i].draw();
}
for (i=0 ; i<cellShips.length ; i++)
{
cellShips[i].draw();
}
}
//-------------------------- RUN FUNCTIONS ---------------------------------
this.newRun = function()
{
threeworld.init ( SKYCOLOR );
initButtons();
initMusic();
canvas = threeworld.canvas;
initContext();
centerX = 30+canvas.width / 2;
centerY = canvas.height / 2;
initStage();
initTeams();
initChart();
};
this.nextStep = function()
{
grow();
move();
update();
timer++;
};
this.endRun = function()
{
};
//------------------------- USER INTERFACE ---------------------------------
//this part is there to create the 3 buttons that change the speed of the simulation
function initButtons()
{
s = "<p><button id='regularButton'>x1</button> <button id='fasterButton'>\> x2 \></button> <button id='f a s t erButton'>\>\> x4 \>\></button></p>";
$("#user_span1").html( s );
document.getElementById("regularButton").addEventListener("click", regularSpeed);
document.getElementById("fasterButton").addEventListener("click", fastSpeed);
document.getElementById("f a s t erButton").addEventListener("click", fasterSpeed);
}
function regularSpeed()
{
SPEED = 1;
for (var i = 0 ; i < cells.length ; i++)
{
cells[i].rate = Math.floor(30/cells[i].maxVal/AB.clockTick/SPEED*1000);
}
}
function fastSpeed()
{
SPEED = 2;
for (var i = 0 ; i < cells.length ; i++)
{
cells[i].rate = Math.floor(30/cells[i].maxVal/AB.clockTick/SPEED*1000);
}
}
function fasterSpeed()
{
SPEED = 4;
for (var i = 0 ; i < cells.length ; i++)
{
cells[i].rate = Math.floor(30/cells[i].maxVal/AB.clockTick/SPEED*1000);
}
}
}