├── img
├── bg.jpg
├── bg1.jpg
└── map.jpg
├── poster.jpg
├── README.md
├── model
├── Obstacle.js
└── Organism.js
├── index.html
└── sketch.js
/img/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ljmocic/evolutionary-game-of-life/HEAD/img/bg.jpg
--------------------------------------------------------------------------------
/img/bg1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ljmocic/evolutionary-game-of-life/HEAD/img/bg1.jpg
--------------------------------------------------------------------------------
/img/map.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ljmocic/evolutionary-game-of-life/HEAD/img/map.jpg
--------------------------------------------------------------------------------
/poster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ljmocic/evolutionary-game-of-life/HEAD/poster.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # evolutionary-game-of-life
2 |
3 | ## Live preview
4 | * [demo](https://goo.gl/Uir7yJ)
5 | * [video](https://goo.gl/YB6Q3e)
6 |
7 | ## Poster
8 | 
9 |
10 |
--------------------------------------------------------------------------------
/model/Obstacle.js:
--------------------------------------------------------------------------------
1 | function Obstacle(position, width, height) {
2 |
3 | this.position = position;
4 | this.width = width;
5 | this.height = height;
6 |
7 | this.draw = function () {
8 | stroke(0);
9 | fill(183);
10 | strokeWeight(1);
11 | rectMode(CORNER);
12 | rect(this.position.x, this.position.y, this.width - 10, this.height - 10);
13 | }
14 |
15 | this.contains = function (spot, radius) {
16 | if (spot.x > this.position.x && spot.x < this.position.x + width
17 | && spot.y > this.position.y && spot.y < this.position.y + height) {
18 | return true;
19 | } else {
20 | return false;
21 | }
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | | Population size: |
21 | |
22 | |
23 |
24 |
25 |
26 | | Random food generation: |
27 | |
28 | |
29 |
30 |
31 |
32 | | Elitism: |
33 | |
34 | |
35 |
36 |
37 |
38 | | Mutation rate: |
39 | |
40 | |
41 |
42 |
43 |
44 | | Death enabled |
45 | |
46 | |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/model/Organism.js:
--------------------------------------------------------------------------------
1 | function Organism() {
2 |
3 | this.fitness = 0;
4 |
5 | // position
6 | this.position = createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10);
7 | this.velocity = createVector(random(this.maxSpeed), random(this.maxSpeed));
8 | this.acceleration = createVector(0, 0);
9 | this.desiredPosition = createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10);
10 | this.maxForce = random(1);
11 | this.maxSpeed = random(10);
12 |
13 | // health
14 | this.health = 100;
15 |
16 | // body
17 | this.radius = 10;
18 | this.sight = random(5, 35);
19 |
20 | // color
21 | this.colorR = random(255);
22 | this.colorG = random(255);
23 | this.colorB = random(255);
24 |
25 | this.calculateFitness = function () {
26 | var score = 0;
27 |
28 | score += this.health;
29 |
30 | //score += Math.abs(Math.pow(this.velocity.x, 2) + Math.pow(this.velocity.y, 2));
31 | score += this.maxForce;
32 | score += this.maxSpeed;
33 | score += this.radius;
34 | score += this.sight;
35 |
36 | if (score > maxFitness) {
37 | maxFitness = score;
38 | }
39 |
40 | this.fitness = score;
41 | };
42 |
43 | this.randomMutation = function () {
44 |
45 | var randomNumber = Math.floor(random(6));
46 |
47 | if (randomNumber < 1) {
48 | if (random(1) < 0.5) {
49 | this.sight = this.sight * (random(1) + 1);
50 | }
51 | else {
52 | this.sight = this.sight * random(1);
53 | }
54 | }
55 | else if (randomNumber < 2) {
56 | if (random(1) < 0.5) {
57 | this.radius = this.radius * (random(1) + 1);
58 | }
59 | else {
60 | this.radius = this.radius * random(1);
61 | }
62 | }
63 | else if (randomNumber < 4) {
64 | if (random(1) < 0.5) {
65 | this.maxForce = this.maxForce * (random(1) + 1);
66 | }
67 | else {
68 | this.maxForce = this.maxForce * random(1);
69 | }
70 | }
71 | else if (randomNumber < 5) {
72 | if (random(1) < 0.5) {
73 | this.maxSpeed = this.maxSpeed * (random(1) + 1);
74 | }
75 | else {
76 | this.maxSpeed = this.maxSpeed * random(1);
77 | }
78 | }
79 |
80 | };
81 |
82 | this.eat = function () {
83 |
84 | // eat food
85 | var minDistance = Infinity;
86 | var minIndex = -1;
87 |
88 | for (var i = 0; i < food.length; i++) {
89 |
90 | var distance = int(dist(this.position.x, this.position.y, food[i].x, food[i].y));
91 |
92 | if (distance < minDistance) {
93 | minDistance = distance;
94 | minIndex = i;
95 | }
96 |
97 | }
98 |
99 | if (minDistance < this.radius) {
100 | food.splice(minIndex, 1);
101 | this.health += 50;
102 | }
103 |
104 | // drink water
105 | minDistance = Infinity;
106 | minIndex = -1;
107 |
108 | for (var i = 0; i < water.length; i++) {
109 |
110 | var distance = int(dist(this.position.x, this.position.y, water[i].x, water[i].y));
111 |
112 | if (distance < minDistance) {
113 | minDistance = distance;
114 | minIndex = i;
115 | }
116 |
117 | }
118 |
119 | if (minDistance < this.radius) {
120 | water.splice(minIndex, 1);
121 | this.health += 10;
122 | }
123 |
124 | // poison
125 | minDistance = Infinity;
126 | minIndex = -1;
127 |
128 | for (var i = 0; i < poison.length; i++) {
129 |
130 | var distance = int(dist(this.position.x, this.position.y, poison[i].x, poison[i].y));
131 |
132 | if (distance < minDistance) {
133 | minDistance = distance;
134 | minIndex = i;
135 | }
136 |
137 | }
138 |
139 | if (minDistance < this.radius) {
140 | poison.splice(minIndex, 1);
141 | this.health -= 30;
142 | }
143 |
144 | };
145 |
146 | this.seek = function () {
147 |
148 | var minDistance = Infinity;
149 | var minIndex = -1;
150 |
151 | var goal;
152 | var desire;
153 | var target;
154 |
155 | // if it sees food, it should follow it
156 | for (var i = 0; i < food.length; i++) {
157 |
158 | var distance = int(dist(this.position.x, this.position.y, food[i].x, food[i].y));
159 |
160 | if (distance < minDistance) {
161 | minDistance = distance;
162 | minIndex = i;
163 | }
164 |
165 | }
166 |
167 | if (minDistance < this.sight) {
168 | goal = food[minIndex];
169 |
170 | // want to eat food so bad
171 | desire = 1;
172 |
173 | target = p5.Vector.sub(goal, this.position);
174 |
175 | // normalize then multiply by maximum speed
176 | target.setMag(this.maxSpeed * desire);
177 |
178 | // make it closer to the target
179 | this.desiredPosition = p5.Vector.sub(target, this.velocity);
180 | this.desiredPosition.limit(this.maxForce);
181 |
182 | // move towards target, as fast as you can
183 | this.acceleration.add(this.desiredPosition);
184 | }
185 | // randomize moving
186 | else if (frameCount % floor(random(30)) == 0) {
187 | var randomMovement = 7;
188 | var randX = this.velocity.x + random(randomMovement) - randomMovement / 2;
189 | var randY = this.velocity.y + random(randomMovement) - randomMovement / 2;
190 | this.acceleration = createVector(randX, randY);
191 | }
192 | };
193 |
194 | this.move = function () {
195 |
196 | // avoid obstacle
197 | for (var i = 0; i < obstacles.length; i++) {
198 | if (obstacles[i].contains(this.position, this.radius)) {
199 | this.acceleration.add(createVector(random(20) - 10, random(20) - 10));
200 | }
201 | }
202 |
203 | // update position
204 | this.velocity.add(this.acceleration); // add force if there is any
205 | this.acceleration.mult(0); // reset acceleration
206 | this.velocity.limit(this.maxSpeed);
207 | this.position.add(this.velocity);
208 |
209 | // let wall be a portal to the other side
210 | if (this.position.x < 5) {
211 | this.position.x = width - 5;
212 | }
213 | if (width - this.position.x < 5) {
214 | this.position.x = 5;
215 | }
216 | if (this.position.y < 5) {
217 | this.position.y = height - 5;
218 | }
219 | if (height - this.position.y < 5) {
220 | this.position.y = 5;
221 | }
222 |
223 | // limit boundaries classic way
224 | // this.position.x = constrain(this.position.x, 0, width - 1);
225 | // this.position.y = constrain(this.position.y, 0, height - 1);
226 | };
227 |
228 | this.render = function () {
229 |
230 | var theta = this.velocity.heading() + PI / 2;
231 | push();
232 | // act like it is in the left upper corner
233 | translate(this.position.x, this.position.y);
234 | rotate(theta);
235 |
236 | // draw an organism
237 | fill(this.colorR, this.colorG, this.colorB);
238 | stroke(this.colorG);
239 | beginShape(this.colorB);
240 | ellipse(0, 0, this.radius);
241 |
242 | // draw sensor
243 | fill(255, 255, 255, 30);
244 | stroke(0, 255, 0);
245 | arc(0, 0, this.sight, this.sight, PI + PI / 4, PI + 3 * PI / 4);
246 | endShape(CLOSE);
247 | pop();
248 |
249 | };
250 |
251 | this.update = function () {
252 | this.calculateFitness();
253 | this.eat();
254 | this.seek();
255 | this.move();
256 | this.render();
257 | this.health -= 1;
258 | if(this.health > 100) {
259 | this.health = 100;
260 | }
261 | };
262 | }
263 |
--------------------------------------------------------------------------------
/sketch.js:
--------------------------------------------------------------------------------
1 | var population = new Array();
2 | var food = new Array();
3 | var water = new Array();
4 | var poison = new Array();
5 | var obstacles = new Array();
6 |
7 | var elitism = 5;
8 |
9 | var generation = 0;
10 | var populationSize = 30;
11 | var mutationRate = 0.9;
12 | var numberOfFood = 200;
13 | var numberOfWater = 50;
14 | var numberOfPoison = 30;
15 | var numberOfObstacles = 30;
16 | var maxFitness = 0;
17 |
18 | var randomFoodGeneration = 15;
19 | var randomWaterGeneration = 3;
20 | var randomPoisonGeneration = 3;
21 |
22 | var deathEnabled = false;
23 |
24 | var frameWidth = 883;
25 | var frameHeight = 550;
26 |
27 | // start server
28 | // command: python -m http.server
29 | // adress: http://localhost:8000/
30 | /*
31 | var bg;
32 |
33 | function preload() {
34 | bg = loadImage("model/bg1.jpg");
35 | }
36 | */
37 |
38 | function setup() {
39 | createCanvas(frameWidth, frameHeight);
40 | frameRate(60);
41 |
42 | initElements();
43 | }
44 |
45 | function draw() {
46 |
47 | // every 5 seconds generating new population
48 | if (frameCount % 150 == 0) {
49 | runGeneticAlgorithm();
50 | }
51 |
52 | // remove all elements from last frame
53 | clear();
54 |
55 | // in case of server running, enable background
56 | //background(bg);
57 | background(50);
58 |
59 | removeDead();
60 |
61 | generateElements();
62 |
63 | drawElements();
64 |
65 | refreshParameters();
66 | }
67 |
68 | function initElements() {
69 | for (var i = 0; i < populationSize; i++) {
70 | population.push(new Organism());
71 | }
72 |
73 | for (var i = 0; i < numberOfFood; i++) {
74 | food.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
75 | }
76 |
77 | for (var i = 0; i < numberOfWater; i++) {
78 | water.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
79 | }
80 |
81 | for (var i = 0; i < numberOfPoison; i++) {
82 | poison.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
83 | }
84 |
85 | /*
86 | for (var i = 0; i < numberOfObstacles; i++) {
87 | obstacles.push(new Obstacle(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10), random(30) + 10, random(30) + 10));
88 | }
89 | */
90 |
91 | }
92 |
93 | function drawElements() {
94 | // draw food
95 | for (var i = 0; i < food.length; i++) {
96 | fill(0, 255, 0);
97 | noStroke();
98 | ellipse(food[i].x, food[i].y, 5);
99 | }
100 |
101 | // water
102 | for (var i = 0; i < water.length; i++) {
103 | fill(0, 191, 255);
104 | noStroke();
105 | ellipse(water[i].x, water[i].y, 5);
106 | }
107 |
108 | // poison
109 | for (var i = 0; i < poison.length; i++) {
110 | fill(255, 0, 0);
111 | noStroke();
112 | ellipse(poison[i].x, poison[i].y, 5);
113 | }
114 |
115 | // obstacles
116 | for (var i = 0; i < obstacles.length; i++) {
117 | obstacles[i].draw();
118 | }
119 | }
120 |
121 | function generateElements() {
122 | // random food generation
123 | if (random(1) < 0.3) {
124 | for (var i = 0; i < randomFoodGeneration; i++) {
125 | food.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
126 | }
127 | }
128 |
129 | // random water generation
130 | if (random(1) < 0.3) {
131 | for (var i = 0; i < randomFoodGeneration; i++) {
132 | if(random(1) < 0.1) {
133 | water.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
134 | }
135 | }
136 | }
137 |
138 | // random poison generation
139 | if (random(1) < 0.3) {
140 | for (var i = 0; i < randomPoisonGeneration; i++) {
141 | if(random(1) < 0.1) {
142 | poison.push(createVector(random(frameWidth - 20) + 10, random(frameHeight - 20) + 10));
143 | }
144 | }
145 | }
146 | }
147 |
148 | function removeDead() {
149 | for (var i = population.length - 1; i > 0; i--) {
150 | population[i].update();
151 |
152 | // death
153 | if (deathEnabled == true) {
154 | if (population[i].health <= 0) {
155 | population.splice(i, 1);
156 | }
157 | }
158 | }
159 | }
160 |
161 | function runGeneticAlgorithm() {
162 | var bestInPopulation = new Array();
163 |
164 | if(population.length < 10) {
165 | alert("Population too small! Please reset simulation");
166 | return;
167 | }
168 |
169 | // elitism
170 | var tempElitism = 0;
171 | if(elitism > population.length) {
172 | tempElitism = population.length;
173 | }
174 | else {
175 | tempElitism = elitism;
176 | }
177 | for (var i = 0; i < tempElitism; i++) {
178 |
179 | var maxPopulationFitness = 0;
180 |
181 | var bestChromosome = population[0];
182 | var bestChromosomeIndex = 0;
183 |
184 | for (var j = 0; j < population.length; j++) {
185 | population[j].calculateFitness();
186 | if (population[j].fitness > maxPopulationFitness) {
187 | maxPopulationFitness = population[j].fitness;
188 | bestChromosome = population[j];
189 | bestChromosomeIndex = j;
190 | }
191 | }
192 |
193 | population.splice(bestChromosomeIndex, 1);
194 | bestInPopulation.push(bestChromosome);
195 | }
196 |
197 | var newPopulation = new Array();
198 | for (var j = 0; j < tempElitism; j++) {
199 | newPopulation.push(bestInPopulation[j]);
200 | }
201 |
202 | // crossover
203 | while (newPopulation.length < populationSize) {
204 |
205 | var parent1 = population[Math.floor(random(population.length))];
206 | var parent2 = population[Math.floor(random(population.length))];
207 | var child = new Organism();
208 |
209 | if (random(1) < 0.5) {
210 | child.radius = parent1.radius;
211 | }
212 | else {
213 | child.radius = parent2.radius;
214 | }
215 |
216 | if (random(1) < 0.5) {
217 | child.sight = parent1.sight;
218 | }
219 | else {
220 | child.sight = parent2.sight;
221 | }
222 |
223 | if (random(1) < 0.5) {
224 | child.maxForce = parent1.maxForce;
225 | }
226 | else {
227 | child.maxForce = parent2.maxForce;
228 | }
229 |
230 | if (random(1) < 0.5) {
231 | child.maxSpeed = parent1.maxSpeed;
232 | }
233 | else {
234 | child.maxSpeed = parent2.maxSpeed;
235 | }
236 |
237 | // mutation
238 | child.randomMutation();
239 |
240 | if(child.radius > child.sight) {
241 | child.sight = child.radius + 5;
242 | }
243 |
244 | newPopulation.push(child);
245 | }
246 |
247 | population.splice(0, population.length);
248 | population = newPopulation.slice();
249 | generation++;
250 | //alert("new population is in the game");
251 | }
252 |
253 | // html controllers
254 | function resetSimulation() {
255 | population = new Array();
256 | food = new Array();
257 | water = new Array();
258 | poison = new Array();
259 | obstacles = new Array();
260 |
261 | initElements();
262 | }
263 |
264 | function refreshParameters() {
265 |
266 | // update info
267 | document.getElementById("fitness").innerHTML = "Max fitness: " + maxFitness;
268 | document.getElementById("population").innerHTML = "Generation: " + generation;
269 |
270 | randomFoodGeneration = document.getElementById("randomFoodGeneration").value;
271 | document.getElementById("randomFoodGenerationOutput").innerHTML = randomFoodGeneration;
272 |
273 | populationSize = document.getElementById("populationSize").value;
274 | document.getElementById("populationSizeOutput").innerHTML = populationSize;
275 |
276 | elitism = document.getElementById("elitism").value;
277 | document.getElementById("elitismOutput").innerHTML = elitism;
278 |
279 |
280 | mutationRate = document.getElementById("mutationRate").value * 0.01;
281 | document.getElementById("mutationRateOutput").innerHTML = mutationRate.toFixed(2);
282 |
283 | deathEnabled = document.getElementById("deathCheckbox").checked;
284 | }
285 |
--------------------------------------------------------------------------------