基于p5.js的图形化遗传算法繁殖功能故障排查求助
Hey David, let's work through the breeding issues in your genetic algorithm—there are several key mistakes in the breed() method and related code that are throwing off your evolution logic. Let's break down each problem and fix them step by step:
1. Critical Scope & Chromosome Crossbreeding Issues
- Missing Gene Length Property: In your
Populationconstructor, you acceptg(gene length) but don't store it as an instance property. This means when you useginside thebreed()method, it's undefined (sincegis only in the constructor's local scope). Fix this by addingthis.geneLength = g;to thePopulationconstructor. - Broken Probability Group Integrity: Your sheep's chromosome is structured in groups of 4 genes that sum to 1 (for hungry/eaten movement states). Your current crossbreeding logic splits chromosomes at a random midpoint, which can cut these groups in half, breaking the probability sum constraint. Instead, cross at group boundaries (every 4 genes) to keep each movement state's probabilities valid.
2. Invalid Mutation Logic
Your current mutation sets a gene to Math.floor(Math.random()*10+1)—this creates values between 1 and 10, which completely breaks your probability-based movement logic (your genes should be between 0 and 1, and each group of 4 must sum to 1). Instead, when mutating, we need to adjust a gene slightly while preserving the sum of its group.
3. Typo & Elite Retention Flaws
- Wrong Constructor: You're creating new offspring with
new Eater(g, newchromosome)but your class is namedSheep—this is a typo that will throw errors. - Elite Handling: Your current code mutates the elite sheep first, then copies its chromosomes to a random offspring. A better approach is to clone the elite's chromosomes, apply a low-probability gentle mutation (if desired), then replace a random offspring with this modified elite.
4. Simplified Population Replacement
Instead of looping to copy each sheep from newsheep to this.sheep, you can directly assign this.sheep = newsheep; for cleaner, more efficient code.
Fixed Full Code
Here's the revised code with all these issues addressed, plus a few quality-of-life improvements:
var settings = { populationSize : 25, geneLength : 8, mutationProbability : 0.01, forestSize : 1500, rows : 124, cols : 249, year : 250, end : 20, }; function onCanvas(position){ return position*4+2; } function randombetween(min, max){ return Math.random()*(max - min) + min; // Fixed: now properly respects min/max range } //set up sheep var population = new Population(settings.populationSize, settings.geneLength); function Sheep(g, dna){ this.genLen = g; this.state = 0; this.fitness=0; this.xpos = Math.floor(Math.random()*settings.cols); this.ypos = Math.floor(Math.random()*settings.rows); this.chromosome = new Array(this.genLen); if (dna != null){ this.chromosome = [...dna]; // Create a copy to avoid reference sharing } else{ for(var x=0; x<this.genLen; x+=4){ this.chromosome[x] = Math.random(); this.chromosome[x+1] = randombetween(0, 1-this.chromosome[x]); this.chromosome[x+2] = randombetween(0, 1-this.chromosome[x]-this.chromosome[x+1]); this.chromosome[x+3] = 1-this.chromosome[x]-this.chromosome[x+1]-this.chromosome[x+2]; } } } function Population(p, g){ this.popSize = p; this.geneLength = g; // Store gene length as instance property this.sheep = []; this.matingPool = []; this.maxFit; this.maxFitIndex; for (var x = 0; x < this.popSize; x++) { this.sheep[x] = new Sheep(g, null); } this.evaluate = function() { //find maximum fitness in generation this.maxFit = 0; this.maxFitIndex = 0; for (var x = 0; x < this.popSize; x++) { if (this.sheep[x].fitness > this.maxFit){ this.maxFitIndex = x; this.maxFit = this.sheep[x].fitness; } } //normalize fitness for (var i = 0; i < this.popSize; i++) { this.sheep[i].fitness /= this.maxFit; } //reset mating pool every generation this.matingPool = []; //higher fitness means more representation in the pool (increased multiplier for better selection) for (var i = 0; i < this.popSize; i++) { var n = this.sheep[i].fitness * 100; for (var j = 0; j < n; j++) { this.matingPool.push(this.sheep[i]); } } } //create children sheep this.breed = function (){ var newsheep = []; for (var i = 0; i < this.popSize; i++){ //pick random parents from the mating pool let parentA = this.matingPool[Math.floor(Math.random()*this.matingPool.length)]; let parentB = this.matingPool[Math.floor(Math.random()*this.matingPool.length)]; //parent genes are crossed at group boundaries (preserve probability sums) var newchromosome = []; // Choose to cross after the first movement state group or keep it whole var crossGroup = Math.random() > 0.5 ? 4 : 0; for (var j = 0; j < this.geneLength; j++){ newchromosome[j] = j < crossGroup ? parentA.chromosome[j] : parentB.chromosome[j]; } //offspring may be mutated (preserving probability group sums) if(Math.random() <= settings.mutationProbability){ // Pick a random group of 4 genes var groupStart = Math.floor(Math.random() * (this.geneLength / 4)) * 4; // Mutate one gene slightly var mutateIndex = groupStart + Math.floor(Math.random()*4); var mutationAmount = randombetween(-0.1, 0.1); newchromosome[mutateIndex] += mutationAmount; // Clamp to valid 0-1 range newchromosome[mutateIndex] = Math.max(0, Math.min(1, newchromosome[mutateIndex])); // Adjust last gene in group to keep sum at 1 var sum = newchromosome[groupStart] + newchromosome[groupStart+1] + newchromosome[groupStart+2]; newchromosome[groupStart+3] = 1 - sum; } newsheep[i] = new Sheep(this.geneLength, newchromosome); // Fixed typo: Eater → Sheep } //elite offspring survive into next generation, replacing a random offspring var randomIndex = Math.floor(Math.random()*this.popSize); // Clone elite's chromosome to avoid reference issues var eliteChromosome = [...this.sheep[this.maxFitIndex].chromosome]; // Optional: low-probability gentle mutation for elite if(Math.random() <= settings.mutationProbability * 0.5){ var groupStart = Math.floor(Math.random() * (this.geneLength / 4)) * 4; var mutateIndex = groupStart + Math.floor(Math.random()*4); var mutationAmount = randombetween(-0.05, 0.05); eliteChromosome[mutateIndex] += mutationAmount; eliteChromosome[mutateIndex] = Math.max(0, Math.min(1, eliteChromosome[mutateIndex])); var sum = eliteChromosome[groupStart] + eliteChromosome[groupStart+1] + eliteChromosome[groupStart+2]; eliteChromosome[groupStart+3] = 1 - sum; } newsheep[randomIndex] = new Sheep(this.geneLength, eliteChromosome); //update array of sheep this.sheep = newsheep; // Simplified assignment } } //set up trees var forest = new Forest(settings.forestSize); function radialTreePopulation(x,y,r,count){ let trees = []; for(let i = 0;i < count; i++){ trees.push({ posx : (x + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1))), posy : (y + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1))) }); } return trees; } function Forest(f){ this.forSize = f/ 75; this.trees = []; for (var x = 0; x < this.forSize ; x++) { this.trees.push(...radialTreePopulation( (Math.floor(Math.random()*(settings.cols-20)+10))| 0, (Math.floor(Math.random()*(settings.rows-20)+10))| 0, 11, 75) ); } } //evaluate how to move function moveHungry(x, move){ if(move < population.sheep[x].chromosome[0]){ return 0; } else if(move - population.sheep[x].chromosome[0] < population.sheep[x].chromosome[1]){ return 1; } else if(move - population.sheep[x].chromosome[0] - population.sheep[x].chromosome[1] < population.sheep[x].chromosome[2]){ return 2; } else{ return 3; } } function moveEaten(x,move){ if(move < population.sheep[x].chromosome[4]){ return 0; } else if(move - population.sheep[x].chromosome[4] < population.sheep[x].chromosome[5]){ return 1; } else if(move - population.sheep[x].chromosome[4] - population.sheep[x].chromosome[5] < population.sheep[x].chromosome[6]){ return 2; } else{ return 3; } } //count generations and days var generation=0; var counter = 0; //create world function createWorld(){ background("lightblue"); fill(0,255,0); for(var x=0; x<settings.forestSize; x++){ // Only draw trees that haven't been eaten if(forest.trees[x].posx !== null && forest.trees[x].posy !== null){ rect(onCanvas(forest.trees[x].posx), onCanvas(forest.trees[x].posy), 4, 4); } } fill(255,0,0); for(var x=0; x<settings.populationSize; x++){ population.sheep[x].state=0; rect(onCanvas(population.sheep[x].xpos), onCanvas(population.sheep[x].ypos), 4, 4); } //remove eaten trees for(var x=0; x<settings.populationSize; x++){ for(var y=0; y<settings.forestSize; y++){ if(population.sheep[x].xpos === forest.trees[y].posx && population.sheep[x].ypos === forest.trees[y].posy && forest.trees[y].posx !== null){ forest.trees[y].posx=null; forest.trees[y].posy=null; population.sheep[x].state=1; population.sheep[x].fitness++; } } } //move sheep based on chromosome for(var x=0; x<settings.populationSize; x++){ var move = Math.random(); if(population.sheep[x].state==0){ switch(moveHungry(x, move)){ case 0: //up if(population.sheep[x].ypos>0) population.sheep[x].ypos-=1; break; case 1: //down if(population.sheep[x].ypos<settings.rows-1) population.sheep[x].ypos+=1; break; case 2: //right if(population.sheep[x].xpos<settings.cols-1) population.sheep[x].xpos+=1; break; case 3: //left if(population.sheep[x].xpos>0) population.sheep[x].xpos-=1; } } else { switch(moveEaten(x, move)){ case 0: //up if(population.sheep[x].ypos>0) population.sheep[x].ypos-=1; break; case 1: //down if(population.sheep[x].ypos<settings.rows-1) population.sheep[x].ypos+=1; break; case 2: //right if(population.sheep[x].xpos<settings.cols-1) population.sheep[x].xpos+=1; break; case 3: //left if(population.sheep[x].xpos>0) population.sheep[x].xpos-=1; } } } counter++; } function reset(){ counter=0; //regrow forest forest = new Forest(settings.forestSize); //reset locations and fitness values for(var x=0; x<settings.populationSize; x++){ population.sheep[x].xpos = Math.floor(Math.random()*settings.cols); population.sheep[x].ypos = Math.floor(Math.random()*settings.rows); population.sheep[x].fitness=0; } } function setup() { createCanvas(1000, 500); } function draw() { createWorld(); if(counter>=settings.year){ population.evaluate(); population.breed(); reset(); generation++; if(generation>=settings.end){ noLoop(); } } }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
Additional Notes
- Fixed the
randombetweenfunction to properly respect the min/max range (original code ignored the min parameter). - Increased the mating pool multiplier from 10 to 100 to give higher-fitness sheep more representation in selection.
- Added checks in
createWorldto avoid drawing trees that have already been eaten. - Used spread syntax (
[...dna]) to clone chromosomes, preventing unintended reference sharing between parents and offspring.
内容的提问来源于stack exchange,提问作者David Wu

