diff --git a/index.html b/index.html index 5104d71..054ddb8 100644 --- a/index.html +++ b/index.html @@ -43,7 +43,7 @@ - + diff --git a/js/agent/agent.js b/js/agent/agent.js index 1f88c78..f676b7d 100644 --- a/js/agent/agent.js +++ b/js/agent/agent.js @@ -1,32 +1,37 @@ const Agent = function(dna, position, direction) { this.dna = dna; this.position = position; + this.positionPrevious = this.position.copy(); this.direction = direction; + this.directionPrevious = this.direction.copy(); this.velocity = new Vector(); - this.body = new Body(dna.body, this.position, this.direction); + this.body = new Body(dna.body, this.position, this.positionPrevious, this.direction, this.directionPrevious); this.impulse = new Vector(); this.mass = this.body.getMass(); this.eaten = 0; }; -Agent.FRICTION = 1; -Agent.TORQUE = .5; -Agent.IMPULSE = 150; +Agent.FRICTION = .88; +Agent.TORQUE = .65; +Agent.IMPULSE = 20; -Agent.prototype.update = function(timeStep) { - this.velocity.divide(1 + Agent.FRICTION * timeStep); +Agent.prototype.update = function() { + this.positionPrevious.set(this.position); + this.directionPrevious.set(this.direction); - this.position.x += this.velocity.x * timeStep; - this.position.y += this.velocity.y * timeStep; + this.velocity.multiply(Agent.FRICTION); + + this.position.x += this.velocity.x; + this.position.y += this.velocity.y; this.impulse.zero(); - this.body.update(timeStep, this.impulse); + this.body.update(this.impulse); this.velocity.add(this.impulse.copy().multiply(Agent.IMPULSE / this.mass)); - this.direction.add(this.impulse.copy().multiply(Agent.TORQUE / this.mass)); + this.direction.subtract(this.impulse.copy().multiply(Agent.TORQUE / this.mass)); this.direction.normalize(); }; -Agent.prototype.draw = function(context) { - this.body.draw(context); +Agent.prototype.draw = function(context, f) { + this.body.draw(context, f); }; \ No newline at end of file diff --git a/js/agent/body.js b/js/agent/body.js index 191cfbf..586b645 100644 --- a/js/agent/body.js +++ b/js/agent/body.js @@ -1,6 +1,8 @@ -const Body = function(dna, position, direction) { +const Body = function(dna, position, positionPrevious, direction, directionPrevious) { this.position = position; + this.positionPrevious = positionPrevious; this.direction = direction; + this.directionPrevious = directionPrevious; this.brain = new Brain(dna.brain); this.mouth = new Mouth(dna.mouth); this.eyes = new Eyes(dna.eyes); @@ -10,31 +12,36 @@ const Body = function(dna, position, direction) { Body.MASS_BASE = 50; -Body.prototype.update = function(timeStep, impulse) { - this.brain.update(timeStep); - this.tentacles.update(timeStep, impulse, this.brain.outputs); - this.mouth.update(timeStep); - this.eyes.update(timeStep); +Body.prototype.update = function(impulse) { + this.brain.update(); + this.tentacles.update(impulse, this.brain.outputs); + this.mouth.update(); + this.eyes.update(); }; -Body.prototype.draw = function(context) { - this.tentacles.draw(context); +Body.prototype.draw = function(context, f) { + const x = this.positionPrevious.x + (this.position.x - this.positionPrevious.x) * f; + const y = this.positionPrevious.y + (this.position.y - this.positionPrevious.y) * f; + const dx = this.directionPrevious.x + (this.direction.x - this.directionPrevious.x) * f; + const dy = this.directionPrevious.y + (this.direction.y - this.directionPrevious.y) * f; + + this.tentacles.draw(context, f); this.mouth.draw(context); this.eyes.draw(context); context.strokeStyle = "red"; context.beginPath(); context.moveTo( - this.position.x - this.direction.x * this.radius, - this.position.y - this.direction.y * this.radius); + x - dx * this.radius, + y - dy * this.radius); context.lineTo( - this.position.x + this.direction.x * this.radius, - this.position.y + this.direction.y * this.radius); + x + dx * this.radius, + y + dy * this.radius); context.stroke(); context.strokeStyle = "white"; context.beginPath(); - context.arc(this.position.x, this.position.y, this.radius, 0, Math.PI * 2); + context.arc(x, y, this.radius, 0, Math.PI * 2); context.stroke(); }; diff --git a/js/agent/brain/axon.js b/js/agent/brain/axon.js index 7d00cac..9530a04 100644 --- a/js/agent/brain/axon.js +++ b/js/agent/brain/axon.js @@ -4,6 +4,6 @@ const Axon = function(dna, from, to) { this.to = to; }; -Axon.prototype.update = function(timeStep) { - this.to.activation += this.from.output * this.weight * timeStep; +Axon.prototype.update = function() { + this.to.activation += this.from.output * this.weight; }; \ No newline at end of file diff --git a/js/agent/brain/brain.js b/js/agent/brain/brain.js index 451be4a..4582c49 100644 --- a/js/agent/brain/brain.js +++ b/js/agent/brain/brain.js @@ -31,13 +31,13 @@ Brain.prototype.getNeuron = function(axonIndex) { } }; -Brain.prototype.update = function(timeStep) { +Brain.prototype.update = function() { for (const neuron of this.neurons) - neuron.update(timeStep); + neuron.update(); for (const output of this.outputs) - output.update(timeStep); + output.update(); for (const axon of this.axons) - axon.update(timeStep); + axon.update(); }; \ No newline at end of file diff --git a/js/agent/brain/neuron.js b/js/agent/brain/neuron.js index 8d73907..93fceb6 100644 --- a/js/agent/brain/neuron.js +++ b/js/agent/brain/neuron.js @@ -1,10 +1,15 @@ const Neuron = function(dna) { this.decay = dna.decay; this.activation = .5; + this.activationPrevious = this.activation; this.output = 0; + this.outputPrevious = this.output; }; -Neuron.prototype.update = function(timeStep) { +Neuron.prototype.update = function() { + this.activationPrevious = this.activation; + this.outputPrevious = this.output; + const d = .2; let a = this.activation; @@ -16,19 +21,7 @@ Neuron.prototype.update = function(timeStep) { a = 0; this.output = 1 / (1 + Math.exp(-a)); // Logistic function - // this.output = Math.tanh(a * .1); // Tanh - - // Linear decay - // if (this.activation > 0) { - // if ((this.activation -= this.decay * timeStep) < 0) - // this.activation = 0; - // } - // else { - // if ((this.activation += this.decay * timeStep) > 0) - // this.activation = 0; - // } // Quadratic decay - this.activation /= 1 + this.decay * timeStep; - // this.activation -= this.activation * Math.min(this.decay * timeStep, 1); + this.activation *= this.decay; }; \ No newline at end of file diff --git a/js/agent/dna/dnaAxon.js b/js/agent/dna/dnaAxon.js index c06aee9..0b4554a 100644 --- a/js/agent/dna/dnaAxon.js +++ b/js/agent/dna/dnaAxon.js @@ -1,7 +1,7 @@ const DNAAxon = function( from, to, - weight = (-.5 + Math.random()) * 50) { + weight = (-.5 + Math.random()) * 2) { this.from = from; this.to = to; this.weight = weight; diff --git a/js/agent/dna/dnaNeuron.js b/js/agent/dna/dnaNeuron.js index 7aaf71b..bb354b8 100644 --- a/js/agent/dna/dnaNeuron.js +++ b/js/agent/dna/dnaNeuron.js @@ -3,7 +3,7 @@ const DNANeuron = function( this.decay = decay; }; -DNANeuron.DEFAULT_DECAY = 1; +DNANeuron.DEFAULT_DECAY = .95; DNANeuron.prototype.copy = function() { return new DNANeuron( diff --git a/js/agent/dna/dnaTentacle.js b/js/agent/dna/dnaTentacle.js index 6c84819..c44fd4a 100644 --- a/js/agent/dna/dnaTentacle.js +++ b/js/agent/dna/dnaTentacle.js @@ -11,7 +11,7 @@ const DNATentacle = function( DNATentacle.DEFAULT_ANGLE = 0; DNATentacle.DEFAULT_LENGTH = 8; -DNATentacle.DEFAULT_SPRING = 10; +DNATentacle.DEFAULT_SPRING = .5; DNATentacle.DEFAULT_SPRING_POWER = 3; DNATentacle.prototype.copy = function() { diff --git a/js/agent/eyes.js b/js/agent/eyes.js index 549956a..ec014b3 100644 --- a/js/agent/eyes.js +++ b/js/agent/eyes.js @@ -2,7 +2,7 @@ const Eyes = function(dna) { }; -Eyes.prototype.update = function(timeStep) { +Eyes.prototype.update = function() { }; diff --git a/js/agent/mouth.js b/js/agent/mouth.js index 3587fdb..f3a6dbf 100644 --- a/js/agent/mouth.js +++ b/js/agent/mouth.js @@ -2,7 +2,7 @@ const Mouth = function(dna) { }; -Mouth.prototype.update = function(timeStep) { +Mouth.prototype.update = function() { }; diff --git a/js/agent/tentacle/segment.js b/js/agent/tentacle/segment.js index 5d7b05a..eda6d3e 100644 --- a/js/agent/tentacle/segment.js +++ b/js/agent/tentacle/segment.js @@ -1,5 +1,6 @@ const Segment = function(position, spring = 0, spacing = 0, parent = null) { this.position = position.copy(); + this.positionPrevious = this.position.copy(); this.spring = spring; this.spacing = spacing; this.parent = parent; @@ -9,8 +10,12 @@ const Segment = function(position, spring = 0, spacing = 0, parent = null) { this.direction = this.parent.position.copy().subtract(this.position).normalize(); else this.direction = new Vector(); + + this.directionPrevious = this.direction.copy(); }; +Segment.PUSH_POWER = 3; + Segment.prototype.getHead = function() { if (this.parent) return this.parent.getHead(); @@ -25,27 +30,27 @@ Segment.prototype.getLength = function() { return 0; }; -Segment.prototype.update = function(timeStep, velocity) { +Segment.prototype.update = function(velocity) { if (!this.parent) return; - const xp = this.position.x; - const yp = this.position.y; + this.positionPrevious.set(this.position); + this.directionPrevious.set(this.direction); - this.parent.update(timeStep, velocity); + this.parent.update(velocity); this.direction.set(this.parent.position).subtract(this.position).normalize(); this.delta.set(this.direction).negate(); const lateralDot = (this.direction.x * this.parent.direction.y - this.direction.y * this.parent.direction.x); if (this.direction.dot(this.parent.direction) < 0) { - const force = this.spring * Math.sign(lateralDot) * timeStep; + const force = this.spring * Math.sign(lateralDot); this.delta.x += this.direction.y * force; this.delta.y -= this.direction.x * force; } else { - const force = this.spring * lateralDot * timeStep; + const force = this.spring * lateralDot; this.delta.x += this.direction.y * force; this.delta.y -= this.direction.x * force; @@ -58,35 +63,42 @@ Segment.prototype.update = function(timeStep, velocity) { else this.position.set(this.parent.position).add(this.delta.multiply(this.spacing / this.delta.length())); - const dx = this.position.x - xp; - const dy = this.position.y - yp; - const push = (this.direction.y * dx - this.direction.x * dy) * lateralDot * lateralDot; + const dx = this.position.x - this.positionPrevious.x; + const dy = this.position.y - this.positionPrevious.y; + const push = (this.direction.y * dx - this.direction.x * dy) * Math.pow(Math.abs(lateralDot), Segment.PUSH_POWER); velocity.x -= this.direction.y * push; velocity.y += this.direction.x * push; }; -Segment.prototype.draw = function(context) { +Segment.prototype.draw = function(context, f) { if (!this.parent) return; - this.parent.draw(context); + this.parent.draw(context, f); + + const x = this.positionPrevious.x + (this.position.x - this.positionPrevious.x) * f; + const y = this.positionPrevious.y + (this.position.y - this.positionPrevious.y) * f; + const px = this.parent.positionPrevious.x + (this.parent.position.x - this.parent.positionPrevious.x) * f; + const py = this.parent.positionPrevious.y + (this.parent.position.y - this.parent.positionPrevious.y) * f; + const dx = this.directionPrevious.x + (this.direction.x - this.directionPrevious.x) * f; + const dy = this.directionPrevious.y + (this.direction.y - this.directionPrevious.y) * f; context.strokeStyle = "white"; context.beginPath(); - context.moveTo(this.position.x, this.position.y); - context.lineTo(this.parent.position.x, this.parent.position.y); + context.moveTo(x, y); + context.lineTo(px, py); context.stroke(); context.beginPath(); - context.arc(this.position.x, this.position.y, 3, 0, Math.PI * 2); + context.arc(x, y, 3, 0, Math.PI * 2); context.stroke(); context.strokeStyle = "red"; context.beginPath(); - context.moveTo(this.position.x, this.position.y); - context.lineTo(this.position.x - this.direction.x * 40, this.position.y - this.direction.y * 40); + context.moveTo(x, y); + context.lineTo(x - dx * 40, y - dy * 40); context.stroke(); }; \ No newline at end of file diff --git a/js/agent/tentacle/segmentHead.js b/js/agent/tentacle/segmentHead.js index 15a9987..bbeb81e 100644 --- a/js/agent/tentacle/segmentHead.js +++ b/js/agent/tentacle/segmentHead.js @@ -5,6 +5,9 @@ const SegmentHead = function(position) { SegmentHead.prototype = Object.create(Segment.prototype); SegmentHead.prototype.setAnchor = function(position, angle) { + this.positionPrevious.set(this.position); + this.directionPrevious.set(this.direction); + this.position.set(position); this.direction.fromAngle(angle); }; \ No newline at end of file diff --git a/js/agent/tentacle/tentacle.js b/js/agent/tentacle/tentacle.js index 0eb56d2..7b3b522 100644 --- a/js/agent/tentacle/tentacle.js +++ b/js/agent/tentacle/tentacle.js @@ -13,12 +13,12 @@ const Tentacle = function(dna, position, direction, radius) { Tentacle.SPACING = 18; -Tentacle.prototype.update = function(timeStep, velocity) { - this.tail.update(timeStep, velocity); +Tentacle.prototype.update = function(velocity) { + this.tail.update(velocity); }; -Tentacle.prototype.draw = function(context) { - this.tail.draw(context); +Tentacle.prototype.draw = function(context, f) { + this.tail.draw(context, f); }; Tentacle.prototype.getLength = function() { @@ -36,7 +36,7 @@ Tentacle.prototype.build = function() { let tail = this.head; for (let i = 0; i < this.length; ++i) { - const spring = this.spring * Math.pow(1 - (i / (this.length - 1)) * .35, this.springPower); + const spring = this.spring * Math.pow(1 - (i / (this.length - 1)) * 0.35, this.springPower); tail = new Segment( tail.position.copy().add(this.delta.copy().normalize().multiply(Tentacle.SPACING)), diff --git a/js/agent/tentacles.js b/js/agent/tentacle/tentacles.js similarity index 78% rename from js/agent/tentacles.js rename to js/agent/tentacle/tentacles.js index 7d0e942..3139d09 100644 --- a/js/agent/tentacles.js +++ b/js/agent/tentacle/tentacles.js @@ -19,14 +19,14 @@ Tentacles.prototype.getMass = function() { return mass; }; -Tentacles.prototype.update = function(timeStep, impulse, outputs) { +Tentacles.prototype.update = function(impulse, outputs) { for (let tentacle = 0; tentacle < this.tentacles.length; ++tentacle) { this.tentacles[tentacle].setAnchor(this.position, 2 * outputs[tentacle].output - 1); - this.tentacles[tentacle].update(timeStep, impulse); + this.tentacles[tentacle].update(impulse); } }; -Tentacles.prototype.draw = function(context) { +Tentacles.prototype.draw = function(context, f) { for (const tentacle of this.tentacles) - tentacle.draw(context); + tentacle.draw(context, f); }; \ No newline at end of file diff --git a/js/environment.js b/js/environment.js index 5899273..f1b13f7 100644 --- a/js/environment.js +++ b/js/environment.js @@ -22,30 +22,45 @@ const Environment = function( this.warp = false; this.paused = false; this.selected = null; + this.timeToFrame = 0; this.initialize(this.agentCount); }; +Environment.FRAME_TIME = .065; Environment.SPAWN_INSET = 400; Environment.DEFAULT_AGENT_COUNT = 14; -Environment.DEFAULT_SIM_TIME = 15; -Environment.MAX_ITERATION_TIME = 1 / 60; -Environment.WARP_STEP = .1; +Environment.DEFAULT_SIM_TIME = 20; +Environment.MAX_FRAME_TIME = 1 / 60; +Environment.WARP_STEP = Environment.FRAME_TIME * 10; Environment.SELECT_RADIUS_MULTIPLIER = 3; -Environment.prototype.simulate = function(timeStep) { - this.food.update(timeStep, this.agents); +Environment.prototype.getFrameProgression = function() { + return this.timeToFrame / Environment.FRAME_TIME; +}; +Environment.prototype.step = function() { for (const agent of this.agents) - agent.update(timeStep); + agent.update(); + + this.food.update(this.agents); + + if (this.agents.length === 0 || this.time > this.simTime) + this.nextGeneration(); +}; +Environment.prototype.simulate = function(timeStep) { + this.timeToFrame += timeStep; this.time += timeStep; + while (this.timeToFrame > Environment.FRAME_TIME) { + this.timeToFrame -= Environment.FRAME_TIME; + + this.step(); + } + if (this.onUpdate) this.onUpdate(this); - - if (this.agents.length === 0 || this.time > this.simTime) - this.nextGeneration(); }; Environment.prototype.update = function(timeStep) { @@ -55,7 +70,7 @@ Environment.prototype.update = function(timeStep) { if (this.warp) { const startTime = new Date(); - while ((new Date() - startTime) * .001 < Environment.MAX_ITERATION_TIME) + while ((new Date() - startTime) * .001 < Environment.MAX_FRAME_TIME) this.simulate(Environment.WARP_STEP); } else @@ -63,6 +78,7 @@ Environment.prototype.update = function(timeStep) { }; Environment.prototype.draw = function(context) { + const frameProgression = this.getFrameProgression(); const gradient = context.createRadialGradient(0, 0, this.radius * .1, 0, 0, this.radius); gradient.addColorStop(0, "#4b4b4b"); @@ -78,14 +94,17 @@ Environment.prototype.draw = function(context) { this.food.draw(context); for (const agent of this.agents) - agent.draw(context); + agent.draw(context, frameProgression); if (this.selected) { + const x = this.selected.positionPrevious.x + (this.selected.position.x - this.selected.positionPrevious.x) * frameProgression; + const y = this.selected.positionPrevious.y + (this.selected.position.y - this.selected.positionPrevious.y) * frameProgression; + context.strokeStyle = "yellow"; context.beginPath(); context.arc( - this.selected.position.x, - this.selected.position.y, + x, + y, this.selected.body.radius * Environment.SELECT_RADIUS_MULTIPLIER, 0, Math.PI + Math.PI); @@ -102,9 +121,9 @@ Environment.prototype.click = function(x, y) { for (const agent of this.agents) { const dx = x - agent.position.x; const dy = y - agent.position.y; - const squaredRadius = (agent.body.radius * Environment.SELECT_RADIUS_MULTIPLIER) ** 2; + const radius = agent.body.radius * Environment.SELECT_RADIUS_MULTIPLIER; - if (dx * dx + dy * dy < squaredRadius) { + if (dx * dx + dy * dy < radius * radius) { this.selected = agent; if (this.onSelect) diff --git a/js/food.js b/js/food.js index fb8db9f..3327590 100644 --- a/js/food.js +++ b/js/food.js @@ -28,7 +28,7 @@ Food.GRID_SPACING_INVERSE = 1 / Food.GRID_SPACING; Food.COLOR = "gray"; Food.UNITS_PER_PIXEL = .0007; -Food.prototype.update = function(timeStep, agents) { +Food.prototype.update = function(agents) { for (let agent = 0; agent < agents.length; ++agent) { const distance = agents[agent].body.radius + Food.RADIUS; const xStart = Math.floor((agents[agent].position.x + this.environmentRadius - distance) * Food.GRID_SPACING_INVERSE); diff --git a/js/gui/agent/brainPlot.js b/js/gui/agent/brainPlot.js index 4370ff8..a65d9bb 100644 --- a/js/gui/agent/brainPlot.js +++ b/js/gui/agent/brainPlot.js @@ -16,7 +16,7 @@ BrainPlot.COLOR_NEURON = "orange"; BrainPlot.COLOR_NEURON_OUTPUT = "aqua"; BrainPlot.COLOR_EDGE = "white"; -BrainPlot.prototype.update = function() { +BrainPlot.prototype.update = function(environment) { const context = this.element.getContext("2d"); context.clearRect(0, 0, canvas.width, canvas.height); @@ -25,7 +25,7 @@ BrainPlot.prototype.update = function() { .5 * (this.element.width - this.cellRadius * 2 * this.columns), .5 * (this.element.height - this.cellRadius * 2 * this.rows)); - this.draw(context); + this.draw(context, environment.getFrameProgression()); context.restore(); }; @@ -45,13 +45,17 @@ BrainPlot.prototype.drawNeuron = function(context, x, y, activation, color) { context.stroke(); }; -BrainPlot.prototype.draw = function(context) { +BrainPlot.prototype.getOutput = function(neuron, f) { + return neuron.outputPrevious + (neuron.output - neuron.outputPrevious) * f; +}; + +BrainPlot.prototype.draw = function(context, f) { for (let input = 0; input < this.brain.inputs.length; ++input) this.drawNeuron( context, this.cellRadius * (1 + 2 * input), this.cellRadius, - this.brain.inputs[input].output, + this.getOutput(this.brain.inputs[input], f), BrainPlot.COLOR_NEURON_INPUT); let row = 1; @@ -62,7 +66,7 @@ BrainPlot.prototype.draw = function(context) { context, this.cellRadius * (1 + 2 * column), this.cellRadius * (1 + 2 * row), - this.brain.neurons[neuron].output, + this.getOutput(this.brain.neurons[neuron], f), BrainPlot.COLOR_NEURON); if (++column === this.columns) { @@ -79,7 +83,7 @@ BrainPlot.prototype.draw = function(context) { context, this.cellRadius * (1 + 2 * output), this.cellRadius * (1 + 2 * row), - this.brain.outputs[output].output, + this.getOutput(this.brain.outputs[output], f), BrainPlot.COLOR_NEURON_OUTPUT); }; diff --git a/js/gui/agent/brainView.js b/js/gui/agent/brainView.js index 1b57110..fbe409e 100644 --- a/js/gui/agent/brainView.js +++ b/js/gui/agent/brainView.js @@ -11,7 +11,7 @@ BrainView.prototype.onUpdate = function(environment) { if (!environment.selected) return; - this.plot.update(); + this.plot.update(environment); }; BrainView.prototype.onSelect = function(environment) { @@ -19,5 +19,5 @@ BrainView.prototype.onSelect = function(environment) { return; this.plot.select(environment.selected.body.brain); - this.plot.update(); + this.plot.update(environment); }; \ No newline at end of file diff --git a/js/mutator/mutatorAxon.js b/js/mutator/mutatorAxon.js index 1fbcdbb..0ec5f23 100644 --- a/js/mutator/mutatorAxon.js +++ b/js/mutator/mutatorAxon.js @@ -1,12 +1,12 @@ Mutator.AXON_MODIFY_CHANCE = .65; -Mutator.AXON_MODIFY_AMPLITUDE = 20; +Mutator.AXON_MODIFY_AMPLITUDE = .6; Mutator.prototype.mutateAxon = function(dna) { if (Math.random() > Mutator.AXON_MODIFY_CHANCE) return; - const weightMax = 100; - const weightMin = -100; + const weightMax = 40; + const weightMin = -40; dna.weight += (-1 + 2 * Math.random()) * Mutator.AXON_MODIFY_AMPLITUDE; diff --git a/js/vector.js b/js/vector.js index 024e3aa..11c9f26 100644 --- a/js/vector.js +++ b/js/vector.js @@ -28,10 +28,6 @@ Vector.prototype.multiply = function(scalar) { return this; }; -Vector.prototype.divide = function(scalar) { - return this.multiply(1 / scalar); -}; - Vector.prototype.subtract = function(vector) { this.x -= vector.x; this.y -= vector.y;