Code viewer for World: Cells

// 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);
        }
    }

}