Added rotations, health, damage

This commit is contained in:
MaxRobinsonTheGreat
2020-07-16 13:29:17 -06:00
parent 9f13068cda
commit 2fc2ba7b80
12 changed files with 434 additions and 62 deletions

View File

@@ -1,10 +1,18 @@
# EvolutionSimulatorV2 # EvolutionSimulatorV2
Version 2 of the evolution simulator Version 2 of the evolution simulator
Requirements: Requirements to Play:
- browser (preferably chrome)
Requirements to edit and build:
- npm - npm
- webpack - webpack
## Playing
Included in this repo is an already built version of the game. You can play it simply by opening `index.html` in your browser.
## Building
If you want to change the source code use any of the following commands:
To build minified: `npm run build` To build minified: `npm run build`
To build in dev mode: `npm run build-dev` To build in dev mode: `npm run build-dev`
To build in dev/watch mode: `npm run build-watch` To build in dev/watch mode: `npm run build-watch`

256
dist/bundle.js vendored

File diff suppressed because one or more lines are too long

21
dist/css/style.css vendored
View File

@@ -2,6 +2,8 @@ body{
background: black; background: black;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%;
width: 100%;
} }
canvas { canvas {
@@ -15,15 +17,18 @@ canvas {
} }
.env { .env {
height: 80vh; position: absolute;
position: static; top: 0;
bottom: 300px; /* must correspond to control-panel height*/
width: 100%;
} }
.control-panel { .control-panel {
height: 20vh; height: 300px;
width: 100vw; width: 100%;
bottom: 0;
position: absolute;
background-color: gray; background-color: gray;
position: fixed;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);
@@ -35,20 +40,16 @@ canvas {
border: 10px; border: 10px;
border-color: black; border-color: black;
background-color: lightgray; background-color: lightgray;
max-width: 100%;
/* height: 100%; */
} }
#speed-controller { #speed-controller {
grid-column: 1; grid-column: 1;
grid-row: 1; grid-row: 1;
/* background-color: blue; */
} }
#stats { #stats {
grid-column: 1; grid-column: 1;
grid-row: 2; grid-row: 2;
/* background-color: blue; */
} }
#hyperparameters { #hyperparameters {
@@ -60,8 +61,6 @@ canvas {
#abilities { #abilities {
grid-column: 3; grid-column: 3;
grid-row: 1 / 3; grid-row: 1 / 3;
/* background-color: yellow; */
} }
.control-mode-button { .control-mode-button {

13
dist/html/index.html vendored
View File

@@ -4,14 +4,15 @@
<meta charste="UTF-8"> <meta charste="UTF-8">
<title>Evolution Simulator</title> <title>Evolution Simulator</title>
<link rel="stylesheet" href="../css/style.css"> <link rel="stylesheet" href="../css/style.css">
<!-- <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head> </head>
<body> <body>
<script src="../bundle.js"></script> <script src="../bundle.js"></script>
<div class='env'> <div class='env'>
<canvas id='canvas'></canvas> <canvas id='canvas'></canvas>
</div> </div>
<div class='control-panel'> <div class='control-panel'>
<div id='speed-controller' class='control-set'> <div id='speed-controller' class='control-set'>
<h2>Simulation Speed</h2> <h2>Simulation Speed</h2>
@@ -48,6 +49,15 @@
<label for="remove-prob">Remove Cell:</label> <label for="remove-prob">Remove Cell:</label>
<input class="mut-prob" type="number" id="remove-prob" min="0" max="100" value=33> <input class="mut-prob" type="number" id="remove-prob" min="0" max="100" value=33>
<br/> <br/>
<h4>Organism Rotation</h4>
<label for="mover-rot">Movers can rotate</label>
<input type="checkbox" id="mover-rot" name="scales" checked>
<br/>
<label for="offspring-rot">Offspring rotate</label>
<input type="checkbox" id="offspring-rot" name="scales" checked>
<br/>
<label for="insta-kill">Insta Kill</label>
<input type="checkbox" id="insta-kill" name="scales">
</div> </div>
<div id='abilities' class='control-set'> <div id='abilities' class='control-set'>
@@ -62,6 +72,5 @@
<button id='clear-walls'>Clear All Walls</button> <button id='clear-walls'>Clear All Walls</button>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -81,12 +81,12 @@ function killNeighbors(self, env) {
} }
function killNeighbor(self, n_cell) { function killNeighbor(self, n_cell) {
if(n_cell == null || n_cell.owner == null || n_cell.owner == self.owner || !n_cell.owner.living || n_cell.type == CellTypes.armor) if(n_cell == null || n_cell.owner == null || self.owner == null || n_cell.owner == self.owner || !n_cell.owner.living || n_cell.type == CellTypes.armor)
return; return;
var should_die = n_cell.type == CellTypes.killer; // has to be calculated before death var is_hit = n_cell.type == CellTypes.killer; // has to be calculated before death
n_cell.owner.die(); n_cell.owner.harm();
if (should_die){ if (is_hit){
self.owner.die(); self.owner.harm();
} }
} }

View File

@@ -7,7 +7,7 @@ const CellTypes = {
mover: 5, mover: 5,
killer: 6, killer: 6,
armor: 7, armor: 7,
colors: ['#121D29', 'green', 'gray', 'orange', 'white', 'blue', 'red', 'purple'], colors: ['#121D29', 'green', 'gray', 'orange', 'white', '#3493eb', 'red', 'purple'],
getRandomLivingType: function() { getRandomLivingType: function() {
return Math.floor(Math.random() * 5) + 3; return Math.floor(Math.random() * 5) + 3;
} }

View File

@@ -30,7 +30,6 @@ class ControlPanel {
} }
else if (!this.engine.running){ else if (!this.engine.running){
$('#pause-button').text("Pause"); $('#pause-button').text("Pause");
console.log(this.fps)
this.engine.start(this.fps); this.engine.start(this.fps);
} }
}.bind(this)); }.bind(this));
@@ -78,6 +77,16 @@ class ControlPanel {
$('#change-prob').val(Math.floor(Hyperparams.changeProb)); $('#change-prob').val(Math.floor(Hyperparams.changeProb));
$('#remove-prob').val(Math.floor(Hyperparams.removeProb)); $('#remove-prob').val(Math.floor(Hyperparams.removeProb));
}); });
$('#mover-rot').change(function() {
Hyperparams.moversCanRotate = this.checked;
});
$('#offspring-rot').change(function() {
Hyperparams.offspringRotate = this.checked;
});
$('#insta-kill').change(function() {
Hyperparams.instaKill = this.checked;
});
} }
defineModeControls() { defineModeControls() {

15
src/Directions.js Normal file
View File

@@ -0,0 +1,15 @@
const Directions = {
up:0,
down:1,
left:2,
right:3,
scalars:[[0,-1],[0,1],[-1,0],[1,0]],
getRandomDirection: function() {
return Math.floor(Math.random() * 4);
},
getRandomScalar: function() {
return this.scalars[Math.floor(Math.random() * this.scalars.length)];
}
}
module.exports = Directions;

View File

@@ -45,9 +45,9 @@ class Environment{
OriginOfLife() { OriginOfLife() {
var center = this.grid_map.getCenter(); var center = this.grid_map.getCenter();
var org = new Organism(center[0], center[1], this); var org = new Organism(center[0], center[1], this);
org.addCell(CellTypes.mouth, 1, 1); org.addCell(CellTypes.mouth, 0, 0);
org.addCell(CellTypes.producer, 0, 0); org.addCell(CellTypes.producer, -1, -1);
org.addCell(CellTypes.mouth, -1, -1); org.addCell(CellTypes.producer, 1, 1);
this.addOrganism(org); this.addOrganism(org);
} }

View File

@@ -15,6 +15,11 @@ const Hyperparams = {
changeProb: 33, changeProb: 33,
removeProb: 33, removeProb: 33,
moversCanRotate: true,
offspringRotate: true,
instaKill: false,
// calculates the optimal ratio where a producer cell is most likely to produce 1 food in its lifespan * a scalar of my choice :) // calculates the optimal ratio where a producer cell is most likely to produce 1 food in its lifespan * a scalar of my choice :)
calcProducerFoodRatio : function(lifespan_fixed=true) { calcProducerFoodRatio : function(lifespan_fixed=true) {
if (lifespan_fixed) { if (lifespan_fixed) {

View File

@@ -1,4 +1,6 @@
const CellTypes = require("./CellTypes"); const CellTypes = require("./CellTypes");
const Directions = require("./Directions");
const Hyperparams = require("./Hyperparameters");
// A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid // A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid
class LocalCell{ class LocalCell{
@@ -7,6 +9,32 @@ class LocalCell{
this.loc_col = loc_col; this.loc_col = loc_col;
this.loc_row = loc_row; this.loc_row = loc_row;
} }
rotatedCol(dir){
switch(dir){
case Directions.up:
return this.loc_col;
case Directions.down:
return this.loc_col * -1;
case Directions.left:
return this.loc_row;
case Directions.right:
return this.loc_row * -1;
}
}
rotatedRow(dir){
switch(dir){
case Directions.up:
return this.loc_row;
case Directions.down:
return this.loc_row * -1;
case Directions.left:
return this.loc_col * -1;
case Directions.right:
return this.loc_col;
}
}
} }
module.exports = LocalCell; module.exports = LocalCell;

View File

@@ -4,6 +4,7 @@ const GridMap = require("./GridMap");
const LocalCell = require("./LocalCell"); const LocalCell = require("./LocalCell");
const Neighbors = require("./Neighbors"); const Neighbors = require("./Neighbors");
const Hyperparams = require("./Hyperparameters"); const Hyperparams = require("./Hyperparameters");
const Directions = require("./Directions");
const directions = [[0,1],[0,-1],[1,0],[-1,0]] const directions = [[0,1],[0,-1],[1,0],[-1,0]]
@@ -18,10 +19,13 @@ class Organism {
this.cells = []; this.cells = [];
this.is_producer = false; this.is_producer = false;
this.is_mover = false; this.is_mover = false;
this.direction = this.getRandomDirection(); this.direction = Directions.up;
this.rotation = Directions.up;
this.can_rotate = Hyperparams.moversCanRotate;
this.move_count = 0; this.move_count = 0;
this.move_range = 5; this.move_range = 4;
this.mutability = 5; this.mutability = 5;
this.damage = 0;
if (parent != null) { if (parent != null) {
this.inherit(parent); this.inherit(parent);
} }
@@ -38,6 +42,8 @@ class Organism {
} }
removeCell(c, r) { removeCell(c, r) {
if (c == 0 && r == 0)
return false;
var check_change = false; var check_change = false;
for (var i=0; i<this.cells.length; i++) { for (var i=0; i<this.cells.length; i++) {
var cell = this.cells[i]; var cell = this.cells[i];
@@ -56,6 +62,7 @@ class Organism {
this.checkProducerMover(cell.type); this.checkProducerMover(cell.type);
} }
} }
return true;
} }
checkProducerMover(type) { checkProducerMover(type) {
@@ -84,25 +91,36 @@ class Organism {
return this.cells.length * Hyperparams.lifespanMultiplier; return this.cells.length * Hyperparams.lifespanMultiplier;
} }
maxHealth() {
return this.cells.length;
}
reproduce() { reproduce() {
//produce mutated child //produce mutated child
//check nearby locations (is there room and a direct path) //check nearby locations (is there room and a direct path)
var org = new Organism(0, 0, this.env, this); var org = new Organism(0, 0, this.env, this);
if(Hyperparams.offspringRotate){
org.rotation = Directions.getRandomDirection();
}
var prob = this.mutability; var prob = this.mutability;
if (Hyperparams.useGlobalMutability) if (Hyperparams.useGlobalMutability){
prob = Hyperparams.globalMutability; prob = Hyperparams.globalMutability;
if (Math.random() * 100 <= this.mutability) { }
else {
//mutate the mutability
if (Math.random() <= 0.5)
org.mutability++;
else{
org.mutability--;
if (org.mutability < 1)
org.mutability = 1;
}
}
if (Math.random() * 100 <= prob) {
org.mutate(); org.mutate();
} }
if (Math.random() <= 0.5)
org.mutability++;
else{
org.mutability--;
if (org.mutability < 1)
org.mutability = 1;
}
var direction = this.getRandomDirection(); var direction = Directions.getRandomScalar();
var direction_c = direction[0]; var direction_c = direction[0];
var direction_r = direction[1]; var direction_r = direction[1];
var offset = (Math.floor(Math.random() * 2)) * 2; var offset = (Math.floor(Math.random() * 2)) * 2;
@@ -123,6 +141,7 @@ class Organism {
mutate() { mutate() {
var choice = Math.floor(Math.random() * 100); var choice = Math.floor(Math.random() * 100);
var mutated = false;
if (choice <= Hyperparams.addProb) { if (choice <= Hyperparams.addProb) {
// add cell // add cell
var type = CellTypes.getRandomLivingType(); var type = CellTypes.getRandomLivingType();
@@ -131,38 +150,42 @@ class Organism {
var growth_direction = Neighbors.all[Math.floor(Math.random() * Neighbors.all.length)] var growth_direction = Neighbors.all[Math.floor(Math.random() * Neighbors.all.length)]
var c = branch.loc_col+growth_direction[0]; var c = branch.loc_col+growth_direction[0];
var r = branch.loc_row+growth_direction[1]; var r = branch.loc_row+growth_direction[1];
return this.addCell(type, c, r); mutated = this.addCell(type, c, r);
} }
else if (choice <= Hyperparams.addProb + Hyperparams.changeProb){ else if (choice <= Hyperparams.addProb + Hyperparams.changeProb){
// change cell // change cell
var cell = this.cells[Math.floor(Math.random() * this.cells.length)]; var cell = this.cells[Math.floor(Math.random() * this.cells.length)];
cell.type = CellTypes.getRandomLivingType(); cell.type = CellTypes.getRandomLivingType();
this.checkProducerMover(cell.type); this.checkProducerMover(cell.type);
return true; mutated = true;
} }
else if (choice <= Hyperparams.addProb + Hyperparams.changeProb + Hyperparams.removeProb){ else if (choice <= Hyperparams.addProb + Hyperparams.changeProb + Hyperparams.removeProb){
// remove cell // remove cell
if(this.cells.length > 1) { if(this.cells.length > 1) {
this.cells.splice(Math.floor(Math.random() * this.cells.length), 1); cell = this.cells[Math.floor(Math.random() * this.cells.length)];
return true; mutated = this.removeCell(cell.loc_col, cell.loc_row);
} }
} }
if (this.is_mover) { if (this.is_mover) {
this.move_range += Math.floor(Math.random() * 4) - 2; this.move_range += Math.floor(Math.random() * 4) - 2;
if (this.move_range <= 0){
this.move_range = 1;
}
} }
return false; return mutated;
} }
attemptMove(direction) { attemptMove() {
var direction = Directions.scalars[this.direction];
var direction_c = direction[0]; var direction_c = direction[0];
var direction_r = direction[1]; var direction_r = direction[1];
var new_c = this.c + direction_c; var new_c = this.c + direction_c;
var new_r = this.r + direction_r; var new_r = this.r + direction_r;
if (this.isClear(new_c, new_r)) { if (this.isClear(new_c, new_r)) {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.loc_col; var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.loc_row; var real_r = this.r + cell.rotatedRow(this.rotation);
this.env.changeCell(real_c, real_r, CellTypes.empty, null); this.env.changeCell(real_c, real_r, CellTypes.empty, null);
} }
this.c = new_c; this.c = new_c;
@@ -173,13 +196,30 @@ class Organism {
return false; return false;
} }
getRandomDirection(){ attemptRotate() {
return directions[Math.floor(Math.random() * directions.length)]; if(!this.can_rotate){
this.direction = Directions.getRandomDirection();
this.move_count = 0;
return true;
}
var new_rotation = Directions.getRandomDirection();
if(this.isClear(this.c, this.r, new_rotation)){
for (var cell of this.cells) {
var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.rotatedRow(this.rotation);
this.env.changeCell(real_c, real_r, CellTypes.empty, null);
}
this.rotation = new_rotation;
this.direction = Directions.getRandomDirection();
this.updateGrid();
this.move_count = 0;
return true;
}
return false;
} }
// assumes either c1==c2 or r1==r2, returns true if there is a clear path from point a to b // assumes either c1==c2 or r1==r2, returns true if there is a clear path from point a to b
isStraightPath(c1, r1, c2, r2, parent){ isStraightPath(c1, r1, c2, r2, parent){
// TODO FIX!!!
if (c1 == c2) { if (c1 == c2) {
if (r1 > r2){ if (r1 > r2){
var temp = r2; var temp = r2;
@@ -214,9 +254,9 @@ class Organism {
return cell != null && (cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent || cell.type == CellTypes.food); return cell != null && (cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent || cell.type == CellTypes.food);
} }
isClear(col, row) { isClear(col, row, rotation=this.rotation) {
for(var loccell of this.cells) { for(var loccell of this.cells) {
var cell = this.getRealCell(loccell, col, row); var cell = this.getRealCell(loccell, col, row, rotation);
if(cell == null || cell.type != CellTypes.empty && cell.owner != this) { if(cell == null || cell.type != CellTypes.empty && cell.owner != this) {
return false; return false;
} }
@@ -224,10 +264,17 @@ class Organism {
return true; return true;
} }
harm() {
this.damage++;
if (this.damage >= this.maxHealth() || Hyperparams.instaKill) {
this.die();
}
}
die() { die() {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.loc_col; var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.loc_row; var real_r = this.r + cell.rotatedRow(this.rotation);
this.env.changeCell(real_c, real_r, CellTypes.food, null); this.env.changeCell(real_c, real_r, CellTypes.food, null);
} }
this.living = false; this.living = false;
@@ -235,14 +282,13 @@ class Organism {
updateGrid() { updateGrid() {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.loc_col; var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.loc_row; var real_r = this.r + cell.rotatedRow(this.rotation);
this.env.changeCell(real_c, real_r, cell.type, this); this.env.changeCell(real_c, real_r, cell.type, this);
} }
} }
update() { update() {
// this.food_collected++;
this.lifetime++; this.lifetime++;
if (this.lifetime > this.lifespan()) { if (this.lifetime > this.lifespan()) {
this.die(); this.die();
@@ -259,19 +305,18 @@ class Organism {
} }
if (this.is_mover) { if (this.is_mover) {
this.move_count++; this.move_count++;
var success = this.attemptMove(this.direction); var moved = this.attemptMove();
if (this.move_count > this.move_range || !success){ if (this.move_count > this.move_range){
this.move_count = 0; this.attemptRotate();
this.direction = this.getRandomDirection()
} }
} }
return this.living; return this.living;
} }
getRealCell(local_cell, c=this.c, r=this.r){ getRealCell(local_cell, c=this.c, r=this.r, rotation=this.rotation){
var real_c = c + local_cell.loc_col; var real_c = c + local_cell.rotatedCol(rotation);
var real_r = r + local_cell.loc_row; var real_r = r + local_cell.rotatedRow(rotation);
return this.env.grid_map.cellAt(real_c, real_r); return this.env.grid_map.cellAt(real_c, real_r);
} }