control tabs, organism editor, code refactor

This commit is contained in:
MaxRobinsonTheGreat
2020-07-18 00:31:46 -06:00
parent 2fc2ba7b80
commit ebb39df34a
21 changed files with 524 additions and 402 deletions

93
src/Organism/Cell/Cell.js Normal file
View File

@@ -0,0 +1,93 @@
const CellTypes = require("./CellTypes");
const Hyperparams = require("../../Hyperparameters");
// A cell exists in a grid system.
class Cell{
constructor(type, col, row, x, y){
this.owner = null;
this.setType(type);
this.col = col;
this.row = row;
this.x = x;
this.y = y;
}
setType(type) {
this.type = type;
}
performFunction(env) {
switch(this.type){
case CellTypes.mouth:
eatFood(this, env);
break;
case CellTypes.producer:
growFood(this, env);
break;
case CellTypes.killer:
killNeighbors(this, env);
break;
}
}
getColor() {
return CellTypes.colors[this.type];
}
isLiving() {
return this.type != CellTypes.empty &&
this.type != CellTypes.food &&
this.type != CellTypes.wall;
}
}
function eatFood(self, env){
for (var loc of Hyperparams.edibleNeighbors){
var cell = env.grid_map.cellAt(self.col+loc[0], self.row+loc[1]);
eatNeighborFood(self, cell, env);
}
}
function eatNeighborFood(self, n_cell, env){
if (n_cell == null)
return;
if (n_cell.type == CellTypes.food){
env.changeCell(n_cell.col, n_cell.row, CellTypes.empty, null);
self.owner.food_collected++;
}
}
function growFood(self, env){
if (self.owner.is_mover)
return;
var prob = Hyperparams.foodProdProb;
if (Math.random() * 100 <= prob){
var loc = Hyperparams.growableNeighbors[Math.floor(Math.random() * Hyperparams.growableNeighbors.length)]
var c=loc[0];
var r=loc[1];
var cell = env.grid_map.cellAt(self.col+c, self.row+r);
if (cell != null && cell.type == CellTypes.empty){
env.changeCell(self.col+c, self.row+r, CellTypes.food, null);
return;
}
}
}
function killNeighbors(self, env) {
for (var loc of Hyperparams.killableNeighbors){
var cell = env.grid_map.cellAt(self.col+loc[0], self.row+loc[1]);
killNeighbor(self, cell);
}
}
function killNeighbor(self, n_cell) {
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 is_hit = n_cell.type == CellTypes.killer; // has to be calculated before death
n_cell.owner.harm();
if (is_hit){
self.owner.harm();
}
}
module.exports = Cell;

View File

@@ -0,0 +1,16 @@
const CellTypes = {
empty: 0,
food: 1,
wall: 2,
mouth: 3,
producer: 4,
mover: 5,
killer: 6,
armor: 7,
colors: ['#121D29', 'green', 'gray', 'orange', 'white', '#3493eb', 'red', 'purple'],
getRandomLivingType: function() {
return Math.floor(Math.random() * 5) + 3;
}
}
module.exports = CellTypes;

View File

