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
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

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;
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
View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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
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() {
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);
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);
}