325 lines
11 KiB
JavaScript
325 lines
11 KiB
JavaScript
const CellStates = require("./Cell/CellStates");
|
|
const Neighbors = require("../Grid/Neighbors");
|
|
const Hyperparams = require("../Hyperparameters");
|
|
const Directions = require("./Directions");
|
|
const Anatomy = require("./Anatomy");
|
|
const Brain = require("./Perception/Brain");
|
|
const FossilRecord = require("../Stats/FossilRecord");
|
|
|
|
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.anatomy = new Anatomy(this)
|
|
this.direction = Directions.down; // direction of movement
|
|
this.rotation = Directions.up; // direction of rotation
|
|
this.can_rotate = Hyperparams.rotationEnabled;
|
|
this.move_count = 0;
|
|
this.move_range = 4;
|
|
this.ignore_brain_for = 0;
|
|
this.mutability = 5;
|
|
this.damage = 0;
|
|
this.brain = new Brain(this);
|
|
if (parent != null) {
|
|
this.inherit(parent);
|
|
}
|
|
}
|
|
|
|
inherit(parent) {
|
|
this.move_range = parent.move_range;
|
|
this.mutability = parent.mutability;
|
|
this.species = parent.species;
|
|
// this.birth_distance = parent.birth_distance;
|
|
for (var c of parent.anatomy.cells){
|
|
//deep copy parent cells
|
|
this.anatomy.addInheritCell(c);
|
|
}
|
|
if(parent.anatomy.is_mover) {
|
|
for (var i in parent.brain.decisions) {
|
|
this.brain.decisions[i] = parent.brain.decisions[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// amount of food required before it can reproduce
|
|
foodNeeded() {
|
|
return this.anatomy.is_mover ? this.anatomy.cells.length + Hyperparams.extraMoverFoodCost : this.anatomy.cells.length;
|
|
}
|
|
|
|
lifespan() {
|
|
return this.anatomy.cells.length * Hyperparams.lifespanMultiplier;
|
|
}
|
|
|
|
maxHealth() {
|
|
return this.anatomy.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.rotationEnabled){
|
|
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;
|
|
}
|
|
}
|
|
var mutated = false;
|
|
if (Math.random() * 100 <= prob) {
|
|
if (org.anatomy.is_mover && Math.random() * 100 <= 10) {
|
|
if (org.anatomy.has_eyes) {
|
|
org.brain.mutate();
|
|
}
|
|
org.move_range += Math.floor(Math.random() * 4) - 2;
|
|
if (org.move_range <= 0){
|
|
org.move_range = 1;
|
|
};
|
|
|
|
}
|
|
else {
|
|
mutated = org.mutate();
|
|
}
|
|
}
|
|
|
|
var direction = Directions.getRandomScalar();
|
|
var direction_c = direction[0];
|
|
var direction_r = direction[1];
|
|
var offset = (Math.floor(Math.random() * 3));
|
|
var basemovement = this.anatomy.birth_distance;
|
|
var new_c = this.c + (direction_c*basemovement) + (direction_c*offset);
|
|
var new_r = this.r + (direction_r*basemovement) + (direction_r*offset);
|
|
|
|
// console.log(org.isClear(new_c, new_r, org.rotation, true))
|
|
if (org.isClear(new_c, new_r, org.rotation, true) && 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();
|
|
if (mutated) {
|
|
FossilRecord.addSpecies(org, this.species);
|
|
}
|
|
else {
|
|
org.species.addPop();
|
|
}
|
|
}
|
|
Math.max(this.food_collected -= this.foodNeeded(), 0);
|
|
|
|
}
|
|
|
|
mutate() {
|
|
let mutated = false;
|
|
if (this.calcRandomChance(Hyperparams.addProb)) {
|
|
let branch = this.anatomy.getRandomCell();
|
|
let state = CellStates.getRandomLivingType();//branch.state;
|
|
let growth_direction = Neighbors.all[Math.floor(Math.random() * Neighbors.all.length)]
|
|
let c = branch.loc_col+growth_direction[0];
|
|
let r = branch.loc_row+growth_direction[1];
|
|
if (this.anatomy.canAddCellAt(c, r)){
|
|
mutated = true;
|
|
this.anatomy.addRandomizedCell(state, c, r);
|
|
}
|
|
}
|
|
if (this.calcRandomChance(Hyperparams.changeProb)){
|
|
let cell = this.anatomy.getRandomCell();
|
|
let state = CellStates.getRandomLivingType();
|
|
this.anatomy.replaceCell(state, cell.loc_col, cell.loc_row);
|
|
mutated = true;
|
|
}
|
|
if (this.calcRandomChance(Hyperparams.removeProb)){
|
|
if(this.anatomy.cells.length > 1) {
|
|
let cell = this.anatomy.getRandomCell();
|
|
mutated = this.anatomy.removeCell(cell.loc_col, cell.loc_row);
|
|
}
|
|
}
|
|
return mutated;
|
|
}
|
|
|
|
calcRandomChance(prob) {
|
|
return (Math.random() * 100) < prob;
|
|
}
|
|
|
|
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.anatomy.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, CellStates.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.anatomy.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, CellStates.empty, null);
|
|
}
|
|
this.rotation = new_rotation;
|
|
this.direction = Directions.getRandomDirection();
|
|
this.updateGrid();
|
|
this.move_count = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
changeDirection(dir) {
|
|
this.direction = dir;
|
|
this.move_count = 0;
|
|
}
|
|
|
|
// assumes either c1==c2 or r1==r2, returns true if there is a clear path from point 1 to 2
|
|
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.state == CellStates.empty || cell.owner == this || cell.owner == parent || cell.state == CellStates.food);
|
|
}
|
|
|
|
isClear(col, row, rotation=this.rotation, ignore_armor=false) {
|
|
for(var loccell of this.anatomy.cells) {
|
|
var cell = this.getRealCell(loccell, col, row, rotation);
|
|
if (cell==null) {
|
|
return false;
|
|
}
|
|
if (cell.owner==this || cell.state==CellStates.empty || (!Hyperparams.foodBlocksReproduction && cell.state==CellStates.food)){
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
harm() {
|
|
this.damage++;
|
|
if (this.damage >= this.maxHealth() || Hyperparams.instaKill) {
|
|
this.die();
|
|
}
|
|
}
|
|
|
|
die() {
|
|
for (var cell of this.anatomy.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, CellStates.food, null);
|
|
}
|
|
this.species.decreasePop();
|
|
this.living = false;
|
|
}
|
|
|
|
updateGrid() {
|
|
for (var cell of this.anatomy.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.state, cell);
|
|
}
|
|
}
|
|
|
|
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.anatomy.cells) {
|
|
cell.performFunction();
|
|
if (!this.living)
|
|
return this.living
|
|
}
|
|
|
|
if (this.anatomy.is_mover) {
|
|
this.move_count++;
|
|
var changed_dir = false;
|
|
if (this.ignore_brain_for == 0){
|
|
changed_dir = this.brain.decide();
|
|
}
|
|
else{
|
|
this.ignore_brain_for --;
|
|
}
|
|
var moved = this.attemptMove();
|
|
if ((this.move_count > this.move_range && !changed_dir) || !moved){
|
|
var rotated = this.attemptRotate();
|
|
if (!rotated) {
|
|
this.changeDirection(Directions.getRandomDirection());
|
|
if (changed_dir)
|
|
this.ignore_brain_for = this.move_range + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|