Added rotations, health, damage
This commit is contained in:
10
README.md
10
README.md
@@ -1,10 +1,18 @@
|
||||
# EvolutionSimulatorV2
|
||||
Version 2 of the evolution simulator
|
||||
|
||||
Requirements:
|
||||
Requirements to Play:
|
||||
- browser (preferably chrome)
|
||||
|
||||
Requirements to edit and build:
|
||||
- npm
|
||||
- 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 in dev mode: `npm run build-dev`
|
||||
To build in dev/watch mode: `npm run build-watch`
|
||||
|
||||
256
dist/bundle.js
vendored
256
dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
21
dist/css/style.css
vendored
21
dist/css/style.css
vendored
@@ -2,6 +2,8 @@ body{
|
||||
background: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
canvas {
|
||||
@@ -15,15 +17,18 @@ canvas {
|
||||
}
|
||||
|
||||
.env {
|
||||
height: 80vh;
|
||||
position: static;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 300px; /* must correspond to control-panel height*/
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
height: 20vh;
|
||||
width: 100vw;
|
||||
height: 300px;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
background-color: gray;
|
||||
position: fixed;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
@@ -35,20 +40,16 @@ canvas {
|
||||
border: 10px;
|
||||
border-color: black;
|
||||
background-color: lightgray;
|
||||
max-width: 100%;
|
||||
/* height: 100%; */
|
||||
}
|
||||
|
||||
#speed-controller {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
/* background-color: blue; */
|
||||
}
|
||||
|
||||
#stats {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
/* background-color: blue; */
|
||||
}
|
||||
|
||||
#hyperparameters {
|
||||
@@ -60,8 +61,6 @@ canvas {
|
||||
#abilities {
|
||||
grid-column: 3;
|
||||
grid-row: 1 / 3;
|
||||
/* background-color: yellow; */
|
||||
|
||||
}
|
||||
|
||||
.control-mode-button {
|
||||
|
||||
13
dist/html/index.html
vendored
13
dist/html/index.html
vendored
@@ -4,14 +4,15 @@
|
||||
<meta charste="UTF-8">
|
||||
<title>Evolution Simulator</title>
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<script src="../bundle.js"></script>
|
||||
|
||||
<div class='env'>
|
||||
<canvas id='canvas'></canvas>
|
||||
</div>
|
||||
|
||||
<div class='control-panel'>
|
||||
<div id='speed-controller' class='control-set'>
|
||||
<h2>Simulation Speed</h2>
|
||||
@@ -48,6 +49,15 @@
|
||||
<label for="remove-prob">Remove Cell:</label>
|
||||
<input class="mut-prob" type="number" id="remove-prob" min="0" max="100" value=33>
|
||||
<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 id='abilities' class='control-set'>
|
||||
@@ -62,6 +72,5 @@
|
||||
<button id='clear-walls'>Clear All Walls</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
10
src/Cell.js
10
src/Cell.js
@@ -81,12 +81,12 @@ function killNeighbors(self, env) {
|
||||
}
|
||||
|
||||
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;
|
||||
var should_die = n_cell.type == CellTypes.killer; // has to be calculated before death
|
||||
n_cell.owner.die();
|
||||
if (should_die){
|
||||
self.owner.die();
|
||||
var is_hit = n_cell.type == CellTypes.killer; // has to be calculated before death
|
||||
n_cell.owner.harm();
|
||||
if (is_hit){
|
||||
self.owner.harm();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const CellTypes = {
|
||||
mover: 5,
|
||||
killer: 6,
|
||||
armor: 7,
|
||||
colors: ['#121D29', 'green', 'gray', 'orange', 'white', 'blue', 'red', 'purple'],
|
||||
colors: ['#121D29', 'green', 'gray', 'orange', 'white', '#3493eb', 'red', 'purple'],
|
||||
getRandomLivingType: function() {
|
||||
return Math.floor(Math.random() * 5) + 3;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ class ControlPanel {
|
||||
}
|
||||
else if (!this.engine.running){
|
||||
$('#pause-button').text("Pause");
|
||||
console.log(this.fps)
|
||||
this.engine.start(this.fps);
|
||||
}
|
||||
}.bind(this));
|
||||
@@ -78,6 +77,16 @@ class ControlPanel {
|
||||
$('#change-prob').val(Math.floor(Hyperparams.changeProb));
|
||||
$('#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() {
|
||||
|
||||
15
src/Directions.js
Normal file
15
src/Directions.js
Normal 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;
|
||||
@@ -45,9 +45,9 @@ class Environment{
|
||||
OriginOfLife() {
|
||||
var center = this.grid_map.getCenter();
|
||||
var org = new Organism(center[0], center[1], this);
|
||||
org.addCell(CellTypes.mouth, 1, 1);
|
||||
org.addCell(CellTypes.producer, 0, 0);
|
||||
org.addCell(CellTypes.mouth, -1, -1);
|
||||
org.addCell(CellTypes.mouth, 0, 0);
|
||||
org.addCell(CellTypes.producer, -1, -1);
|
||||
org.addCell(CellTypes.producer, 1, 1);
|
||||
this.addOrganism(org);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ const Hyperparams = {
|
||||
changeProb: 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 :)
|
||||
calcProducerFoodRatio : function(lifespan_fixed=true) {
|
||||
if (lifespan_fixed) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
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
|
||||
class LocalCell{
|
||||
@@ -7,6 +9,32 @@ class LocalCell{
|
||||
this.loc_col = loc_col;
|
||||
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;
|
||||
|
||||
119
src/Organism.js
119
src/Organism.js
@@ -4,6 +4,7 @@ const GridMap = require("./GridMap");
|
||||
const LocalCell = require("./LocalCell");
|
||||
const Neighbors = require("./Neighbors");
|
||||
const Hyperparams = require("./Hyperparameters");
|
||||
const Directions = require("./Directions");
|
||||
|
||||
const directions = [[0,1],[0,-1],[1,0],[-1,0]]
|
||||
|
||||
@@ -18,10 +19,13 @@ class Organism {
|
||||
this.cells = [];
|
||||
this.is_producer = 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_range = 5;
|
||||
this.move_range = 4;
|
||||
this.mutability = 5;
|
||||
this.damage = 0;
|
||||
if (parent != null) {
|
||||
this.inherit(parent);
|
||||
}
|
||||
@@ -38,6 +42,8 @@ class Organism {
|
||||
}
|
||||
|
||||
removeCell(c, r) {
|
||||
if (c == 0 && r == 0)
|
||||
return false;
|
||||
var check_change = false;
|
||||
for (var i=0; i<this.cells.length; i++) {
|
||||
var cell = this.cells[i];
|
||||
@@ -56,6 +62,7 @@ class Organism {
|
||||
this.checkProducerMover(cell.type);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
checkProducerMover(type) {
|
||||
@@ -84,25 +91,36 @@ class Organism {
|
||||
return this.cells.length * Hyperparams.lifespanMultiplier;
|
||||
}
|
||||
|
||||
maxHealth() {
|
||||
return this.cells.length;
|
||||
}
|
||||
|
||||
reproduce() {
|
||||
//produce mutated child
|
||||
//check nearby locations (is there room and a direct path)
|
||||
var org = new Organism(0, 0, this.env, this);
|
||||
if(Hyperparams.offspringRotate){
|
||||
org.rotation = Directions.getRandomDirection();
|
||||
}
|
||||
var prob = this.mutability;
|
||||
if (Hyperparams.useGlobalMutability)
|
||||
if (Hyperparams.useGlobalMutability){
|
||||
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();
|
||||
}
|
||||
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_r = direction[1];
|
||||
var offset = (Math.floor(Math.random() * 2)) * 2;
|
||||
@@ -123,6 +141,7 @@ class Organism {
|
||||
|
||||
mutate() {
|
||||
var choice = Math.floor(Math.random() * 100);
|
||||
var mutated = false;
|
||||
if (choice <= Hyperparams.addProb) {
|
||||
// add cell
|
||||
var type = CellTypes.getRandomLivingType();
|
||||
@@ -131,38 +150,42 @@ class Organism {
|
||||
var growth_direction = Neighbors.all[Math.floor(Math.random() * Neighbors.all.length)]
|
||||
var c = branch.loc_col+growth_direction[0];
|
||||
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){
|
||||
// change cell
|
||||
var cell = this.cells[Math.floor(Math.random() * this.cells.length)];
|
||||
cell.type = CellTypes.getRandomLivingType();
|
||||
this.checkProducerMover(cell.type);
|
||||
return true;
|
||||
mutated = true;
|
||||
}
|
||||
else if (choice <= Hyperparams.addProb + Hyperparams.changeProb + Hyperparams.removeProb){
|
||||
// remove cell
|
||||
if(this.cells.length > 1) {
|
||||
this.cells.splice(Math.floor(Math.random() * this.cells.length), 1);
|
||||
return true;
|
||||
cell = this.cells[Math.floor(Math.random() * this.cells.length)];
|
||||
mutated = this.removeCell(cell.loc_col, cell.loc_row);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.is_mover) {
|
||||
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_r = direction[1];
|
||||
var new_c = this.c + direction_c;
|
||||
var new_r = this.r + direction_r;
|
||||
if (this.isClear(new_c, new_r)) {
|
||||
for (var cell of this.cells) {
|
||||
var real_c = this.c + cell.loc_col;
|
||||
var real_r = this.r + cell.loc_row;
|
||||
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.c = new_c;
|
||||
@@ -173,13 +196,30 @@ class Organism {
|
||||
return false;
|
||||
}
|
||||
|
||||
getRandomDirection(){
|
||||
return directions[Math.floor(Math.random() * directions.length)];
|
||||
attemptRotate() {
|
||||
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
|
||||
isStraightPath(c1, r1, c2, r2, parent){
|
||||
// TODO FIX!!!
|
||||
if (c1 == c2) {
|
||||
if (r1 > 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);
|
||||
}
|
||||
|
||||
isClear(col, row) {
|
||||
isClear(col, row, rotation=this.rotation) {
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@@ -224,10 +264,17 @@ class Organism {
|
||||
return true;
|
||||
}
|
||||
|
||||
harm() {
|
||||
this.damage++;
|
||||
if (this.damage >= this.maxHealth() || Hyperparams.instaKill) {
|
||||
this.die();
|
||||
}
|
||||
}
|
||||
|
||||
die() {
|
||||
for (var cell of this.cells) {
|
||||
var real_c = this.c + cell.loc_col;
|
||||
var real_r = this.r + cell.loc_row;
|
||||
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.food, null);
|
||||
}
|
||||
this.living = false;
|
||||
@@ -235,14 +282,13 @@ class Organism {
|
||||
|
||||
updateGrid() {
|
||||
for (var cell of this.cells) {
|
||||
var real_c = this.c + cell.loc_col;
|
||||
var real_r = this.r + cell.loc_row;
|
||||
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, cell.type, this);
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
// this.food_collected++;
|
||||
this.lifetime++;
|
||||
if (this.lifetime > this.lifespan()) {
|
||||
this.die();
|
||||
@@ -259,19 +305,18 @@ class Organism {
|
||||
}
|
||||
if (this.is_mover) {
|
||||
this.move_count++;
|
||||
var success = this.attemptMove(this.direction);
|
||||
if (this.move_count > this.move_range || !success){
|
||||
this.move_count = 0;
|
||||
this.direction = this.getRandomDirection()
|
||||
var moved = this.attemptMove();
|
||||
if (this.move_count > this.move_range){
|
||||
this.attemptRotate();
|
||||
}
|
||||
}
|
||||
|
||||
return this.living;
|
||||
}
|
||||
|
||||
getRealCell(local_cell, c=this.c, r=this.r){
|
||||
var real_c = c + local_cell.loc_col;
|
||||
var real_r = r + local_cell.loc_row;
|
||||
getRealCell(local_cell, c=this.c, r=this.r, rotation=this.rotation){
|
||||
var real_c = c + local_cell.rotatedCol(rotation);
|
||||
var real_r = r + local_cell.rotatedRow(rotation);
|
||||
return this.env.grid_map.cellAt(real_c, real_r);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user