Created
November 13, 2021 20:02
-
-
Save slacktracer/3ccb61b902746ae85e084551fe51edf6 to your computer and use it in GitHub Desktop.
agar.evo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* !ideas! | |
add an inherited trait to splitting | |
?connect processes like moving speed and resting to comsumption of mass? | |
add abilty to quickly run canvas off screen without drawing | |
spectate focus jumps around on low raidus cells | |
also can crash on low raidus cells | |
maybe give every cell a unique id for tracking and sorting | |
think of other traits for cells to pass on | |
add in a way to control how many 1/dist there are as a cell trait. | |
add in regions to the game space so that cells only have to think about objects nearby | |
make an array that lists the cells in order of radius for drawing order | |
give each cell a team ID | |
cells will not dividing into or chase the same team ID | |
let that ID evolve so that 5% of the time the ID will shift to a new ID | |
use cell name as the team ID | |
AI visualization for focused cell? | |
cell division trait: reuse for determining how often a cell will divide | |
range, lower size limit? | |
have food pellet grow until hitting a cap of 5 like in the game | |
*/ | |
var canvas = document.createElement("canvas"); | |
var ctx = canvas.getContext("2d"); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
document.body.appendChild(canvas); | |
window.onresize = function(event) { | |
ctx.canvas.width = window.innerWidth; | |
ctx.canvas.height = window.innerHeight; | |
}; | |
//game bounds | |
var container = new function() { | |
this.foodID = 0; | |
this.cycle = 0; //cycle keeps track of the interations of the game loop | |
this.pause = false; | |
this.spectate = true; | |
this.visualize = true; | |
this.info = false; | |
this.cellFocus = 0; | |
this.nextFocus = function() { | |
container.cellFocus++; | |
if (container.cellFocus > cell.length - 1) container.cellFocus = 0; | |
}; | |
this.CameraX = 0; | |
this.CameraY = 0; | |
this.width = 10000; // size of the map //10000 | |
this.height = 8000; // size of the map //6000 | |
this.friction = 0.97; //closer to 1 means higher speeds and less turning | |
this.speed = 0.1; //controls thrust per cycle from cells | |
this.divideSpeed = 20; //speed that cell mopve at after a division at 30 it lets them move about 1000 befor ethey slow down (at speed = 0.09 and friciton =0.97) | |
this.scale = 0.01; //the zoom | |
this.scaleGoal = 0.2; //what the scale will smoothly move towards | |
this.totalFood = (this.width * this.height) * 0.0000100; //0.0000150 | |
this.totalVirus = (this.width * this.height) * 0.0000002; //0.0000003 | |
this.totalCells = (this.width * this.height) * 0.0000005; //0.0000010 | |
this.nameMutationRate = 0.1; | |
}; | |
//container.scaleGraphPaper(); | |
//small scale testing setup comment this out for full scale | |
/* | |
container.width = 3600; | |
container.height = 2600; | |
container.scaleGoal = 0.34; | |
container.spectate = false; | |
container.totalFood = 500; | |
container.totalVirus = 6; | |
container.totalCells = 8 | |
*/ | |
/* | |
//huge scale testing setup comment this out for full scale | |
container.width = 40000; | |
container.height = 20000; | |
container.scaleGoal = 0.1; | |
container.spectate = false; | |
container.totalFood = 2000; | |
container.totalVirus = 20; | |
container.totalCells = 20; | |
*/ | |
var gui = new dat.GUI(); | |
//gui.close(); | |
gui.add(container, 'info'); | |
gui.add(container, 'pause'); | |
scontroller = gui.add(container, 'spectate'); | |
scontroller.onFinishChange(function(value) { | |
container.scaleGoal = 0.14; | |
}); | |
gui.add(container, 'scaleGoal', 0.1, 1); | |
//gui.add(container, 'nextFocus'); | |
var guiSettings = gui.addFolder('environment'); | |
guiSettings.add(container, 'speed', 0.01, 1).step(0.01); | |
guiSettings.add(container, 'friction', 0.9, .999).step(0.001); | |
guiSettings.add(container, 'width', 100, 20000).step(100); | |
guiSettings.add(container, 'height', 100, 20000).step(100); | |
var fcontroller = guiSettings.add(container, 'totalFood', 0, 3000).step(1); | |
fcontroller.onFinishChange(function(value) { | |
if (food.length > container.totalFood) food.splice(value); | |
}); | |
var vcontroller = guiSettings.add(container, 'totalVirus', 0, 100).step(1); | |
vcontroller.onFinishChange(function(value) { | |
if (virus.length > container.totalVirus) virus.splice(value); | |
}); | |
var ccontroller = guiSettings.add(container, 'totalCells', 1, 200).step(1); | |
ccontroller.onFinishChange(function(value) { | |
//if (cell.length>container.totalCells) cell.length = value; | |
if (cell.length > container.totalCells) cell.splice(value); | |
}); | |
//repeating check to spawn food up the max value | |
function spawner() { | |
if (food.length < container.totalFood) { | |
pushFood(Math.random() * container.width, Math.random() * container.height); | |
} | |
if (cell.length < container.totalCells) { | |
newCellRandom(); | |
} | |
if (virus.length < container.totalVirus) { | |
pushVirus(); | |
} | |
} | |
function randomName() { | |
var text = ""; | |
var possible = "abcdefghijklmnopqrstuvwxyz"; | |
//capitalized first letter | |
text += possible.charAt(Math.floor(Math.random() * possible.length)); | |
text = text.toUpperCase(); | |
//add a random number of other letters | |
for (var k = 0; k < 3 + Math.round(Math.random() * 10); k++) { | |
text += possible.charAt(Math.floor(Math.random() * possible.length)); | |
} | |
text[0] = text.charAt(0).toUpperCase() | |
return text; | |
} | |
//replace a char in a string function also a chance to just add a letter or remove a letter at the replacement site | |
String.prototype.mutateName = function(index) { | |
var possible = "abcdefghijklmnopqrstuvwxyz"; | |
return this.substr(0, index + Math.round(2 * (Math.random() - 0.5))) + possible.charAt(Math.floor(Math.random() * possible.length)) + this.substr(index + 1); | |
} | |
var cell = []; | |
function pushCell(x, y, Vx, Vy, r, name, color, division, inertia, seekFood, seekCell, fleeCell, fleeVirus) { | |
cell.push({ | |
alive: true, | |
Vx: Vx, //Velocity in x direction on spawn | |
Vy: Vy, //Velocity in Y direction on spawn | |
zone: { | |
x: 0, | |
y: 0 | |
}, | |
dir: 0, //direction cell is moving | |
rDraw: 0, //makes cell radius changes look smooth | |
stroke: "", //cell outline color is always a bit darker then fill color | |
//traits that evolve / are passed on with division | |
x: x, //Math.random() * container.width, | |
y: y, //Math.random() * container.height, | |
r: r, //30, //30 | |
name: name, | |
color: color, ///rainbow in is color library file | |
textColor: "", | |
division: division, //100 if cell raidus goes above this division occurs | |
inertia: inertia, //0.000002 higher means less turning, lower is more jerky | |
seekFood: seekFood, //1 1/dist^3 | |
seekCell: seekCell, //30 1/dist^3 | |
fleeCell: fleeCell, //8000 1/dist^4 | |
fleeVirus: fleeVirus, //5 1/dist^4 | |
}); | |
//set cell stroke to be a bit darker than fill | |
cell[cell.length - 1].stroke = ColorLuminance(cell[cell.length - 1].color, -0.1); | |
//set text color to be a color that readable compared to the body color | |
cell[cell.length - 1].textColor = invertCssColor(cell[cell.length - 1].color); | |
} | |
function newCellRandom() { | |
pushCell(Math.random() * container.width, //x | |
Math.random() * container.height, //y | |
0, | |
0, | |
30, //r | |
randomNameAustin(), //name | |
rainbow(32, Math.floor(Math.random() * 32)), //color | |
Math.random() * 500, //division 200 | |
Math.random() * 100000, //inertia 100000/10000000 = 0.1 | |
//50-50 chance of having negative traits | |
(0.5 - Math.random()) * 20000, //seekFood | |
(0.5 - Math.random()) * 20000, //seekCell | |
(0.5 - Math.random()) * 20000, //fleeCell | |
(0.5 - Math.random()) * 20000 //fleeVirus | |
/* | |
//all traits have the "best" number sign | |
Math.random() * 10000, //seekFood | |
Math.random() * 10000, //seekCell | |
Math.random() * -10000, //fleeCell | |
Math.random() * -10000 //fleeVirus | |
*/ | |
); | |
} | |
for (var i = 0; i < container.totalCells; i++) { | |
newCellRandom() | |
} | |
//this.speed = 0.08 * (1 + 150 / this.r + 4000 / this.r / this.r) | |
//if (this.r > 50) this.r = 0.99995 * this.r - 0.00000005 * this.r * this.r | |
function cellDivision(i, dir) { | |
//original cell knock back | |
//cell[i].Vx = -Math.cos(dir); | |
//cell[i].Vy = -Math.sin(dir); | |
//pushCell(x,y,Vx,Vy,r,name,color,division,inertia,seekFood,seekCell,fleeCell,fleeVirus) | |
pushCell(cell[i].x + cell[i].r * Math.cos(dir), | |
cell[i].y + cell[i].r * Math.sin(dir), | |
container.divideSpeed * Math.cos(dir), | |
container.divideSpeed * Math.sin(dir), | |
cell[i].r, cell[i].name, colorMutate(cell[i].color), cell[i].division, cell[i].inertia, | |
cell[i].seekFood, cell[i].seekCell, cell[i].fleeCell, cell[i].fleeVirus | |
); | |
//mutations values shift by up to +-10% | |
cell[i].division += cell[i].division * (0.5 - Math.random()) * 0.20; | |
cell[i].inertia += cell[i].inertia * (0.5 - Math.random()) * 0.20; | |
cell[i].seekFood += cell[i].seekFood * (0.5 - Math.random()) * 0.20; | |
cell[i].seekCell += cell[i].seekCell * (0.5 - Math.random()) * 0.20; | |
cell[i].fleeCell += cell[i].fleeCell * (0.5 - Math.random()) * 0.20; | |
cell[i].fleeVirus += cell[i].fleeVirus * (0.5 - Math.random()) * 0.20; | |
//10% chance to mutate name | |
if (Math.random() < container.nameMutationRate) { | |
cell[i].name = cell[i].name.mutateName(Math.floor(Math.random() * cell[i].name.length)); | |
} | |
//cell[i].color = cell[i].color.mutateColor(); | |
//cell[i].stroke = ColorLuminance(cell[i].color, -0.1) | |
} | |
function cellMove() { | |
var cellLength = cell.length; | |
for (var i = 0; i < cellLength; i++) { | |
//determine zone | |
cell[i].zone.x = Math.floor(cell[i].x / 1000); | |
cell[i].zone.y = Math.floor(cell[i].y / 1000); | |
//AI | |
//inertia range: good around (0.0001) no negatives (evolving range 1 to 0.00001) | |
var inertia = cell[i].inertia / 10000000; | |
var vector = { | |
x: inertia * Math.cos(cell[i].dir), | |
y: inertia * Math.sin(cell[i].dir) | |
}; | |
var length = food.length; //move towards food | |
for (var j = 0; j < length; j++) { | |
var dx = food[j].x - (cell[i].x); | |
var dy = food[j].y - (cell[i].y); | |
var dist = Math.sqrt(dx * dx + dy * dy) - cell[i].r; | |
dx = cell[i].seekFood * dx / dist / (dist * dist * cell[i].r); | |
dy = cell[i].seekFood * dy / dist / (dist * dist * cell[i].r); | |
vector.x += dx; | |
vector.y += dy; | |
} | |
length = virus.length; //avoid viruses | |
for (j = 0; j < length; j++) { | |
if (virus[j].r < cell[i].r) { | |
var dy = virus[j].y - (cell[i].y); | |
var dx = virus[j].x - (cell[i].x); | |
var dist = Math.sqrt(dx * dx + dy * dy) - cell[i].r; | |
dx = cell[i].fleeVirus * dx / dist / (dist * dist * dist * dist) * 1000; | |
dy = cell[i].fleeVirus * dy / dist / (dist * dist * dist * dist) * 1000; | |
vector.x += dx; | |
vector.y += dy; | |
} | |
} | |
length = cell.length; //chase and run from cells | |
for (j = 0; j < length; j++) { | |
//if (i != j) { | |
//START AI | |
if (cell[i].name != cell[j].name) { | |
var dx = cell[j].x - cell[i].x; | |
var dy = cell[j].y - cell[i].y; | |
var dist = Math.sqrt(dx * dx + dy * dy) - cell[i].r; | |
//seekCell | |
if (cell[i].r > cell[j].r * 1.12) { //chase smaller cells | |
dx = dx / dist / (dist * dist * dist); | |
dy = dy / dist / (dist * dist * dist); | |
sizeWeight = cell[j].r * cell[j].r; //larger cells are more valuable and eaiser to catch | |
vector.x += cell[i].seekCell * dx * sizeWeight; | |
vector.y += cell[i].seekCell * dy * sizeWeight; | |
//check for division target | |
if (cell[i].seekCell > 0 && cell[j].fleeCell < 0 && //if you chase and they run | |
cell[i].r * 0.65 > cell[j].r * 1.12 && // if the cell is big enough to eat | |
cell[j].r * 1.5 > cell[i].r * 0.65 && //if the cell is large enough to be worth eatting | |
dist < 250 && dist > 150 && // if the cell isn't too close or too far | |
cell[i].r * 0.65 > 40) { //if you are big enough to split and not starve | |
//There is a cost to division so less than 0.707 | |
cell[i].r *= 0.65; //radius*1/2^(0.5) because of volume. | |
var dirX = (cell[j].x + 10 * cell[j].Vx) - cell[i].x; | |
var dirY = (cell[j].y + 10 * cell[j].Vy) - cell[i].y; | |
cellDivision(i, Math.atan2(dirY, dirX)); | |
} | |
//fleeCell | |
} else if (cell[i].r < cell[j].r) { //run from bigger cells | |
dx = dx / dist / (dist * dist * dist * dist) * 1000; | |
dy = dy / dist / (dist * dist * dist * dist) * 1000; | |
vector.x += cell[i].fleeCell * dx; | |
vector.y += cell[i].fleeCell * dy; | |
} | |
//END AI | |
} | |
//eat smaller cells | |
if (cell[i].r > cell[j].r * 1.1) { | |
var x = cell[i].x - cell[j].x; | |
var y = cell[i].y - cell[j].y; | |
var d = Math.sqrt(x * x + y * y); | |
if (d < cell[i].r) { | |
//Volume+volume = volume PIr^2+PIr^2 =PIr^r root(r^2+r^2)=new r | |
cell[i].r = Math.sqrt(cell[i].r * cell[i].r + cell[j].r * cell[j].r) | |
cell[j].alive = false; | |
} | |
} | |
} | |
//direction | |
cell[i].dir = Math.atan2(vector.y, vector.x); | |
//move | |
var speed = container.speed * (2 + 50 / cell[i].r) // + 2000 / cell[i].r / cell[i].r) | |
cell[i].Vx = container.friction * cell[i].Vx + speed * Math.cos(cell[i].dir); | |
cell[i].Vy = container.friction * cell[i].Vy + speed * Math.sin(cell[i].dir); | |
cell[i].x += cell[i].Vx; | |
cell[i].y += cell[i].Vy; | |
//walls | |
if (cell[i].x < 0) { | |
cell[i].x = 0; | |
cell[i].Vx = 0; | |
} else if (cell[i].x > container.width) { | |
cell[i].x = container.width; | |
cell[i].Vx = 0; | |
} | |
if (cell[i].y < 0) { | |
cell[i].y = 0; | |
cell[i].Vy = 0; | |
} else if (cell[i].y > container.height) { | |
cell[i].y = container.height; | |
cell[i].Vy = 0; | |
} | |
//mass loss | |
cell[i].r *= 0.99999; | |
//starve? | |
if (cell[i].r < 20) { //dont' switch r to r draw here or script will make too many new cells | |
cell[i].alive = false; | |
for (var k = 0; k < 3; k++) { //spawn some food where cell dies | |
pushFood(cell[i].x + 25 - Math.random() * 50, cell[i].y + 25 - Math.random() * 50); | |
}; | |
} | |
/* | |
//divide? | |
else if (cell[i].seekCell < 0 && cell[i].r > cell[i].division) { | |
//There is a cost to division so less than 0.707 | |
cell[i].r *= 0.65; //radius*1/2^(0.5) because of volume. | |
cellDivision(i, -cell[i].dir); | |
} | |
*/ | |
//touching virus | |
j = virus.length | |
while (j--) { | |
if (cell[i].r > virus[j].r) { | |
var x = virus[j].x - cell[i].x | |
var y = virus[j].y - cell[i].y | |
var d = Math.sqrt(x * x + y * y); | |
if (d < cell[i].r) { | |
var spawnCells = 0; | |
//for big cells divide into 10 cells with larger raidus | |
if (cell[i].r > 95) { // 94 = root(10 x 30^2) = root(spawnCells x (new cellRadius)^2) | |
if (cell[i].r > 300) { | |
if (cell[i].r > 400) cell[i].r = 400; //cell radius can't be too big | |
//relocate virus on hitting large cells | |
virus[j].x = Math.random() * container.width; | |
virus[j].y = Math.random() * container.height; | |
virus[j].r = 80 + Math.random() * 25; | |
} | |
spawnCells = 10; | |
cell[i].r /= Math.sqrt(spawnCells); | |
//relocate virus for large cell collisions | |
} else { // for smaller cells dividing into up to 10 cells | |
spawnCells = Math.round(Math.sqrt(cell[i].r * cell[i].r / (30 * 30))); | |
cell[i].r = 30; | |
} | |
for (var k = 0; k < spawnCells; k++) { //spawn mutated versions of mother cell | |
cellDivision(i, Math.random() * 6.28); | |
} | |
} | |
} | |
} | |
} | |
var k = cell.length; | |
while (k--) { | |
//die (needs to come at the end of the while loop) | |
if (!cell[k].alive) { | |
cell.splice(k, 1); | |
} | |
} | |
} | |
var virus = []; | |
function pushVirus() { | |
virus.push({ | |
x: Math.random() * container.width, | |
y: Math.random() * container.height, | |
r: 80 + Math.random() * 25, | |
}); | |
} | |
//spawn viruses at start | |
for (var i = 0; i < container.totalVirus; i++) { | |
pushVirus(); | |
} | |
var food = []; | |
//build a 10x10 array to track zones | |
/* var zone = []; | |
for (var i = 0; i < 10; i++) { | |
zone.push([]); | |
} | |
for (var j = 0; j < 10; j++) { | |
for (var i = 0; i < 10; i++) { | |
zone[i].push([]); | |
} | |
} */ | |
//new food cells are pushed into a zone | |
//find food by each food's id and remove them from the array when eaten | |
//https://www.w3schools.com/jsref/jsref_indexof_array.asp | |
//use index of to find the index needed to delete | |
function pushFood(x, y) { | |
food.push({ | |
id: container.foodID, //IDs are used to that food can be removed from the zone tracker after it is eaten | |
x: x, | |
y: y, | |
color: rainbow(32, Math.floor(Math.random() * 32)), | |
r: 12, | |
rShift: Math.random() * 2 * Math.PI, | |
angle: Math.random() * 2 * Math.PI | |
}); | |
//add food to food zone tracker | |
/* var i = food.length - 1; | |
var zX = Math.floor(food[i].x / 1000); | |
var zY = Math.floor(food[i].y / 1000); | |
//add 3 array eleemnt at a time | |
zone[zX][zY].push(food[i].id,food[i].x,food[i].y); | |
container.foodID += 3; */ | |
} | |
//spawn food at start | |
for (var z = 0; z < container.totalFood; z++) { | |
pushFood(Math.random() * container.width, Math.random() * container.height); | |
} | |
//check if player is near food | |
function touchingFood() { | |
var length = cell.length; | |
for (var j = 0; j < length; j++) { | |
var i = food.length; | |
while (i--) { | |
var dX = food[i].x - cell[j].x; | |
var dY = food[i].y - cell[j].y; | |
var dist = Math.sqrt(dX * dX + dY * dY); | |
if (dist < cell[j].r - 2 * food[i].r) { | |
cell[j].r = Math.sqrt(cell[j].r * cell[j].r + food[i].r * food[i].r); | |
//remove food from zone array | |
/* var zoneX = Math.floor(food[i].x / 1000); | |
var zoneY = Math.floor(food[i].y / 1000); | |
var index = zone[zoneX][zoneY].indexOf(food[i].id) | |
zone[zoneX][zoneY].splice(index,3); */ | |
//remove from food array | |
food.splice(i, 1); | |
//player.waves += ((Math.random() > 0.5) ? 1 : -1); | |
} else if (dist < cell[j].r + food[i].r * 2) { | |
var y = cell[j].y - food[i].y; | |
var x = cell[j].x - food[i].x; | |
var angle = Math.atan2(y, x); | |
var speed = 1 + Math.sqrt(cell[j].Vx * cell[j].Vx + cell[j].Vy * cell[j].Vy); | |
food[i].x += speed * Math.cos(angle); // + cell[j].Vx; | |
food[i].y += speed * Math.sin(angle); // + cell[j].Vy; | |
} | |
} | |
} | |
} | |
function draw() { | |
//clear screen | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
//scale and translate | |
//exponential function that controls how scale depends on cell raidus | |
if (container.spectate) { | |
container.scaleGoal = 0.83 * Math.exp(-0.001941 * cell[container.cellFocus].r); | |
} | |
if (container.scaleGoal < 0.1) container.scaleGoal = 0.1; //max zoom | |
container.scale += (container.scaleGoal - container.scale) * 0.01; //smooth zooming | |
container.CameraX += (cell[container.cellFocus].x - container.CameraX) * 0.03; //smooth translation X | |
container.CameraY += (cell[container.cellFocus].y - container.CameraY) * 0.03; //smooth translation Y | |
//transforms and rotates the canvas camera | |
ctx.save(); | |
ctx.scale(container.scale, container.scale); | |
if (container.spectate) { | |
ctx.translate(window.innerWidth * 0.5 / container.scale - container.CameraX, window.innerHeight * 0.5 / container.scale - container.CameraY); | |
} else { | |
ctx.translate(50, 50); | |
} | |
//draw graph paper background | |
var size = 50; | |
var length = Math.ceil(container.width / size) + 1; | |
ctx.lineWidth = 0.5; | |
ctx.strokeStyle = '#707070'; | |
ctx.beginPath(); | |
for (var i = 0; i < length; i++) { | |
ctx.moveTo(i * size, 0); | |
ctx.lineTo(i * size, container.height); | |
} | |
var length = Math.ceil(container.height / size) + 1; | |
for (var i = 0; i < length; i++) { | |
ctx.moveTo(0, i * size); | |
ctx.lineTo(container.width, i * size); | |
} | |
ctx.stroke(); | |
//draw food | |
length = food.length; | |
var root3p2 = Math.sqrt(3) / 2 | |
for (var i = 0; i < length; i++) { | |
ctx.fillStyle = food[i].color; | |
if (container.scale > 0.4) { //if zoomed in draw hexagons | |
ctx.save(); | |
food[i].angle += 0.007; //food rotation speed | |
food[i].r = 12 + 1 * Math.sin(container.cycle * 0.08 + food[i].rShift); | |
ctx.translate(food[i].x, food[i].y); | |
ctx.rotate(food[i].angle); | |
ctx.translate(-food[i].x, -food[i].y); | |
ctx.beginPath(); | |
ctx.lineTo(food[i].x + 0.5 * food[i].r, food[i].y + root3p2 * food[i].r); | |
ctx.lineTo(food[i].x + food[i].r, food[i].y); | |
ctx.lineTo(food[i].x + 0.5 * food[i].r, food[i].y - root3p2 * food[i].r); | |
ctx.lineTo(food[i].x - 0.5 * food[i].r, food[i].y - root3p2 * food[i].r); | |
ctx.lineTo(food[i].x - food[i].r, food[i].y); | |
ctx.lineTo(food[i].x - 0.5 * food[i].r, food[i].y + root3p2 * food[i].r); | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.restore(); | |
} else { // if zoomed out just draw circles | |
ctx.beginPath(); | |
ctx.arc(food[i].x, food[i].y, food[i].r, 0, 2 * Math.PI); | |
ctx.fill(); | |
} | |
} | |
//draw cells | |
ctx.globalAlpha = 0.9 | |
ctx.textAlign = "center"; | |
length = cell.length; | |
for (var i = length - 1; i > -1; i--) { | |
cell[i].rDraw += (cell[i].r - cell[i].rDraw) * 0.07; //controls cell raidus zoom | |
ctx.lineWidth = 12; | |
ctx.fillStyle = cell[i].color; | |
ctx.beginPath(); | |
ctx.arc(cell[i].x, cell[i].y, cell[i].rDraw, 0, 2 * Math.PI); | |
ctx.fill(); | |
ctx.strokeStyle = cell[i].stroke; | |
ctx.stroke(); | |
//cell names | |
ctx.lineWidth = 2; | |
//ctx.font = "bold " + Math.floor(24 + cell[i].rDraw / 8) + "px Arial"; | |
ctx.font = "bold " + Math.floor(6 + cell[i].rDraw / 5) + "px Arial"; | |
//ctx.fillStyle = 'white'; | |
//ctx.fillStyle = invertCssColor(cell[i].color); | |
ctx.fillStyle = cell[i].textColor; | |
ctx.fillText(cell[i].name, cell[i].x, cell[i].y + 5); | |
} | |
//draw viruses | |
length = virus.length; | |
for (var i = 0; i < length; i++) { | |
ctx.fillStyle = 'lime'; | |
ctx.lineWidth = 7; | |
ctx.strokeStyle = '#00e600'; | |
ctx.beginPath(); | |
if (container.scale > 0.4) { //if zoomed in draw spikes | |
var amp = 2.5; | |
var sineCount = 40; | |
//for loop is sinecount*4 | |
//angle = 360/(sineCount*4) * Math.PI / 180 * j; | |
for (var j = 0; j < 160; j++) { | |
var angle = 0.03927 * j; //0.0448798951 * j; | |
var x = Math.round(virus[i].x + (virus[i].r + amp * Math.sin(sineCount * angle)) * Math.cos(angle)); | |
var y = Math.round(virus[i].y + (virus[i].r + amp * Math.sin(sineCount * angle)) * Math.sin(angle)); | |
ctx.lineTo(x, y); | |
} | |
} else { | |
ctx.arc(virus[i].x, virus[i].y, virus[i].r, 0, 2 * Math.PI); | |
} | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.stroke(); | |
} | |
ctx.globalAlpha = 1; | |
//info for all on screen | |
if (container.info) { | |
var length = cell.length; | |
ctx.textAlign = "left"; | |
ctx.font = "15px Arial"; | |
for (var i = 0; i < length; i++) { | |
ctx.save(); | |
ctx.translate(cell[i].x, cell[i].y - cell[i].r - 3); | |
ctx.scale(1 / container.scale, 1 / container.scale); | |
var x = -60 | |
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; | |
ctx.fillRect(x - 5, -5, 130, -110); | |
ctx.fillStyle = 'white'; | |
ctx.fillText("seekFood = " + Math.round(cell[i].seekFood), x, -100); | |
ctx.fillText("seekCell = " + Math.round(cell[i].seekCell), x, -85); | |
ctx.fillText("fleeCell = " + Math.round(cell[i].fleeCell), x, -70); | |
ctx.fillText("fleeVirus = " + Math.round(cell[i].fleeVirus), x, -55); | |
ctx.fillText("inertia = " + Math.round(cell[i].inertia), x, -40); | |
ctx.fillText("division = " + Math.round(cell[i].division), x, -25); | |
ctx.fillText("radius = " + Math.round(cell[i].r), x, -10); | |
ctx.restore(); | |
} | |
} | |
ctx.restore(); //undo translate and scale effects | |
//player info | |
/* if (container.spectate) { | |
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; | |
//ctx.fillRect(8, window.innerHeight - 5, 120, -60); | |
ctx.fillRect(8, window.innerHeight - 5, 120, -110); | |
ctx.fillStyle = 'white'; | |
ctx.textAlign = "left"; | |
ctx.font = "15px Arial"; | |
ctx.fillText("seekFood = " + Math.round(cell[container.cellFocus].seekFood), 11, window.innerHeight - 100); | |
ctx.fillText("seekCell = " + Math.round(cell[container.cellFocus].seekCell), 11, window.innerHeight - 85); | |
ctx.fillText("fleeCell = " + Math.round(cell[container.cellFocus].fleeCell), 11, window.innerHeight - 70); | |
ctx.fillText("fleeVirus = " + Math.round(cell[container.cellFocus].fleeVirus), 11, window.innerHeight - 55); | |
ctx.fillText("inertia = " + Math.round(cell[container.cellFocus].inertia), 11, window.innerHeight - 40); | |
ctx.fillText("division = " + Math.round(cell[container.cellFocus].division), 11, window.innerHeight - 25); | |
ctx.fillText("radius = " + Math.round(cell[container.cellFocus].r), 11, window.innerHeight - 10); | |
//leader board | |
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; | |
ctx.fillRect(window.innerWidth - 5, 25, -200, 320); | |
ctx.fillStyle = 'white'; | |
ctx.textAlign = "center"; | |
ctx.font = "bold 26px Arial"; | |
ctx.fillText("Leaderboard", window.innerWidth - 105, 55); | |
ctx.font = "bold 19px Arial"; | |
var leaderLength = 10; | |
if (cell.length < 10) leaderLength = cell.length; | |
for (var i = 0; i < leaderLength; i++) { | |
ctx.fillText(i + 1 + ". " + cell[i].name, window.innerWidth - 105, 88 + i * 27); | |
} | |
} */ | |
} | |
function cycle() { | |
container.cycle++; | |
draw(); | |
if (!container.pause) { | |
touchingFood(); | |
cellMove(); | |
spawner(); | |
} | |
requestAnimationFrame(cycle); | |
} | |
requestAnimationFrame(cycle); | |
var myVar = setInterval(function() { | |
sortCells(); | |
}, 6000); | |
function sortCells() { | |
// sort by value | |
cell.sort(function(a, b) { | |
if (a.r > b.r) return -1; //flip the signs on the 1s to reverse order | |
if (a.r < b.r) return 1; | |
return 0; | |
}); | |
} | |
function colorMutate(color) { | |
var index = Math.ceil(Math.random() * 6) | |
var char = color.charAt(index); | |
if (Math.random() > 0.5) { | |
char = shiftCharUp(char); | |
char = shiftCharUp(char); | |
} else if (Math.random() > 0.5) { | |
char = shiftCharDown(char); | |
char = shiftCharDown(char); | |
} | |
return color.substr(0, index) + char + color.substr(index + 1); | |
} | |
function shiftCharUp(char) { | |
switch (char) { | |
case "0": | |
char = '1' | |
break; | |
case "1": | |
char = '2' | |
break; | |
case "2": | |
char = '3' | |
break; | |
case "3": | |
char = '4' | |
break; | |
case "4": | |
char = '5' | |
break; | |
case "5": | |
char = '6' | |
break; | |
case "6": | |
char = '7' | |
break; | |
case "7": | |
char = '8' | |
break; | |
case "8": | |
char = '9' | |
break; | |
case "9": | |
char = 'a' | |
break; | |
case "a": | |
char = 'b' | |
break; | |
case "b": | |
char = 'c' | |
break; | |
case "c": | |
char = 'd' | |
break; | |
case "d": | |
char = 'e' | |
break; | |
default: | |
char = 'f' | |
} | |
return char; | |
} | |
function shiftCharDown(char) { | |
switch (char) { | |
case "2": | |
char = '1' | |
break; | |
case "3": | |
char = '2' | |
break; | |
case "4": | |
char = '3' | |
break; | |
case "5": | |
char = '4' | |
break; | |
case "6": | |
char = '5' | |
break; | |
case "7": | |
char = '6' | |
break; | |
case "8": | |
char = '7' | |
break; | |
case "9": | |
char = '8' | |
break; | |
case "a": | |
char = '9' | |
break; | |
case "b": | |
char = 'a' | |
break; | |
case "c": | |
char = 'b' | |
break; | |
case "d": | |
char = 'c' | |
break; | |
case "e": | |
char = 'd' | |
break; | |
case "f": | |
char = 'e' | |
break; | |
default: | |
char = '0' | |
} | |
return char; | |
} | |
function randomNameAustin(){ | |
var name= ['Arron','Adam','Adrian','Alan','Alex','Albert','Alec','Alvin','Armando','Anderson','Andres','Andrew','Anthony','Anton','Antonio','Arnold','Alejandro','Ben','Boe','Blake','Byron','Bjorn','Bart','Bin','Boof','Bartholemeu','Birtha','Alexa','Erin','Charles','Charlie','Chalk','Chalupa','Candice','Chatauqua','Channing','Cody','Colonel','Drew','Dendrite','Dawn','Dorothy','Dibble','Dolby','Drake','Effigy','Ephemeral','Endive','Eloquent','Fat','Full','Fun','Fandrew','Guy','Gory','Gland','Gargantuant','Glop','Gope','Hilt','Hunter','Huntington','Igloo','Ink','Interior','Jack','Jalopy','Jaunt','Jubilance','Kool','Kwan','Knox','Krook','Lewis','Lawn','Lorpe','Link','Latch','Man','Mourn','Map','Maude','Max','Nando','Null','Nix','Ophelia','Operation','Prawn','Paul','Patricia']; | |
var adj= ['Arcadian', | |
'Baleful', | |
'Bellicose', | |
'Bilious', | |
'Boorish', | |
'Calamitous', | |
'Caustic', | |
'Cerulean', | |
'Comely', | |
'Concomitant', | |
'Contumacious', | |
'Corpulent', | |
'Crapulous', | |
'Defamatory', | |
'Didactic', | |
'Dilatory', | |
'Dowdy', | |
'Efficacious', | |
'Effulgent', | |
'Egregious', | |
'Endemic', | |
'Equanimous', | |
'Execrable', | |
'Fastidious', | |
'Feckless', | |
'Fecund', | |
'Friable', | |
'Fulsome', | |
'Garrulous', | |
'Guileless', | |
'Gustatory', | |
'Heuristic', | |
'Histrionic', | |
'Hubristic', | |
'Incendiary', | |
'Insidious', | |
'Insolent', | |
'Intransigent', | |
'Inveterate', | |
'Invidious', | |
'Irksome', | |
'Jejune', | |
'Jocular', | |
'Judicious', | |
'Lachrymose', | |
'Limpid', | |
'Loquacious', | |
'Luminous', | |
'Mannered', | |
'Mendacious', | |
'Meretricious', | |
'Minatory', | |
'Mordant', | |
'Munificent', | |
'Nefarious', | |
'Noxious', | |
'Obtuse', | |
'Parsimonious', | |
'Pendulous', | |
'Pernicious', | |
'Pervasive', | |
'Petulant', | |
'Platitudinous', | |
'Precipitate', | |
'Propitious', | |
'Puckish', | |
'Querulous', | |
'Quiescent', | |
'Rebarbative', | |
'Recalcitrant', | |
'Redolent', | |
'Rhadamanthine', | |
'Risible', | |
'Ruminative', | |
'Sagacious', | |
'Salubrious', | |
'Sartorial', | |
'Sclerotic', | |
'Serpentine', | |
'Spasmodic', | |
'Strident', | |
'Taciturn', | |
'Tenacious', | |
'Tremulous', | |
'Trenchant', | |
'Turbulent', | |
'Turgid', | |
'Ubiquitous', | |
'Uxorious', | |
'Verdant', | |
'Voluble', | |
'Voracious', | |
'Wheedling', | |
'Withering', | |
'Zealous', | |
]; | |
var adjselec = adj[Math.floor(Math.random() * adj.length)]; | |
var nameselec = name[Math.floor(Math.random() * name.length)]; | |
return adjselec +' ' + nameselec; | |
} | |
/* | |
function AI() { | |
//moves towards the sum of all distance wieghted food pointing vectors | |
var inertia = 0.000002 // a larger coefficient makes the bot less likely to change direction | |
var vector = { | |
x: inertia * Math.cos(player.dir), | |
y: inertia * Math.sin(player.dir) | |
}; | |
if (container.visual) { | |
ctx.strokeStyle = 'rgba(0, 255, 0, 0.3)';//'green'; | |
ctx.lineWidth = 4; | |
ctx.beginPath(); | |
ctx.moveTo(0.5 * window.innerWidth, 0.5 * window.innerHeight); | |
ctx.lineTo(300000000 * vector.x + 0.5 * window.innerWidth, 300000000 * vector.y + 0.5 * window.innerHeight); | |
ctx.stroke(); | |
ctx.lineWidth = 1; | |
ctx.strokeStyle = 'black'; | |
} | |
var length = food.length; | |
for (var i = 0; i < length; i++) { | |
var dy = food[i].y - (player.y); | |
var dx = food[i].x - (player.x); | |
var dist = Math.sqrt(dx * dx + dy * dy) - player.r;; | |
dx = dx / dist / (dist * dist * dist) // x/dist is a directionless unit vector | |
dy = dy / dist / (dist * dist * dist) // 1/(dist*dist*dist) weights the closer vectors higher | |
vector.x += dx; | |
vector.y += dy; | |
//vector visualization | |
if (dist < 800 && container.visual) { | |
ctx.beginPath(); | |
ctx.moveTo(0.5 * window.innerWidth, 0.5 * window.innerHeight); | |
ctx.lineTo(300000000 * dx + 0.5 * window.innerWidth, 300000000 * dy + 0.5 * window.innerHeight); | |
ctx.stroke(); | |
} | |
} | |
length = virus.length; | |
for (var i = 0; i < length; i++) { | |
if (virus[i].r < player.r) { | |
var dy = virus[i].y - (player.y); | |
var dx = virus[i].x - (player.x); | |
var dist = Math.sqrt(dx * dx + dy * dy) - player.r; | |
dx = 10 * dx / dist / (dist * dist * dist * dist) // x/dist is a directionless unit vector | |
dy = 10 * dy / dist / (dist * dist * dist * dist) // 1/(dist*dist*dist*dist) wieghts the closer vectors | |
vector.x -= dx; | |
vector.y -= dy; | |
if (dist < 800 && container.visual) { | |
ctx.strokeStyle = 'red'; | |
ctx.beginPath(); | |
ctx.moveTo(0.5 * window.innerWidth, 0.5 * window.innerHeight); | |
ctx.lineTo(-300000000 * dx + 0.5 * window.innerWidth, -300000000 * dy + 0.5 * window.innerHeight); | |
ctx.stroke(); | |
} | |
} | |
} | |
//find the angle of the vector | |
player.dir = Math.atan2(vector.y, vector.x); | |
player.move(); | |
}*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/464612/agarAIcolorfunctions.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
background-color: #f0faff; | |
overflow:hidden; | |
} | |
canvas { | |
position: absolute; | |
left: 0; | |
top: 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment