Skip to content

Instantly share code, notes, and snippets.

@slacktracer
Created November 13, 2021 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slacktracer/b58932114643f8b6756f35c484a9f8c0 to your computer and use it in GitHub Desktop.
Save slacktracer/b58932114643f8b6756f35c484a9f8c0 to your computer and use it in GitHub Desktop.
agar.ai
<!--
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="80" height="80" patternUnits="userSpaceOnUse">
<path id='path' d="M 80 0 L 0 0 0 80" fill="none" stroke="black" stroke-width="0.5"/>
</pattern>
</defs>
<rect id = 'background' x="-1000" y="-1000" width="800em" height="800em" fill="url(#grid)" />
</svg>
-->
/*ideas
make it so that cells below a size die out
they could turn into food
give each cell different traits: weights for attraction and repulsion viruses, food, other cells also when to divide
allow cell to have a size threshhold where they split into 2
splitting into two still fires one cell at others, so they can use it aggressively
the daughter cells have similar traits to the parent, but with some mutation
cell names coudl also mutation to give a hint at lineage
maybe...viruses force a split in many small cells?
maybe...connect processes like moving speed and resting to comsumption of mass
*/
var myVar = setInterval(function() {
sortCells();
}, 3000);
function sortCells() {
if (container.spectate) {
var largest = cell[0].name
// 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;
});
}
}
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.cycle = 0;
this.pause = false;
this.spectate = true;
this.CameraX = 0;
this.CameraY = 0;
this.x = 0;
this.y = 0;
this.width = 8000;
this.height = 8000;
this.scale = 1; //the zoom
this.scaleGoal = 1; //what the scale will smoothly move towards
this.totalFood = 0.000015 * (this.width * this.height);
this.totalVirus = 0.0000002 * (this.width * this.height);
this.totalCells = 0.0000006 * (this.width * this.height);
this.graphscale = 100;
this.scaleGraphPaper = function() {
var size = this.graphscale * this.scale * this.scale;
document.getElementById("grid").setAttribute('width', size);
document.getElementById("grid").setAttribute('height', size);
document.getElementById("path").setAttribute('d', "M " + size + " 0 L 0 0 0 " + size);
document.getElementById("path").setAttribute('stroke-width', size * 0.01);
};
};
//container.scaleGraphPaper();
/* var gui = new dat.GUI();
gui.close();
gui.add(container, 'pause');
gui.add(container, 'spectate');
gui.add(container, 'width', 100, 20000).step(100);
gui.add(container, 'height', 100, 20000).step(100);
gui.add(container, 'scale', 0.01, 1.7);
var fcontroller = gui.add(container, 'totalFood', 0, 9999);
fcontroller.onFinishChange(function(value) {
if (food.length > container.totalFood) food.splice(value);
});
var vcontroller = gui.add(container, 'totalVirus', 0, 99);
vcontroller.onFinishChange(function(value) {
if (virus.length > container.totalVirus) virus.splice(value);
});
var ccontroller = gui.add(container, 'totalCells', 1, 99);
ccontroller.onFinishChange(function(value) {
//if (cell.length>container.totalCells) cell.length = value;
if (cell.length > container.totalCells) cell.splice(value);
}); */
var cell = [];
function pushCell() {
var color = rainbow(32, Math.floor(Math.random() * 32)); //randomColor({ luminosity: 'bright' });
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 < 1 + Math.round(Math.random() * 15); k++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
text[0] = text.charAt(0).toUpperCase()
cell.push({
alive: true,
name: text,
x: Math.random() * container.width,
y: Math.random() * container.height,
Vx: 0,
Vy: 0,
dir: 0,
color: color,
stroke: ColorLuminance(color, -0.1),
r: 30, //30
rDraw: 30,
score: 0,
speed: 1,
});
}
for (var i = 0; i < container.totalCells; i++) {
pushCell();
}
//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 cellMove() {
var cellLength = cell.length;
for (var i = 0; i < cellLength; i++) {
//AI
var inertia = 0.000002;
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 = dx / dist / (dist * dist * dist);
dy = dy / dist / (dist * dist * dist);
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 = 5 * dx / dist / (dist * dist * dist * dist);
dy = 5 * dy / dist / (dist * dist * dist * dist);
vector.x -= dx;
vector.y -= dy;
}
}
length = cell.length; //chase and run from cells
for (j = 0; j < length; j++) {
if (i != j) {
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;
if (cell[j].r + 4 < cell[i].r) { //chase smaller cells
dx = dx / dist / (dist * dist * dist);
dy = dy / dist / (dist * dist * dist);
vector.x += 30 * dx;
vector.y += 30 * dy;
} else if (cell[i].r + 1 < cell[j].r) { //run from bigger cells
dx = dx / dist / (dist * dist * dist * dist);
dy = dy / dist / (dist * dist * dist * dist);
vector.x -= 8000 * dx;
vector.y -= 8000 * dy;
}
}
}
//direction
cell[i].dir = Math.atan2(vector.y, vector.x);
//move
cell[i].speed = 0.11 * (1 + 100 / cell[i].r + 2000 / cell[i].r / cell[i].r)
cell[i].Vx = 0.9 * cell[i].Vx + cell[i].speed * Math.cos(cell[i].dir);
cell[i].Vy = 0.9 * cell[i].Vy + cell[i].speed * Math.sin(cell[i].dir);
cell[i].x += cell[i].Vx;
cell[i].y += cell[i].Vy;
//walls
if (cell[i].x < container.x) {
cell[i].x = container.x;
} else if (cell[i].x > container.width) {
cell[i].x = container.width;
}
if (cell[i].y < container.y) {
cell[i].y = container.y;
} else if (cell[i].y > container.height) {
cell[i].y = container.height;
}
//mass loss
if (cell[i].r > 50) cell[i].r = 0.99995 * cell[i].r - 0.00000005 * cell[i].r * cell[i].r
//eat
length = cell.length
for (j = 0; j < length; j++) {
if (cell[i].r > cell[j].r + 3) {
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;
}
}
}
//touching virus
length = virus.length
for (j = 0; j < length; 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) {
cell[i].r *= 0.2;
if (cell[i].r < 30) cell[i].r = 30;
}
}
}
}
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: 50 + Math.random() * 25,
});
}
//spawn viruses at start
for (var i = 0; i < container.totalVirus; i++) {
pushVirus();
}
var food = [];
function pushfood() {
food.push({
x: Math.random() * container.width,
y: Math.random() * container.height,
color: rainbow(32, Math.floor(Math.random() * 32)), //randomColor({ luminosity: 'bright', }),
r: 12,
rShift: Math.random() * 2 * Math.PI,
angle: Math.random() * 2 * Math.PI
});
}
//spawn food at start
for (var i = 0; i < container.totalFood; i++) {
pushfood();
}
//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 - food[i].r) {
cell[j].r += food[i].r * 0.06;
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);
food[i].x += 6 * Math.cos(angle);
food[i].y += 6 * Math.sin(angle);
}
}
}
}
//repeating check to spawn food up the max value
function spawner() {
if (food.length < container.totalFood) {
pushfood();
}
if (cell.length < container.totalCells) {
pushCell();
}
if (virus.length < container.totalVirus) {
pushVirus();
}
}
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
container.scaleGoal = 0.838 * Math.exp(-0.001941 * cell[0].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[0].x - container.CameraX) * 0.05; //smooth translation X
container.CameraY += (cell[0].y - container.CameraY) * 0.05; //smooth translation Y
//transforms and rotates the canvas camera
ctx.save();
ctx.scale(container.scale, container.scale);
ctx.translate(window.innerWidth * 0.5 / container.scale - container.CameraX, window.innerHeight * 0.5 / container.scale - container.CameraY);
//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.textAlign = "center";
length = cell.length;
for (var i = length - 1; i > -1; i--) {
cell[i].rDraw += (cell[i].r - cell[i].rDraw) * 0.08; //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.fillStyle = 'white';
ctx.fillStyle = invertCssColor(cell[i].color);
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 = 6;
ctx.strokeStyle = '#00e600';
ctx.beginPath();
if (container.scale > 0.4) { //if zoomed in draw spikes
var amp = 2;
var sineCount = 35;
//for loop is sinecount*4
//angle = 360/(sineCount*4) * j * Math.PI / 180;
for (var j = 0; j < 140; j++) {
var angle = 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.restore(); //undo translate and scale effects
//player info
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
//ctx.fillRect(8, window.innerHeight - 5, 120, -60);
ctx.fillRect(8, window.innerHeight - 5, 120, -35);
ctx.fillStyle = 'white';
ctx.textAlign = "left";
ctx.font = "bold 20px Arial";
//ctx.fillText("Score: " + cell[0].score, 15, window.innerHeight - 40);
ctx.fillText("Mass: " + Math.round(cell[0].r), 15, window.innerHeight - 15);
//leader board
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
ctx.fillRect(window.innerWidth - 5, 5, -200, 320);
ctx.fillStyle = 'white';
ctx.textAlign = "center";
ctx.font = "bold 26px Arial";
ctx.fillText("Leaderboard", window.innerWidth - 105, 35);
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, 68 + i * 27);
}
}
function cycle() {
container.cycle++;
if (!container.pause) {
touchingFood();
cellMove();
spawner();
}
draw();
requestAnimationFrame(cycle);
}
requestAnimationFrame(cycle);
<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>
body {
font: 15px arial, sans-serif;
background-color: #f0faff;
overflow:hidden;
}
canvas {
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
svg{
position: absolute;
left: 0;
top: 0;
z-index: -2;
width: 100%;
height: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment