// Cloned by Enhanced on 10 Jul 2018 from World "CELLS WITH MIND (with more minds)" by SinfulSalad // Please leave this clone trail here. // 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. //VERY IMPORTANT : //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 //TODO : WHEN YOU ARE DONE, DONT FORGET TO ADD IT TO mindArray SO THE WORLD // KNOWS IT EXISTS. mindArray's definition is right below the last mind, and // right above the "useful functions" //the behaviour of this mind is to expand as fast as possible //Whenever it's cells have the required value to infect another cell, it will //do so right away. var newMind = function(team) { team.ordersArray = []; team.hugeTarget = "avg"; team.avgTarget = "small"; team.smallTarget = "small"; team.counter = 0; //return a random cell of the specified type, that belongs to an opponent 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]; }; //changes the type of target of each kind of cell, in order not to always //focus the same kind of cells. 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; } }; //check each cell individually, and check if it can infect another cell 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; }; }; //The goal of this team is to start the game by focusing all the huge cells //(their high maxValue and production rate make them valuable targets) 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; }; }; //there is no strategy here, each cell acts on its own, and choses //its target randomly among enemies and neutral cells var basicMind = function(team) { team.ordersArray = []; 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); //--------------------------------END MINDS------------------------------------- //---------------------------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' > " ); }