// Cloned by SinfulSalad on 5 Jul 2018 from World "CELLS WITH MIND" 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. Each team can't split more than 15 of it's cells at any given time.
// The team that wins is the team that controls all cells
// ===================================================================================================================
// === 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
//put your music file here if you want to change the music
const MUSIC_BACK = '/uploads/sinfulsalad/Forest.mp3' ;
//you can change change the color used here if they do not suit you.
//warning : if you modify a name, don't forget to modify the corresponding code
//you can go there to get the code for any color : http://jdstiles.com/colorchart.html
const COLOR_NAME = ["aquamarine", "red", "hotpink", "lightgoldenrodyellow", "lime","orange", "saddlebrown", "mediumblue"];
const COLOR_CODE = ["#7FFFD4", "#FF0000", "#FF69B4", "#FAFAD2", "#00FF00", "#FFA500", "#8B4513", "#0000DD"];
//you can change the number of teams fighting for total domination.
//by default, there will be as many teams as there are minds (two in this version)
//but if NB_TEAMS is higher than the number of minds, then it will add additional
//teams with a basic mind.
const NB_TEAMS = 4;
// ===================================================================================================================
// === End of tweaker's box ==========================================================================================
// ===================================================================================================================
// You will need to be some sort of JavaScript programmer to change things below the tweaker's box.
//----------------------------- MINDS ------------------------------------------
/*
//Here is the template to create your own mind
//(An example of a basic mind is coded just below)
//There is only one order the mind can give to the world :
//which cell should try to infect which other cell, and when.
//To give this order, the mind have to define the attribute team.ordersArray
//and the function team.ACTION() (more on that below).
//There can't be more than 15 infections in progress at any given time
//If new orders are sent while this limit is reached, they will be ignored.
//Any kind of function or attribute can be defined here.
//When you are done, don't forget to push your mind into the mindArray
//(you can do it right below the basic mind example).
//(it is recommended to read the class definition of the Team and Cell object
//in the World, to understand how they work, and what information they contain)
var newMind = function(team) {
//this attribute is necessary for the mind to work properly
//each order is a tuple containing two elements :
//1. the cell the mind wants to split
//2. the cell the mind is trying to infect
//ex : team.ordersArray.push([team.cells[2], team.possibleTargets[12]]);
//note : each mind can technically acces any cell on the battlefield because
//there are no private attributes, but it is only fair that it should only
//control the cells that belong to it's own team.
team.ordersArray = [];
team.newAttribute;
team.newFunction = function(){
};
//This function is necessary for the mind to work properly
//This is the function called by the World when it is waiting for
//an input from the mind.
//It should contain any kind of code using the functions and attributes created
//above, in order to generate orders. The orders will then be executed by the World.
//ACTION() should not modify any of the variables contained in the World,
//just read them to generate orders accordingly.
team.ACTION = function() {
//reset the orders' array
ordersArray = [];
//code
return team.ordersArray;
};
};
*/
//TODO : BUILD YOUR OWN MIND HERE
var newMind = function(team) {
team.ordersArray = [];
team.hugeTarget = "avg";
team.avgTarget = "small";
team.smallTarget = "small";
team.counter = 0;
team.searchTarget = function(type){
var targetArray = [];
for (var i = 0; i < team.possibleTargets.length ; i++)
{
if (team.possibleTargets[i].type == type)
targetArray.push(team.possibleTargets[i]);
}
if (targetArray.length === 0) return undefined;
rand = randomintAtoB(0, targetArray.length);
return targetArray[rand];
};
team.changeTargetType = function(type){
var rand;
switch (type){
case "huge" :
if (randomintAtoB(0, 10) < 2) team.hugeTarget = "huge";
else team.hugeTarget = "avg";
break;
case "avg" :
if (randomintAtoB(0, 10) < 2) team.avgTarget = "huge";
else if (randomintAtoB(0, 10) > 6) team.avgTarget = "avg";
else team.avgTarget = "small";
break;
default :
if (randomintAtoB(0, 10) > 6) team.smallTarget = "avg";
else team.smallTarget = "small";
break;
}
};
team.individualMind = function(){
var rand;
var targetType;
var target;
for (var i = 0 ; i<team.cells.length ; i++)
{
switch (team.cells[i].type){
case "huge" :
targetType = team.hugeTarget;
break;
case "avg" :
targetType = team.avgTarget;
break;
default :
targetType = team.smallTarget;
}
switch (targetType){
case "huge" :
target = team.searchTarget(targetType);
if (target === undefined) team.changeTargetType(team.cells[i].type);
if (team.cells[i].value > 42 && target !== undefined)
{
team.ordersArray.push([team.cells[i], target]);
team.changeTargetType(team.cells[i].type);
}
break;
case "avg" :
target = team.searchTarget(targetType);
if (target === undefined) team.changeTargetType(team.cells[i].type);
if (team.cells[i].value > 22 && target !== undefined)
{
team.ordersArray.push([team.cells[i], target]);
team.changeTargetType(team.cells[i].type);
}
break;
default :
target = team.searchTarget(targetType);
if (target === undefined) team.changeTargetType(team.cells[i].type);
if (team.cells[i].value > 12 && target !== undefined)
{
team.ordersArray.push([team.cells[i], target]);
team.changeTargetType(team.cells[i].type);
}
}
}
};
team.ACTION = function() {
team.ordersArray = [];
team.individualMind();
team.counter ++;
if(team.counter == 100) basicMind(team);
return team.ordersArray;
};
};
var mindFocus = function(team) {
team.ordersArray = [];
team.counter = 0;
team.nbShipsMax = 15;
team.disableMind = false;
team.alreadyTargeted = [];
//there is no strategy here, each cell acts on its own, and choses
//its target randomly among enemies and neutral cell
team.individualMind = function(){
if (team.disableMind === false)
{
var rand;
for (var i = 0 ; i<team.cells.length ; i++)
{
//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 (team.cells[i].type == "huge")
{
//console.log("I want to split huge things");
rand = (team.cells[i].maxVal - team.cells[i].value);
if (randomintAtoB(0, rand) === 0 && team.cells[i].team.possibleTargets.length !== 0)
{
//console.log("IMSPLITTINGTHIS")
rand = randomintAtoB(0, team.cells[i].team.possibleTargets.length);
team.ordersArray.push([team.cells[i], team.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 ((team.cells[i].type == "small" || team.cells[i].type == "avg") && team.cells[i].value == team.cells[i].maxVal)
{
if (team.cells[i].team.possibleTargets.length !== 0)
{
rand = randomintAtoB(0, team.cells[i].team.possibleTargets.length);
team.ordersArray.push([team.cells[i], team.possibleTargets[rand]]);
}
}
}
}
};
team.bigMind = function()
{
if (team.cells[0].value > 42)
{
for (var j = 0 ; j < team.possibleTargets.length ; j++)
{
if (team.possibleTargets[j].type == "huge" && team.possibleTargets[j].team === undefined)
{
if (getIndexOfItem(team.possibleTargets[j], team.alreadyTargeted) == -1)
{
team.ordersArray.push([team.cells[0], team.possibleTargets[j]]);
team.alreadyTargeted.push(team.possibleTargets[j]);
break;
}
}
}
}
};
team.ACTION = function(){
team.ordersArray = [];
if (team.counter < 70)
team.bigMind();
else
team.individualMind();
console.log(team.counter);
team.counter ++;
return team.ordersArray;
};
};
var basicMind = function(team) {
team.ordersArray = [];
//there is no strategy here, each cell acts on its own, and choses
//its target randomly among enemies and neutral cell
team.individualMind = function(){
var rand;
for (var i = 0 ; i<team.cells.length ; i++)
{
//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 (team.cells[i].type == "huge")
{
//console.log("I want to split huge things");
rand = (team.cells[i].maxVal - team.cells[i].value);
if (randomintAtoB(0, rand) === 0 && team.cells[i].team.possibleTargets.length !== 0)
{
//console.log("IMSPLITTINGTHIS")
rand = randomintAtoB(0, team.cells[i].team.possibleTargets.length);
team.ordersArray.push([team.cells[i], team.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 ((team.cells[i].type == "small" || team.cells[i].type == "avg") && team.cells[i].value == team.cells[i].maxVal)
{
if (team.cells[i].team.possibleTargets.length !== 0)
{
rand = randomintAtoB(0, team.cells[i].team.possibleTargets.length);
team.ordersArray.push([team.cells[i], team.possibleTargets[rand]]);
}
}
}
};
team.ACTION = function(){
team.ordersArray = [];
team.individualMind();
return team.ordersArray;
};
};
//TODO : ADD YOUR MIND HERE
var mindArray = [];
mindArray.push(newMind);
mindArray.push(mindFocus);
//---------------------------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 allCells = [];
//contains all the "ships" that travels from one cell to an other
var allShips = [];
//array containing all the teams
var allTeams = [];
//constructor of Team objects
function Team(name, color, startingCell)
{
var team = Object.create(teamPrototype);
team.name = name;
team.score = 0;
//hexadecimal value of the color of its cells
team.color = color;
//contains all the cells possessed by the team
team.cells = [];
team.nbShips = 0;
//give a starting sell to the team, and paint it the right color
startingCell.color = team.color;
startingCell.team = team;
team.cells.push(startingCell);
//create an array that contains only the cells that need to be conquered
team.possibleTargets = allCells.slice();
team.possibleTargets.splice(getIndexOfItem(startingCell, team.possibleTargets), 1);
allTeams.push(team);
return team;
}
var teamPrototype = {
//no functions to be defined at the moment
};
//TODO???
function getTeamByColor(color)
{
for (var i = 0 ; i<allTeams.length ; i++)
{
if (allTeams[i].color == color) return allTeams[i];
else
{
console.log("geTeamByColor failed");
return undefined;
}
}
}
//constructor of cells
function Cell(x,y, type, color = '#555555', destination = allCells, team = undefined)
{
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.production = cell.maxVal/60 ;
//the default color of each cell is '#555555'
cell.color = color;
cell.team = team;
//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 newShip = Cell(this.x, this.y, "small", this.color, allShips, this.team);
newShip.value = Math.floor(this.value/2);
newShip.dest = dest;
newShip.direction;
newShip.setDirection(dest);
this.value = Math.floor(this.value/2);
this.team.nbShips ++;
},
//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
allShips.splice(getIndexOfItem(this, allShips), 1);
this.team.nbShips --;
//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
this.dest.team.cells.splice(getIndexOfItem(this.dest,this.dest.team.cells), 1);
//add cell to original team.target
this.dest.team.possibleTargets.push(this.dest);
}
this.dest.color = this.color;
this.dest.team = this.team;
//delete cell from new team.target
this.team.possibleTargets.splice(getIndexOfItem(this.dest, this.team.possibleTargets), 1);
//add cell to new team.cell
this.team.cells.push(this.dest);
}
if (this.dest.value > this.dest.maxVal) this.dest.value = this.dest.maxVal;
}
},
grow : function(){
if (this.color !== "#555555" && this.value < this.maxVal)
{
this.value += this.production;
if (this.value > this.maxVal) this.value = this.maxVal;
}
}
};
//------------------------------- INIT FUNCTION ----------------------------
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 = allCells.length;
for (var i=0 ; i<length ; i++)
{
allCells[i].draw();
allCells[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()
{
var nbTeamsCreated = 0;
for (var i = 0 ; i<mindArray.length ; i++)
{
Team(COLOR_NAME[i], COLOR_CODE[i], searchStartingCell());
mindArray[i](allTeams[i]);
nbTeamsCreated++;
}
while (NB_TEAMS-nbTeamsCreated > 0)
{
Team(COLOR_NAME[nbTeamsCreated], COLOR_CODE[nbTeamsCreated], searchStartingCell());
basicMind(allTeams[nbTeamsCreated]);
nbTeamsCreated++;
}
}
function searchStartingCell(){
for (var i = 0 ; i < allCells.length ; i++)
{
if (allCells[i].type == "huge" && allCells[i].team === undefined)
return allCells[i];
}
}
//-------------------------- UPDATE FUNCTIONS ------------------------------
function updateChart()
{
for (var i = 0 ; i<allTeams.length ; i++) allTeams[i].score = 0;
for (i = 0 ; i < allCells.length ; i++)
{
if (allCells[i].team !== undefined)
{
allCells[i].team.score += allCells[i].value;
}
}
for (i = 0 ; i < allShips.length ; i++) allShips[i].team.score += allShips[i].value;
}
//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
var posX = 25;
var 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, 150, canvas.height);
var widthEach = Math.floor(120/allTeams.length);
for (var i =0 ; i<allTeams.length ; i++)
{
context.fillStyle = allTeams[i].color;
context.fillRect(posX, posY-allTeams[i].score/6, widthEach, allTeams[i].score/6);
context.fillStyle = "#FFFFFF";
context.fillText(Math.floor(allTeams[i].score), posX+10, posY-5-allTeams[i].score/6);
posX += widthEach;
}
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;
}
//changes the position of all the ships, one step closer to their destination
function move()
{
for (var i=0 ; i<allShips.length ; i++)
{
allShips[i].x += SPEED*Math.cos(allShips[i].direction);
allShips[i].y += SPEED*Math.sin(allShips[i].direction);
allShips[i].infect();
}
}
//this function redraws everything at each new step
function update()
{
updateBackground();
updateChart();
drawChart();
for (var i=0 ; i<allCells.length ; i++)
{
if (timer%Math.floor(1000/(33*2*SPEED)) === 0 && timer !== 0)
{
allCells[i].grow();
}
allCells[i].draw();
}
if (timer%Math.floor(1000/(33*2*SPEED)) === 0 && timer !== 0)
{
TAKE_ACTION();
}
for (i=0 ; i<allShips.length ; i++)
{
allShips[i].draw();
}
}
//------------------------- RUN FUNCTIONS --------------------------------------
function TAKE_ACTION()
{
var ordersArray = [];
for (var i = 0 ; i<allTeams.length ; i++)
{
ordersArray = ordersArray.concat(allTeams[i].ACTION());
//console.log(ordersArray.length);
}
for (i = 0 ; i<ordersArray.length ; i++)
{
//TODO : explain this
if (ordersArray[i][0].team.nbShips < 14)
{
ordersArray[i][0].split(ordersArray[i][1]);
}
}
}
this.newRun = function()
{
threeworld.init ( SKYCOLOR );
initButtons();
initMusic();
canvas = threeworld.canvas;
initContext();
centerX = 30+canvas.width / 2;
centerY = canvas.height / 2;
initStage();
initTeams();
};
this.nextStep = function()
{
centerX = 30+canvas.width / 2;
centerY = canvas.height / 2;
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;}
function fastSpeed() {SPEED = 2;}
function fasterSpeed() {SPEED = 4;}
}
// ------------------------------ 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' > " );
}