@@ -0,0 +1,40 @@
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{
constructor(type, loc_col, loc_row){
this.type = type;
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

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

339
src/Organism/Organism.js Normal file
View File

@@ -0,0 +1,339 @@
const CellTypes = require("./Cell/CellTypes");
const Cell = require("./Cell/Cell");
const GridMap = require("../Grid/GridMap");
const LocalCell = require("./Cell/LocalCell");
const Neighbors = require("../Grid/Neighbors");
const Hyperparams = require("../Hyperparameters");
const Directions = require("./Directions");
const directions = [[0,1],[0,-1],[1,0],[-1,0]]
class Organism {
constructor(col, row, env, parent=null) {
this.c = col;
this.r = row;
this.env = env;
this.lifetime = 0;
this.food_collected = 0;
this.living = true;
this.cells = [];
this.is_producer = false;
this.is_mover = false;
this.direction = Directions.up;
this.rotation = Directions.up;
this.can_rotate = Hyperparams.moversCanRotate;
this.move_count = 0;
this.move_range = 4;
this.mutability = 5;
this.damage = 0;
if (parent != null) {
this.inherit(parent);
}
}
addCell(type, c, r) {
for (var cell of this.cells) {
if (cell.loc_col == c && cell.loc_row == r){
return false;
}
}
this.checkProducerMover(type);
this.cells.push(new LocalCell(type, c, r));
return true;
}
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];
if (cell.loc_col == c && cell.loc_row == r){
if (cell.type == CellTypes.producer || cell.type == CellTypes.mover) {
check_change = true;
}
this.cells.splice(i, 1);
break;
}
}
if (check_change) {
this.is_producer = false;
this.is_producer = false;
for (var cell of this.cells) {
this.checkProducerMover(cell.type);
}
}
return true;
}
getLocalCell(c, r) {
for (var cell of this.cells) {
if (cell.loc_col == c && cell.loc_row == r){
return cell;
}
}
return null;
}
checkProducerMover(type) {
if (type == CellTypes.producer)
this.is_producer = true;
if (type == CellTypes.mover)
this.is_mover = true;
}
inherit(parent) {
this.move_range = parent.move_range;
this.mutability = parent.mutability;
for (var c of parent.cells){
//deep copy parent cells
this.addCell(c.type, c.loc_col, c.loc_row);
}
}
// amount of food required before it can reproduce
foodNeeded() {
return this.cells.length;
}
lifespan() {
// console.log(Hyperparams.lifespanMultiplier)
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){
prob = Hyperparams.globalMutability;
}
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();
}
var direction = Directions.getRandomScalar();
var direction_c = direction[0];
var direction_r = direction[1];
var offset = (Math.floor(Math.random() * 2)) * 2;
var new_c = this.c + (direction_c*Math.min(this.cells.length*2, 15)) + (direction_c*offset);
var new_r = this.r + (direction_r*Math.min(this.cells.length*2, 15)) + (direction_r*offset);
// var new_c = Math.min(this.cells.length*2, 10);
// var new_r = Math.min(this.cells.length*2, 10);
if (org.isClear(new_c, new_r) && org.isStraightPath(new_c, new_r, this.c, this.r, this)){
org.c = new_c;
org.r = new_r;
this.env.addOrganism(org);
org.updateGrid();
}
this.food_collected -= this.foodNeeded();
}
mutate() {
var choice = Math.floor(Math.random() * 100);
var mutated = false;
if (choice <= Hyperparams.addProb) {
// add cell
var type = CellTypes.getRandomLivingType();
var num_to_add = Math.floor(Math.random() * 3) + 1;
var branch = this.cells[Math.floor(Math.random() * this.cells.length)];
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];
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);
mutated = true;
}
else if (choice <= Hyperparams.addProb + Hyperparams.changeProb + Hyperparams.removeProb){
// remove cell
if(this.cells.length > 1) {
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 mutated;
}
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.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;
this.r = new_r;
this.updateGrid();
return true;
}
return false;
}
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){
if (c1 == c2) {
if (r1 > r2){
var temp = r2;
r2 = r1;
r1 = temp;
}
for (var i=r1; i!=r2; i++) {
var cell = this.env.grid_map.cellAt(c1, i)
if (!this.isPassableCell(cell, parent)){
return false;
}
}
return true;
}
else {
if (c1 > c2){
var temp = c2;
c2 = c1;
c1 = temp;
}
for (var i=c1; i!=c2; i++) {
var cell = this.env.grid_map.cellAt(i, r1);
if (!this.isPassableCell(cell, parent)){
return false;
}
}
return true;
}
}
isPassableCell(cell, parent){
return cell != null && (cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent || cell.type == CellTypes.food);
}
isClear(col, row, rotation=this.rotation) {
for(var loccell of this.cells) {
var cell = this.getRealCell(loccell, col, row, rotation);
if(cell == null || cell.type != CellTypes.empty && cell.owner != this) {
return false;
}
}
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.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;
}
updateGrid() {
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, cell.type, this);
}
}
update() {
this.lifetime++;
if (this.lifetime > this.lifespan()) {
this.die();
return this.living;
}
if (this.food_collected >= this.foodNeeded()) {
this.reproduce();
}
for (var cell of this.cells) {
this.getRealCell(cell).performFunction(this.env);
}
if (!this.living){
return this.living
}
if (this.is_mover) {
this.move_count++;
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, 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);
}
}
module.exports = Organism;