class System{
constructor(props){
this.ctx = props.ctx
this.width = props.width
this.height = props.height
this.numFrames = props.numFrames
this.numTicks = props.numTicks
this.stepSize = props.stepSize
this.numAnts = props.numAnts
this.numGen = props.numGen
this.basket = props.basket
this.mutRate = props.mutRate
this.mutSize = props.mutSize
this.colony = this.makeColony()
this.frame = 0
this.tick = 0
this.generation = 0
}
loop(){
this.ctx.clearRect(0,0,this.width,this.height)
this.drawBasket()
this.colony.map(ant => {
if(this.within(ant)){
ant.draw()
}else{
ant.step(ant.dna[this.tick],this.stepSize)
}
})
const text = d3.sum(this.colony,ant => this.within(ant))
this.ctx.fillText(text,400,50)
if(this.frame < this.numFrames){
this.frame = this.frame + 1
window.requestAnimationFrame(this.loop.bind(this))
}else if(this.tick < this.numTicks){
this.frame = 0
this.tick = this.tick + 1
window.requestAnimationFrame(this.loop.bind(this))
}else if(this.generation < this.numGen){
this.frame = 0
this.tick = 0
this.generation = this.generation + 1
this.colony = this.genColony()
window.requestAnimationFrame(this.loop.bind(this))
}
}
makeAnt(){
const dna = []
for(let i = 0; i <= this.numTicks; i = i + 1){
const direction = d3.randomUniform(0,2*Math.PI)()
dna.push(direction)
}
const props = {
ctx:this.ctx,
x:this.width/2,
y:this.height/2,
dna:dna
}
return new Ant(props)
}
makeColony(){
const colony = []
for(let i = 0; i < this.numAnts; i = i + 1){
const ant = this.makeAnt()
colony.push(ant)
}
return colony
}
drawBasket(){
this.ctx.save()
this.ctx.fillStyle = "steelblue"
this.ctx.beginPath()
this.ctx.arc(this.basket[0],this.basket[1],this.basket[2],0,2*Math.PI)
this.ctx.fill()
this.ctx.closePath()
this.ctx.restore()
}
distance(ant){
return Math.sqrt((ant.x - this.basket[0])**2 + (ant.y - this.basket[1])**2)
}
within(ant){
if(this.distance(ant) < this.basket[2]){
return true
}else{
return false
}
}
makeWheel(a){
const sum = d3.sum(a)
return a.map(d => {
return {value:d, prob:d/sum}
})
}
spinWheel(wheel){
const probs = wheel.map(slice => slice.prob)
const cprobs = d3.cumsum(probs)
const r = d3.randomUniform(0,1)()
return [...cprobs,r].sort((a,b) => a - b).indexOf(r)
}
crossover(x,y){
const n = x.length
const xPart = x.slice(0,n/2)
const yPart = y.slice(n/2)
return [...xPart,...yPart]
}
mutate(dna){
const r = d3.randomUniform(0,1)()
if(r < this.mutRate){
const i = d3.randomInt(0,dna.length)()
dna[i] = dna[i] + d3.randomNormal(0,this.mutSize)()
}
return dna
}
reproduce(ant1,ant2){
let dna = this.crossover(ant1.dna,ant2.dna)
dna = this.mutate(dna)
const props = {
ctx:this.ctx,
x:this.width/2,
y:this.height/2,
dna:dna
}
return new Ant(props)
}
pickParents(){
let fitness = this.colony.map(ant => 1/this.distance(ant))
let wheel = this.makeWheel(fitness)
const p1 = this.spinWheel(wheel)
const colony2 = [...this.colony.slice(0,p1), ...this.colony.slice(p1+1)]
fitness = colony2.map(ant => 1/this.distance(ant))
wheel = this.makeWheel(fitness)
const p2 = this.spinWheel(wheel)
return [this.colony[p1],colony2[p2]]
}
genColony(){
const newColony = []
for(let i = 0; i < this.numAnts; i = i + 1){
const parents = this.pickParents()
const child = this.reproduce(parents[0],parents[1])
newColony.push(child)
}
return newColony
}
}