xxxxxxxxxx
393
const startx = 25
const starty = 25
var drawsdone = 0;
var particlegen = 0;
/*
0,1 : x,y movement influenced by time
2,3 : lifespan
4,5 : index of next x,y
6,7 : step of next x, y
8,9 : growths of step x
10,11: growths of step y
*/
const XES = 50
const YES = 50
const DEFAULT_STARTER_GENES = [
/* x, y */ 10,10,
/* life */ 1,1,
/* index */ 0,0,
/* step */ 1,1,
/* stepgrowth */ 0.5,0.5,0.5,0.5,
/* AMT OF XES, YES, VERY IMPORTANT TO MATCH */ XES,YES,
/* 10 xes */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,
/* 10 yes */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0,
];
var COMAX = 20
var distscale;
const initial_gene_length = 10;
var t = 0;
var dt = 0.025 // time delta
const particles = [];
const AMT = 555;
var best_genes;
var text_field_gene_load;
function setup() {
createCanvas(800, 600);
linevas = createGraphics(800, 600);
distscale = dist(startx, starty, width,height)
var ancestor_gene = localStorage.getItem('best-genes');
if (ancestor_gene == undefined) {
ancestor_gene = DEFAULT_STARTER_GENES;
} else {
ancestor_gene = JSON.parse(ancestor_gene)
best_genes = ancestor_gene.slice(0);
}
print(`starting with ancestor [${ancestor_gene.slice(0).map(e=>e.toFixed(1))}]`)
// add some particles
for (var i = 0; i < AMT; i++) {
particles.push(new Particle(ancestor_gene.slice(0)));
particles[i].mutate_genes();
}
createButton('reset-best-genes').mousePressed(function(e){
print(`reset best genes. best was: ${best_genes}`)
localStorage.setItem('best-genes', JSON.stringify(DEFAULT_STARTER_GENES))
particles.splice(0,particles.length)
for (var i = 0; i < AMT; i++) {
particles.push(new Particle(DEFAULT_STARTER_GENES.slice(0)));
particles[i].mutate_genes();
}
})
createButton('load-genes').mousePressed(function(e){
var val = text_field_gene_load.value()
print(`loading: ${val}`)
var parsedval = JSON.parse(val)
localStorage.setItem('best-genes', val)
particles.splice(0,particles.length)
for (var i = 0; i < AMT; i++) {
particles.push(new Particle(parsedval.slice(0)));
particles[i].mutate_genes();
}
})
text_field_gene_load= createInput('');
}
class Looper{
constructor(){
}
}
class Particle {
constructor(parent_genes){
this.life = 200;
this.id = particlegen++;
this.pos = createVector(startx, starty)
this.prevPos = this.pos.copy();
/*
0,1 : x,y movement influenced by time
2,3 : lifespan
4,5 : index of next x,y
6,7 : step of next x, y
8,9 : growths of step x
10,11: growths of step y
*/
this.updates=0;
this.genes = parent_genes;
this.setcolor()
this.distances = [0,0,0,0,0]; // -x, -y, x, and y distance traveled
}
setcolor(){
this.color = color(
noise(this.genes.length, t) * 255,
noise(t, this.id) * 255,
noise((t + 45358475) << 3, this.id) * 255
);
}
mutate_genes(x=1){
// mutate genes by adding a small random value to each element
for (let i = 0; i < this.genes.length; i++) {
if (i > 12){
this.genes[i] += random(-1*x,1*x);
} else {
this.genes[i] += random(-0.02*x,0.02*x);
}
}
this.genes[0] = min(COMAX,max(this.genes[0],-COMAX))
this.genes[1] = min(COMAX,max(this.genes[1],-COMAX))
}
update(){
// update position based on genes and current time
this.prevPos = this.pos.copy();
this.pos.x += this.genes[0] * noise(t) // dt + noise(t) // map(noise(t), 0,1,-1,1);
this.pos.y += this.genes[1] * noise(t+1000) // dt + noise(t+1000) // map(noise(t+1000),0,1,-1,1);
this.life -= max(this.genes[2],this.genes[3])
this.life += min(this.genes[2],this.genes[3])
this.updates++;
if (this.updates > 1000) {
print("some particle was really old")
this.life = -1
}
let center = createVector(width / 2, height / 2);
let dir = p5.Vector.sub(center, this.pos);
dir.normalize();
this.pos.add(dir.mult(this.genes[0] * noise(t)));
this.life -= max(this.genes[2], this.genes[3]);
this.life += min(this.genes[2], this.genes[3]);
this.x += this.speedX;
this.y += this.speedY;
// Applying the nudge
let noiseScale = 0.01; // Change this value to change the granularity of the noise
let nudgeMagnitude = 2.0; // Change this to control the strength of the nudge
let nudgeX = noise(this.x * noiseScale, this.y * noiseScale) * 2 - 1; // noise() returns a value between 0 and 1, so we shift it to be between -1 and 1
let nudgeY = noise(this.y * noiseScale, this.x * noiseScale) * 2 - 1;
this.x += nudgeX * nudgeMagnitude;
this.y += nudgeY * nudgeMagnitude;
/*
0,1 : x,y movement influenced by time
2,3 : lifespan
4,5 : index of next x,y
6,7 : step of next x, y
8,9 : growths of step x
10,11: growths of step y
*/
// this breaks the code currently
// get indices for movement coefficients
const ix = Math.floor(this.genes[4]);
const iy = Math.floor(this.genes[5]);
// add the 'step' to the index
this.genes[4] += abs(this.genes[6]);
this.genes[5] += abs(this.genes[7]);
// grow the steps
this.genes[6] += (noise(t) > 0.5) ? this.genes[8] : this.genes[9]
this.genes[7] += (noise(t) > 0.5) ? this.genes[10] : this.genes[11]
const Xes = XES;
const Yes = YES;
const xv = this.genes[12 + (ix % Xes)]
const yv = this.genes[12+Xes + (iy % Yes)]
// if (t==0 && this.id == 1){
// console.log(" x, y movement:", this.genes[0].toFixed(1), this.genes[1].toFixed(1));
// console.log(" lifespan:", this.genes[2].toFixed(1), this.genes[3].toFixed(1));
// console.log(" index of next x,y:", this.genes[4].toFixed(1), this.genes[5].toFixed(1));
// console.log(" step of next x, y:", this.genes[6].toFixed(1), this.genes[7].toFixed(1));
// console.log(" step growths:", this.genes[8].toFixed(1), this.genes[9].toFixed(1), this.genes[10].toFixed(1), this.genes[11].toFixed(1));
// console.log(" amount of xes and ys:", this.genes[12].toFixed(1), this.genes[13].toFixed(1))
// }
this.pos.x += xv
this.pos.y += yv
var d = this.prevPos.dist(this.pos) / distscale;
var ls= this.updates/200;
this.distances[4]+=d
var dx = ls*d *(this.pos.x-this.prevPos.x)
var dy = ls*d *(this.pos.y-this.prevPos.y)
if (dx > 0) {this.distances[2]+=dx} else {this.distances[0]+=dx}
if (dy > 0) {this.distances[3]+=dx} else {this.distances[1]+=dy}
this.distances[4]+=ls*d
}
draw() {
// Modify alpha based on life
this.color.setAlpha(map(this.life, 0, 200, 0, 255));
linevas.stroke(this.color);
linevas.line(this.prevPos.x, this.prevPos.y, this.pos.x, this.pos.y);
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, 5, 5);
}
outOfBounds(){return (this.pos.x < 0 || this.pos.x > width || this.pos.y < 0 || this.pos.y > height);}
}
// Generate a random gene of length n
function generateGene(n) {
const genes = [];
for (let i = 0; i < n; i++) {
genes.push(random(-1, 1));
}
return genes;
}
const scoreFunction = (particle) => {
const score = particle.updates +abs(particle.distances[0]) + abs(particle.distances[1]) + particle.distances[2] + particle.distances[3] + particle.distances[4]
return score + map(particle.life, 200, 0, -200, 0);
}
const scoreFunc2 = (particle) => {
const score = [art]
}
function getScores(particles) {
let minScore = Number.MAX_VALUE;
let maxScore = Number.MIN_VALUE;
let totalScore = 0;
particles.forEach(particle => {
const score = scoreFunction(particle);
minScore = Math.min(minScore, score);
maxScore = Math.max(maxScore, score);
totalScore += score;
});
return {
minScore,
maxScore,
averageScore: totalScore / particles.length
};
}
function draw() {
background(220);
image(linevas, 0, 0);
// select top 10% performing particles based on combined distance traveled
const topPerformers = particles.sort((a, b) => scoreFunction(b) - scoreFunction(a)).slice(0, Math.floor(particles.length * 0.1));
// const topPerformers = particles.sort((a, b) => {
// const aDistance = a.distances.reduce((sum, d) => sum + d**2);
// const bDistance = b.distances.reduce((sum, d) => sum + d**2);
// return (aDistance + a.distances[0]**2 + a.distances[1]**2) - (bDistance + b.distances[0]**2 + b.distances[1]**2);
// }).slice(0, Math.floor(particles.length * 0.1));
// const topPerformers = particles.sort((a, b) => {
// const aDistance = a.distances.reduce((sum, d) => sum + d**2);
// const bDistance = b.distances.reduce((sum, d) => sum + d**2);
// return (aDistance + a.distances[0]**2 + a.distances[1]**2) - (bDistance + b.distances[0]**2 + b.distances[1]**2);
// }).slice(0, Math.floor(particles.length * 0.1));
// const topPerformers = particles.sort((a, b) => a.distances.reduce((sum, d) => sum + d) - b.distances.reduce((sum, d) => sum + d)).slice(0, Math.floor(particles.length * 0.1));
// const worstPerformers = particles.sort((a, b) => b.distances.reduce((sum, d) => sum + d) - a.distances.reduce((sum, d) => sum + d)).slice(0, Math.floor(particles.length * 0.1));
for (var i = particles.length - 1; i > -1; i--) {
particles[i].update();
particles[i].draw();
// reset particles that are out of the screen or dead
if (
particles[i].outOfBounds() ||
particles[i].life <= 0
) {
const parentGene = random(topPerformers).genes;
particles[i].genes = parentGene.slice()
particles[i].pos = createVector(startx, starty)
particles[i].prevPos = particles[i].pos.copy()
particles[i].distances = [0,0,0,0,0]
particles[i].mutate_genes()
particles[i].updates = 0;
particles[i].setcolor()
particles[i].life = 200;
}
}
// updatePerformersDiv(bestPerformersDiv, topPerformers);
if (drawsdone % 250 == 0 ){
const scores = getScores(particles);
console.log(`min: ${scores.minScore}`); // lowest score
console.log(`max: ${scores.maxScore}`); // highest score
console.log(`avg: ${scores.averageScore}`); // average score
}
if (topPerformers && topPerformers.length>0 && drawsdone % 1000 ==0) {
if (1){
// print(`storing ${topPerformers[0]} aka ${JSON.stringify(topPerformers[0])}`)
localStorage.setItem('best-genes', JSON.stringify(topPerformers[0].genes))
print(`new best ${scoreFunction(topPerformers[0])}: [${topPerformers[0].genes.map(e=>e.toFixed(1))}]`)
best_genes=topPerformers[0].genes;
}
// print some stats about the best performers
// console.log("Best performers:");
// topPerformers.forEach(p => {
// console.log(` Gene: ${p.genes}`);
// console.log(` Life: ${p.life}`);
// console.log(` Distances: ${p.distances}`);
// });
// generate new particles based on the top performers, with slightly mutated genes
for (let i = 0; i < AMT - particles.length; i++) {
const parentGene = random(topPerformers).genes;
const mutatedGene = parentGene.slice();
var newp = new Particle(mutatedGene);
newp.mutate_genes();
particles.push(newp);
}
}
// if (worstPerformers && worstPerformers.length>0 && t % 1000 === 0) {
// // print some stats about the worst performers, every 100 frames
// console.log("Worst performers:");
// worstPerformers.forEach(p => {
// console.log(` Gene: ${p.genes}`);
// console.log(` Life: ${p.life}`);
// console.log(` Distances: ${p.distances}`);
// });
// }
t += dt;
drawsdone++;
}
// const bestPerformersDiv = document.createElement("div");
// bestPerformersDiv.style.cssText = "position: absolute; top: 0; right: 0; padding: 10px; background-color: rgba(255,255,255,0.8);";
// document.body.appendChild(bestPerformersDiv);
// const worstPerformersDiv = document.createElement("div");
// worstPerformersDiv.style.cssText = "position: absolute; top: 0; left: 0; padding: 10px; background-color: rgba(255,255,255,0.8);";
// document.body.appendChild(worstPerformersDiv);
function updatePerformersDiv(div, performers) {
// clear the contents of the div
div.innerHTML = "";
// add a title to the div
const title = document.createElement("h2");
title.textContent = performers[0].life > 0 ? "Best performers" : "Worst performers";
div.appendChild(title);
// add a list of performers to the div
const list = document.createElement("ul");
performers.forEach(p => {
const item = document.createElement("li");
item.textContent = `Gene: ${p.genes} Life: ${p.life} Distances: ${p.distances}`;
list.appendChild(item);
});
div.appendChild(list);
}