Created
November 13, 2021 19:51
-
-
Save slacktracer/b58932114643f8b6756f35c484a9f8c0 to your computer and use it in GitHub Desktop.
agar.ai
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
<!-- | |
<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> | |
--> |
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 | |
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); |
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 { | |
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