Added body cell functionality

This commit is contained in:
MaxRobinsonTheGreat
2020-08-17 23:35:47 -06:00
parent 3589df3919
commit 3f05fbe7f9
24 changed files with 598 additions and 294 deletions

2
dist/js/bundle.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,5 @@
const Hyperparams = require("../Hyperparameters"); const Hyperparams = require("../Hyperparameters");
const Modes = require("./ControlModes"); const Modes = require("./ControlModes");
const CellTypes = require("../Organism/Cell/CellTypes");
class ControlPanel { class ControlPanel {
constructor(engine) { constructor(engine) {

View File

@@ -1,6 +1,6 @@
const CanvasController = require("./CanvasController"); const CanvasController = require("./CanvasController");
const Modes = require("./ControlModes"); const Modes = require("./ControlModes");
const CellTypes = require("../Organism/Cell/CellTypes"); const CellStates = require("../Organism/Cell/CellStates");
const Directions = require("../Organism/Directions"); const Directions = require("../Organism/Directions");
class EditorController extends CanvasController{ class EditorController extends CanvasController{
@@ -25,7 +25,6 @@ class EditorController extends CanvasController{
mouseUp(){} mouseUp(){}
getCurLocalCell(){ getCurLocalCell(){
console.log(this.env.organism.getLocalCell(this.mouse_c-this.env.organism.c, this.mouse_r-this.env.organism.r))
return this.env.organism.getLocalCell(this.mouse_c-this.env.organism.c, this.mouse_r-this.env.organism.r); return this.env.organism.getLocalCell(this.mouse_c-this.env.organism.c, this.mouse_r-this.env.organism.r);
} }
@@ -37,25 +36,13 @@ class EditorController extends CanvasController{
if (this.edit_cell_type == null || this.mode != Modes.Edit) if (this.edit_cell_type == null || this.mode != Modes.Edit)
return; return;
if (this.left_click){ if (this.left_click){
if(this.edit_cell_type == CellTypes.eye) { if(this.edit_cell_type == CellStates.eye && this.cur_cell.state == CellStates.eye) {
if (this.cur_cell.type == CellTypes.eye){ var loc_cell = this.getCurLocalCell();
var loc_cell = this.getCurLocalCell(); loc_cell.direction = Directions.rotateRight(loc_cell.direction);
var dir = loc_cell.eye.direction; this.env.renderFull();
dir = Directions.rotateRight(dir);
loc_cell.eye.direction = dir;
this.cur_cell.direction = dir;
this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type);
}
else {
this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type);
var loc_cell = this.getCurLocalCell();
loc_cell.eye.direction = Directions.up;
this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type);
}
} }
else{ else
this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type); this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type);
}
} }
if (this.right_click) if (this.right_click)
this.env.removeCellFromOrg(this.mouse_c, this.mouse_r); this.env.removeCellFromOrg(this.mouse_c, this.mouse_r);
@@ -70,22 +57,22 @@ class EditorController extends CanvasController{
$('.cell-type').click( function() { $('.cell-type').click( function() {
switch(this.id){ switch(this.id){
case "mouth": case "mouth":
self.edit_cell_type = CellTypes.mouth; self.edit_cell_type = CellStates.mouth;
break; break;
case "producer": case "producer":
self.edit_cell_type = CellTypes.producer; self.edit_cell_type = CellStates.producer;
break; break;
case "mover": case "mover":
self.edit_cell_type = CellTypes.mover; self.edit_cell_type = CellStates.mover;
break; break;
case "killer": case "killer":
self.edit_cell_type = CellTypes.killer; self.edit_cell_type = CellStates.killer;
break; break;
case "armor": case "armor":
self.edit_cell_type = CellTypes.armor; self.edit_cell_type = CellStates.armor;
break; break;
case "eye": case "eye":
self.edit_cell_type = CellTypes.eye; self.edit_cell_type = CellStates.eye;
break; break;
} }
$(".cell-type" ).css( "border-color", "black" ); $(".cell-type" ).css( "border-color", "black" );

View File

@@ -1,9 +1,9 @@
const CanvasController = require("./CanvasController"); const CanvasController = require("./CanvasController");
const Organism = require('../Organism/Organism'); const Organism = require('../Organism/Organism');
const Modes = require("./ControlModes"); const Modes = require("./ControlModes");
const CellTypes = require("../Organism/Cell/CellTypes"); const CellStates = require("../Organism/Cell/CellStates");
const Neighbors = require("../Grid/Neighbors"); const Neighbors = require("../Grid/Neighbors");
const Cell = require("../Organism/Cell/Cell"); const Cell = require("../Organism/Cell/GridCell");
class EnvironmentController extends CanvasController{ class EnvironmentController extends CanvasController{
constructor(env, canvas) { constructor(env, canvas) {
@@ -69,19 +69,19 @@ class EnvironmentController extends CanvasController{
switch(mode) { switch(mode) {
case Modes.FoodDrop: case Modes.FoodDrop:
if (left_click){ if (left_click){
this.dropCellType(cell.col, cell.row, CellTypes.food, false); this.dropCellType(cell.col, cell.row, CellStates.food, false);
} }
else if (right_click){ else if (right_click){
this.dropCellType(cell.col, cell.row, CellTypes.empty, false); this.dropCellType(cell.col, cell.row, CellStates.empty, false);
} }
break; break;
case Modes.WallDrop: case Modes.WallDrop:
if (left_click){ if (left_click){
this.dropCellType(cell.col, cell.row, CellTypes.wall, true); this.dropCellType(cell.col, cell.row, CellStates.wall, true);
} }
else if (right_click){ else if (right_click){
this.dropCellType(cell.col, cell.row, CellTypes.empty, false); this.dropCellType(cell.col, cell.row, CellStates.empty, false);
} }
break; break;
case Modes.ClickKill: case Modes.ClickKill:
@@ -118,7 +118,7 @@ class EnvironmentController extends CanvasController{
} }
} }
dropCellType(col, row, type, killBlocking=false) { dropCellType(col, row, state, killBlocking=false) {
for (var loc of Neighbors.allSelf){ for (var loc of Neighbors.allSelf){
var c=col + loc[0]; var c=col + loc[0];
var r=row + loc[1]; var r=row + loc[1];
@@ -131,7 +131,7 @@ class EnvironmentController extends CanvasController{
else if (cell.owner != null) { else if (cell.owner != null) {
continue; continue;
} }
this.env.changeCell(c, r, type, null); this.env.changeCell(c, r, state, null);
} }
} }

View File

@@ -8,8 +8,8 @@ class Environment{
alert("Environment.update() must be overriden"); alert("Environment.update() must be overriden");
} }
changeCell(c, r, type, owner) { changeCell(c, r, state, owner) {
this.grid_map.setCellType(c, r, type); this.grid_map.setCellType(c, r, state);
this.grid_map.setCellOwner(c, r, owner); this.grid_map.setCellOwner(c, r, owner);
} }
} }

View File

@@ -2,9 +2,9 @@ const Environment = require('./Environment');
const Organism = require('../Organism/Organism'); const Organism = require('../Organism/Organism');
const GridMap = require('../Grid/GridMap'); const GridMap = require('../Grid/GridMap');
const Renderer = require('../Rendering/Renderer'); const Renderer = require('../Rendering/Renderer');
const CellTypes = require('../Organism/Cell/CellTypes'); const CellStates = require('../Organism/Cell/CellStates');
const EditorController = require("../Controllers/EditorController"); const EditorController = require("../Controllers/EditorController");
const Cell = require("../Organism/Cell/Cell"); const Cell = require("../Organism/Cell/GridCell");
const Eye = require('../Organism/Perception/Eye'); const Eye = require('../Organism/Perception/Eye');
const Directions = require('../Organism/Directions'); const Directions = require('../Organism/Directions');
@@ -27,28 +27,27 @@ class OrganismEditor extends Environment{
} }
} }
changeCell(c, r, type, owner) { changeCell(c, r, state, owner) {
super.changeCell(c, r, type, owner); super.changeCell(c, r, state, owner);
this.renderFull();
}
renderFull() {
this.renderer.renderFullGrid(this.grid_map.grid); this.renderer.renderFullGrid(this.grid_map.grid);
} }
// absolute c r, not local // absolute c r, not local
addCellToOrg(c, r, type) { addCellToOrg(c, r, state) {
var center = this.grid_map.getCenter(); var center = this.grid_map.getCenter();
var loc_c = c - center[0]; var loc_c = c - center[0];
var loc_r = r - center[1]; var loc_r = r - center[1];
var prev_cell = this.organism.getLocalCell(loc_c, loc_r) var prev_cell = this.organism.getLocalCell(loc_c, loc_r)
if (prev_cell != null) { if (prev_cell != null) {
console.log(prev_cell.type) var new_cell = this.organism.replaceCell(state, prev_cell.loc_col, prev_cell.loc_row, false);
if (type == CellTypes.eye && prev_cell.type != CellTypes.eye){ this.changeCell(c, r, state, new_cell);
prev_cell.eye = new Eye(Directions.up);
}
prev_cell.type = type;
this.changeCell(c, r, type, this.organism);
} }
else if (this.organism.addCell(type, loc_c, loc_r)){ else if (this.organism.canAddCellAt(loc_c, loc_r)){
this.changeCell(c, r, type, this.organism); this.changeCell(c, r, state, this.organism.addDefaultCell(state, loc_c, loc_r));
} }
} }
@@ -63,13 +62,13 @@ class OrganismEditor extends Environment{
var prev_cell = this.organism.getLocalCell(loc_c, loc_r) var prev_cell = this.organism.getLocalCell(loc_c, loc_r)
if (prev_cell != null) { if (prev_cell != null) {
if (this.organism.removeCell(loc_c, loc_r)) { if (this.organism.removeCell(loc_c, loc_r)) {
this.changeCell(c, r, CellTypes.empty, null); this.changeCell(c, r, CellStates.empty, null);
} }
} }
} }
setOrganismToCopyOf(orig_org){ setOrganismToCopyOf(orig_org){
this.grid_map.fillGrid(CellTypes.empty); this.grid_map.fillGrid(CellStates.empty);
var center = this.grid_map.getCenter(); var center = this.grid_map.getCenter();
this.organism = new Organism(center[0], center[1], this, orig_org); this.organism = new Organism(center[0], center[1], this, orig_org);
this.organism.updateGrid(); this.organism.updateGrid();
@@ -82,10 +81,10 @@ class OrganismEditor extends Environment{
} }
clear() { clear() {
this.grid_map.fillGrid(CellTypes.empty); this.grid_map.fillGrid(CellStates.empty);
var center = this.grid_map.getCenter(); var center = this.grid_map.getCenter();
this.organism = new Organism(center[0], center[1], this, null); this.organism = new Organism(center[0], center[1], this, null);
this.organism.addCell(CellTypes.mouth, 0, 0); this.organism.addDefaultCell(CellStates.mouth, 0, 0);
this.organism.updateGrid(); this.organism.updateGrid();
} }
} }

View File

@@ -3,8 +3,8 @@ const Grid = require('../Grid/GridMap');
const Renderer = require('../Rendering/Renderer'); const Renderer = require('../Rendering/Renderer');
const GridMap = require('../Grid/GridMap'); const GridMap = require('../Grid/GridMap');
const Organism = require('../Organism/Organism'); const Organism = require('../Organism/Organism');
const CellTypes = require('../Organism/Cell/CellTypes'); const CellStates = require('../Organism/Cell/CellStates');
const Cell = require('../Organism/Cell/Cell'); const Cell = require('../Organism/Cell/GridCell');
const EnvironmentController = require('../Controllers/EnvironmentController'); const EnvironmentController = require('../Controllers/EnvironmentController');
class WorldEnvironment extends Environment{ class WorldEnvironment extends Environment{
@@ -54,12 +54,12 @@ class WorldEnvironment extends 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.eye, 0, 0); org.addDefaultCell(CellStates.eye, 0, 0);
// org.addCell(CellTypes.mouth, 1, 1); org.addDefaultCell(CellStates.mouth, 1, 1);
// org.addCell(CellTypes.mover, -1, -1); org.addDefaultCell(CellStates.mover, 1, -1);
org.addCell(CellTypes.mouth, 0, 0); // org.addDefaultCell(CellStates.mouth, 0, 0);
org.addCell(CellTypes.producer, 1, 1); // org.addDefaultCell(CellStates.producer, 1, 1);
org.addCell(CellTypes.producer, -1, -1); // org.addDefaultCell(CellStates.producer, -1, -1);
this.addOrganism(org); this.addOrganism(org);
} }
@@ -77,17 +77,17 @@ class WorldEnvironment extends Environment{
return this.total_mutability / this.organisms.length; return this.total_mutability / this.organisms.length;
} }
changeCell(c, r, type, owner) { changeCell(c, r, state, owner) {
super.changeCell(c, r, type, owner); super.changeCell(c, r, state, owner);
this.renderer.addToRender(this.grid_map.cellAt(c, r)); this.renderer.addToRender(this.grid_map.cellAt(c, r));
if(type == CellTypes.wall) if(state == CellStates.wall)
this.walls.push(this.grid_map.cellAt(c, r)); this.walls.push(this.grid_map.cellAt(c, r));
} }
clearWalls() { clearWalls() {
for(var wall of this.walls){ for(var wall of this.walls){
if (this.grid_map.cellAt(wall.col, wall.row).type == CellTypes.wall) if (this.grid_map.cellAt(wall.col, wall.row).state == CellStates.wall)
this.changeCell(wall.col, wall.row, CellTypes.empty, null); this.changeCell(wall.col, wall.row, CellStates.empty, null);
} }
} }
@@ -99,7 +99,7 @@ class WorldEnvironment extends Environment{
reset(clear_walls=true) { reset(clear_walls=true) {
this.organisms = []; this.organisms = [];
this.grid_map.fillGrid(CellTypes.empty); this.grid_map.fillGrid(CellStates.empty);
this.renderer.renderFullGrid(this.grid_map.grid); this.renderer.renderFullGrid(this.grid_map.grid);
this.total_mutability = 0; this.total_mutability = 0;
this.OriginOfLife(); this.OriginOfLife();

View File

@@ -1,5 +1,5 @@
const Cell = require('../Organism/Cell/Cell'); const Cell = require('../Organism/Cell/GridCell');
const CellTypes = require('../Organism/Cell/CellTypes'); const CellStates = require('../Organism/Cell/CellStates');
class GridMap { class GridMap {
constructor(cols, rows, cell_size) { constructor(cols, rows, cell_size) {
@@ -14,18 +14,19 @@ class GridMap {
for(var c=0; c<cols; c++) { for(var c=0; c<cols; c++) {
var row = []; var row = [];
for(var r=0; r<rows; r++) { for(var r=0; r<rows; r++) {
var cell = new Cell(CellTypes.empty, c, r, c*cell_size, r*cell_size); var cell = new Cell(CellStates.empty, c, r, c*cell_size, r*cell_size);
row.push(cell); row.push(cell);
} }
this.grid.push(row); this.grid.push(row);
} }
} }
fillGrid(type) { fillGrid(state) {
for (var col of this.grid) { for (var col of this.grid) {
for (var cell of col) { for (var cell of col) {
cell.setType(type); cell.setType(state);
cell.owner = null; cell.owner = null;
cell.cell_owner = null;
} }
} }
} }
@@ -37,18 +38,22 @@ class GridMap {
return this.grid[col][row]; return this.grid[col][row];
} }
setCellType(col, row, type) { setCellType(col, row, state) {
if (!this.isValidLoc(col, row)) { if (!this.isValidLoc(col, row)) {
return; return;
} }
this.grid[col][row].setType(type); this.grid[col][row].setType(state);
} }
setCellOwner(col, row, owner) { setCellOwner(col, row, cell_owner) {
if (!this.isValidLoc(col, row)) { if (!this.isValidLoc(col, row)) {
return; return;
} }
this.grid[col][row].owner = owner; this.grid[col][row].cell_owner = cell_owner;
if (cell_owner != null)
this.grid[col][row].owner = cell_owner.org;
else
this.grid[col][row].owner = null;
} }
isValidLoc(col, row){ isValidLoc(col, row){

View File

@@ -0,0 +1,10 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
class ArmorCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.armor, org, loc_col, loc_row);
}
}
module.exports = ArmorCell;

View File

@@ -0,0 +1,73 @@
const CellStates = require("../CellStates");
const Directions = require("../../Directions");
// A body cell defines the relative location of the cell in it's parent organism. It also defines their functional behavior.
class BodyCell{
constructor(state, org, loc_col, loc_row){
this.state = state;
this.org = org;
this.loc_col = loc_col;
this.loc_row = loc_row;
}
initInherit(parent) {
// deep copy parent values
this.loc_col = parent.loc_col;
this.loc_row = parent.loc_row;
}
initRandom() {
// initialize values randomly
}
initDefault() {
// initialize to default values
}
performFunction(env) {
// default behavior: none
}
getRealCol() {
return this.org.c + this.rotatedCol(this.org.rotation);
}
getRealRow() {
return this.org.r + this.rotatedRow(this.org.rotation);
}
getRealCell() {
var real_c = this.getRealCol();
var real_r = this.getRealRow();
return this.org.env.grid_map.cellAt(real_c, real_r);
}
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 = BodyCell;

View File

@@ -0,0 +1,42 @@
const MouthCell = require("./MouthCell");
const ProducerCell = require("./ProducerCell");
const MoverCell = require("./MoverCell");
const KillerCell = require("./KillerCell");
const ArmorCell = require("./ArmorCell");
const EyeCell = require("./EyeCell");
const CellStates = require("../CellStates");
const BodyCellFactory = {
init: function() {
var type_map = {};
type_map[CellStates.mouth.name] = MouthCell;
type_map[CellStates.producer.name] = ProducerCell;
type_map[CellStates.mover.name] = MoverCell;
type_map[CellStates.killer.name] = KillerCell;
type_map[CellStates.armor.name] = ArmorCell;
type_map[CellStates.eye.name] = EyeCell;
this.type_map = type_map;
},
createInherited: function(org, to_copy) {
var cell = new this.type_map[to_copy.state.name](org, to_copy.loc_col, to_copy.loc_row);
cell.initInherit(to_copy);
return cell;
},
createRandom: function(org, state, loc_col, loc_row) {
var cell = new this.type_map[state.name](org, loc_col, loc_row);
cell.initRandom();
return cell;
},
createDefault: function(org, state, loc_col, loc_row) {
var cell = new this.type_map[state.name](org, loc_col, loc_row);
cell.initDefault();
return cell;
},
}
BodyCellFactory.init();
module.exports = BodyCellFactory;

View File

@@ -0,0 +1,81 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
const Hyperparams = require("../../../Hyperparameters");
const Directions = require("../../Directions");
const Observation = require("../../Perception/Observation")
class EyeCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.eye, org, loc_col, loc_row);
this.org.has_eyes = true;
}
initInherit(parent) {
// deep copy parent values
super.initInherit(parent);
this.direction = parent.direction;
}
initRandom() {
// initialize values randomly
this.direction = Directions.getRandomDirection();
}
initDefault() {
// initialize to default values
this.direction = Directions.up;
}
getAbsoluteDirection() {
var dir = this.org.rotation + this.direction;
if (dir > 3)
dir -= 4;
return dir;
}
performFunction() {
var obs = this.look();
this.org.brain.observe(obs);
}
look() {
var env = this.org.env;
var direction = this.getAbsoluteDirection();
var addCol = 0;
var addRow = 0;
switch(direction) {
case Directions.up:
addRow = -1;
break;
case Directions.down:
addRow = 1;
break;
case Directions.right:
addCol = 1;
break;
case Directions.left:
addCol = -1;
break;
}
var start_col = this.getRealCol();
var start_row = this.getRealRow();
var col = start_col;
var row = start_row;
var cell = null;
for (var i=0; i<Hyperparams.lookRange; i++){
col+=addCol;
row+=addRow;
cell = env.grid_map.cellAt(col, row);
if (cell == null) {
break;
}
if (cell.state != CellStates.empty){
var distance = Math.abs(start_col-col) + Math.abs(start_row-row);
return new Observation(cell, distance, direction);
}
}
return new Observation(cell, Hyperparams.lookRange, direction);
}
}
module.exports = EyeCell;

View File

@@ -0,0 +1,32 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
const Hyperparams = require("../../../Hyperparameters");
class KillerCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.killer, org, loc_col, loc_row);
}
performFunction() {
var env = this.org.env;
var c = this.getRealCol();
var r = this.getRealRow();
for (var loc of Hyperparams.killableNeighbors) {
var cell = env.grid_map.cellAt(c+loc[0], r+loc[1]);
this.killNeighbor(cell);
}
}
killNeighbor(n_cell) {
// console.log(n_cell)
if(n_cell == null || n_cell.owner == null || n_cell.owner == this.org || !n_cell.owner.living || n_cell.state == CellStates.armor)
return;
var is_hit = n_cell.state == CellStates.killer; // has to be calculated before death
n_cell.owner.harm();
if (is_hit) {
this.org.harm();
}
}
}
module.exports = KillerCell;

View File

@@ -0,0 +1,30 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
const Hyperparams = require("../../../Hyperparameters");
class MouthCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.mouth, org, loc_col, loc_row);
}
performFunction() {
var env = this.org.env;
var real_c = this.getRealCol();
var real_r = this.getRealRow();
for (var loc of Hyperparams.edibleNeighbors){
var cell = env.grid_map.cellAt(real_c+loc[0], real_r+loc[1]);
this.eatNeighbor(cell, env);
}
}
eatNeighbor(n_cell, env) {
if (n_cell == null)
return;
if (n_cell.state == CellStates.food){
env.changeCell(n_cell.col, n_cell.row, CellStates.empty, null);
this.org.food_collected++;
}
}
}
module.exports = MouthCell;

View File

@@ -0,0 +1,11 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
class MoverCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.mover, org, loc_col, loc_row);
this.org.is_mover = true;
}
}
module.exports = MoverCell;

View File

@@ -0,0 +1,31 @@
const CellStates = require("../CellStates");
const BodyCell = require("./BodyCell");
const Hyperparams = require("../../../Hyperparameters");
class ProducerCell extends BodyCell{
constructor(org, loc_col, loc_row){
super(CellStates.producer, org, loc_col, loc_row);
this.org.is_producer = true;
}
performFunction() {
if (this.org.is_mover && !Hyperparams.moversCanProduce)
return;
var env = this.org.env;
var prob = Hyperparams.foodProdProb;
var real_c = this.getRealCol();
var real_r = this.getRealRow();
if (Math.random() * 100 <= prob) {
var loc = Hyperparams.growableNeighbors[Math.floor(Math.random() * Hyperparams.growableNeighbors.length)]
var loc_c=loc[0];
var loc_r=loc[1];
var cell = env.grid_map.cellAt(real_c+loc_c, real_r+loc_r);
if (cell != null && cell.state == CellStates.empty){
env.changeCell(real_c+loc_c, real_r+loc_r, CellStates.food, null);
return;
}
}
}
}
module.exports = ProducerCell;

View File

@@ -1,98 +0,0 @@
const CellTypes = require("./CellTypes");
const Hyperparams = require("../../Hyperparameters");
// A cell exists in a grid map.
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;
this.func = null;
}
setType(type) {
this.type = type;
switch(this.type){
case CellTypes.mouth:
this.func = eatFood;
break;
case CellTypes.producer:
this.func = growFood;
break;
case CellTypes.killer:
this.func = killNeighbors;
break;
default:
this.func = null;
}
}
performFunction(env) {
if (this.func == null) return;
this.func(this, env);
}
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 && !Hyperparams.moversCanProduce)
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,100 @@
// A cell state is used to differentiate type and render the cell
class CellState{
constructor(name, color) {
this.name = name;
this.color = color
}
render(ctx, cell, size) {
ctx.fillStyle = this.color;
ctx.fillRect(cell.x, cell.y, size, size);
}
}
class Empty extends CellState {
constructor() {
super('empty', '#121D29');
}
}
class Food extends CellState {
constructor() {
super('food', 'green');
}
}
class Wall extends CellState {
constructor() {
super('wall', 'gray');
}
}
class Mouth extends CellState {
constructor() {
super('mouth', 'orange');
}
}
class Producer extends CellState {
constructor() {
super('producer', 'white');
}
}
class Mover extends CellState {
constructor() {
super('mover', '#3493EB');
}
}
class Killer extends CellState {
constructor() {
super('killer', 'red');
}
}
class Armor extends CellState {
constructor() {
super('armor', 'purple');
}
}
class Eye extends CellState {
constructor() {
super('eye', '#d4bb3f');
this.slit_color = '#121D29';
}
render(ctx, cell, size) {
ctx.fillStyle = this.color;
ctx.fillRect(cell.x, cell.y, size, size);
if(size == 1)
return;
var half = size/2;
var x = -(size)/8
var y = -half
var h = size/2 + size/4;
var w = size/4;
ctx.translate(cell.x+half, cell.y+half);
ctx.rotate((cell.cell_owner.getAbsoluteDirection() * 90) * Math.PI / 180);
ctx.fillStyle = this.slit_color
ctx.fillRect(x, y, w, h);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
}
const CellStates = {
empty: new Empty(),
food: new Food(),
wall: new Wall(),
mouth: new Mouth(),
producer: new Producer(),
mover: new Mover(),
killer: new Killer(),
armor: new Armor(),
eye: new Eye(),
defineLists() {
this.all = [this.empty, this.food, this.wall, this.mouth, this.producer, this.mover, this.killer, this.armor, this.eye]
this.living = [this.mouth, this.producer, this.mover, this.killer, this.armor, this.eye];
},
getRandomName: function() {
return this.all[Math.floor(Math.random() * this.all.length)].name;
},
getRandomLivingType: function() {
return this.living[Math.floor(Math.random() * this.living.length)];
}
}
CellStates.defineLists();
module.exports = CellStates;

View File

@@ -0,0 +1,21 @@
const CellStates = require("./CellStates");
const Hyperparams = require("../../Hyperparameters");
// A cell exists in a grid map.
class Cell{
constructor(state, col, row, x, y){
this.owner = null; // owner organism
this.cell_owner = null; // owner cell of ^that organism
this.setType(state);
this.col = col;
this.row = row;
this.x = x;
this.y = y;
}
setType(state) {
this.state = state;
}
}
module.exports = Cell;

View File

@@ -1,15 +1,14 @@
const CellTypes = require("./CellTypes"); const CellStates = require("./CellStates");
const Directions = require("../Directions"); const Directions = require("../Directions");;
const Hyperparams = require("../../Hyperparameters");
const Eye = require("../Perception/Eye.js"); const Eye = require("../Perception/Eye.js");
// A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid // A body cell defines the relative location of the cell in it's parent organism. It also defines their functional behavior.
class LocalCell{ class LocalCell{
constructor(type, loc_col, loc_row, eye=null){ constructor(state, loc_col, loc_row, eye=null){
this.type = type; this.state = state;
this.loc_col = loc_col; this.loc_col = loc_col;
this.loc_row = loc_row; this.loc_row = loc_row;
if (this.type == CellTypes.eye){ if (this.state == CellStates.eye){
this.eye = new Eye(); this.eye = new Eye();
if (eye != null) { if (eye != null) {
this.eye.direction = eye.direction; this.eye.direction = eye.direction;
@@ -17,8 +16,6 @@ class LocalCell{
} }
} }
rotatedCol(dir){ rotatedCol(dir){
switch(dir){ switch(dir){
case Directions.up: case Directions.up:

View File

@@ -1,7 +1,9 @@
const CellTypes = require("./Cell/CellTypes"); // const CellTypes = require("./Cell/CellTypes");
const Cell = require("./Cell/Cell"); const CellStates = require("../Organism/Cell/CellStates");
const Cell = require("./Cell/GridCell");
const GridMap = require("../Grid/GridMap"); const GridMap = require("../Grid/GridMap");
const LocalCell = require("./Cell/LocalCell"); const LocalCell = require("./Cell/LocalCell");
const BodyCellFactory = require("./Cell/BodyCells/BodyCellFactory");
const Neighbors = require("../Grid/Neighbors"); const Neighbors = require("../Grid/Neighbors");
const Hyperparams = require("../Hyperparameters"); const Hyperparams = require("../Hyperparameters");
const Directions = require("./Directions"); const Directions = require("./Directions");
@@ -36,39 +38,54 @@ class Organism {
} }
} }
addCell(type, c, r, eye=null) { canAddCellAt(c, r) {
for (var cell of this.cells) { for (var cell of this.cells) {
if (cell.loc_col == c && cell.loc_row == r){ if (cell.loc_col == c && cell.loc_row == r){
return false; return false;
} }
} }
this.checkTypeChange(type);
this.cells.push(new LocalCell(type, c, r, eye));
return true; return true;
} }
removeCell(c, r) { addDefaultCell(state, c, r) {
if (c == 0 && r == 0) var new_cell = BodyCellFactory.createDefault(this, state, c, r);
this.cells.push(new_cell);
return new_cell;
}
addRandomizedCell(state, c, r) {
var new_cell = BodyCellFactory.createRandom(this, state, c, r);
this.cells.push(new_cell);
return new_cell;
}
addInheritCell(parent_cell) {
var new_cell = BodyCellFactory.createInherited(this, parent_cell);
this.cells.push(new_cell);
return new_cell;
}
replaceCell(state, c, r, randomize=true) {
this.removeCell(c, r, true);
if (randomize) {
return this.addRandomizedCell(state, c, r);
}
else {
return this.addDefaultCell(state, c, r);
}
}
removeCell(c, r, override_defense=false) {
if (c == 0 && r == 0 && !override_defense)
return false; return 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];
if (cell.loc_col == c && cell.loc_row == r){ 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); this.cells.splice(i, 1);
break; break;
} }
} }
if (check_change) { this.checkTypeChange(cell.state);
this.is_producer = false;
this.is_producer = false;
for (var cell of this.cells) {
this.checkTypeChange(cell.type);
}
}
return true; return true;
} }
@@ -81,13 +98,18 @@ class Organism {
return null; return null;
} }
checkTypeChange(type) { checkTypeChange() {
if (type == CellTypes.producer) this.is_producer = false;
this.is_producer = true; this.is_mover = false;
if (type == CellTypes.mover) this.has_eyes = false;
this.is_mover = true; for (var cell of this.cells) {
if (type == CellTypes.eye) if (cell.state == CellStates.producer)
this.has_eyes = true; this.is_producer = true;
if (cell.state == CellStates.mover)
this.is_mover = true;
if (cell.state == CellStates.eye)
this.has_eyes = true;
}
} }
inherit(parent) { inherit(parent) {
@@ -96,11 +118,7 @@ class Organism {
this.birth_distance = parent.birth_distance; this.birth_distance = parent.birth_distance;
for (var c of parent.cells){ for (var c of parent.cells){
//deep copy parent cells //deep copy parent cells
if (c.type == CellTypes.eye){ this.addInheritCell(c);
this.addCell(c.type, c.loc_col, c.loc_row, c.eye);
}
else
this.addCell(c.type, c.loc_col, c.loc_row);
} }
if(parent.is_mover) { if(parent.is_mover) {
for (var i in parent.brain.decisions) { for (var i in parent.brain.decisions) {
@@ -172,30 +190,32 @@ class Organism {
var mutated = false; var mutated = false;
if (choice <= Hyperparams.addProb) { if (choice <= Hyperparams.addProb) {
// add cell // add cell
var type = CellTypes.getRandomLivingType(); // console.log("add cell")
var num_to_add = Math.floor(Math.random() * 3) + 1;
var state = CellStates.getRandomLivingType();
var branch = this.cells[Math.floor(Math.random() * this.cells.length)]; var branch = this.cells[Math.floor(Math.random() * this.cells.length)];
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];
mutated = this.addCell(type, c, r); // mutated = this.addCell(state, c, r);
if (this.canAddCellAt(c, r)){
mutated = true;
this.addRandomizedCell(state, c, r);
}
this.birth_distance++; this.birth_distance++;
} }
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)];
if (cell.type == CellTypes.eye) { var state = CellStates.getRandomLivingType();
delete cell.eye; // console.log("change cell", state)
} this.replaceCell(state, cell.loc_col, cell.loc_row);
cell.type = CellTypes.getRandomLivingType();
if (cell.type == CellTypes.eye) {
cell.eye = new Eye();
}
this.checkTypeChange(cell.type);
mutated = 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
// console.log("remove cell")
if(this.cells.length > 1) { if(this.cells.length > 1) {
var cell = this.cells[Math.floor(Math.random() * this.cells.length)]; var cell = this.cells[Math.floor(Math.random() * this.cells.length)];
mutated = this.removeCell(cell.loc_col, cell.loc_row); mutated = this.removeCell(cell.loc_col, cell.loc_row);
@@ -229,7 +249,7 @@ class Organism {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.rotatedCol(this.rotation); var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.rotatedRow(this.rotation); 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, CellStates.empty, null);
} }
this.c = new_c; this.c = new_c;
this.r = new_r; this.r = new_r;
@@ -250,7 +270,7 @@ class Organism {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.rotatedCol(this.rotation); var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.rotatedRow(this.rotation); 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, CellStates.empty, null);
} }
this.rotation = new_rotation; this.rotation = new_rotation;
this.direction = Directions.getRandomDirection(); this.direction = Directions.getRandomDirection();
@@ -299,7 +319,7 @@ class Organism {
} }
isPassableCell(cell, parent){ isPassableCell(cell, parent){
return cell != null && (cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent || cell.type == CellTypes.food); return cell != null && (cell.state == CellStates.empty || cell.owner == this || cell.owner == parent || cell.state == CellStates.food);
} }
isClear(col, row, rotation=this.rotation) { isClear(col, row, rotation=this.rotation) {
@@ -308,7 +328,7 @@ class Organism {
if(cell==null) { if(cell==null) {
return false; return false;
} }
if (cell.owner==this || cell.type==CellTypes.empty || (!Hyperparams.foodBlocksReproduction && cell.type==CellTypes.food)){ if (cell.owner==this || cell.state==CellStates.empty || (!Hyperparams.foodBlocksReproduction && cell.state==CellStates.food)){
continue; continue;
} }
return false; return false;
@@ -327,7 +347,7 @@ class Organism {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.rotatedCol(this.rotation); var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.rotatedRow(this.rotation); 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, CellStates.food, null);
} }
this.living = false; this.living = false;
} }
@@ -336,10 +356,7 @@ class Organism {
for (var cell of this.cells) { for (var cell of this.cells) {
var real_c = this.c + cell.rotatedCol(this.rotation); var real_c = this.c + cell.rotatedCol(this.rotation);
var real_r = this.r + cell.rotatedRow(this.rotation); 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.state, cell);
if (cell.type == CellTypes.eye) {
this.getRealCell(cell).direction = cell.eye.getAbsoluteDirection(this.rotation);
}
} }
} }
@@ -353,16 +370,9 @@ class Organism {
this.reproduce(); this.reproduce();
} }
for (var cell of this.cells) { for (var cell of this.cells) {
if (cell.type == CellTypes.eye){ cell.performFunction();
var obs = cell.eye.look(this.getRealCol(cell), this.getRealRow(cell), this.rotation, this.env); if (!this.living)
this.brain.observe(obs); return this.living
}
else {
this.getRealCell(cell).performFunction(this.env);
if (!this.living){
return this.living
}
}
} }
if (this.is_mover) { if (this.is_mover) {

View File

@@ -1,6 +1,6 @@
const CellTypes = require("../Cell/CellTypes");
const Hyperparams = require("../../Hyperparameters"); const Hyperparams = require("../../Hyperparameters");
const Directions = require("../Directions"); const Directions = require("../Directions");
const CellStates = require("../Cell/CellStates");
const Decision = { const Decision = {
neutral: 0, neutral: 0,
@@ -17,17 +17,17 @@ class Brain {
this.observations = []; this.observations = [];
// corresponds to CellTypes // corresponds to CellTypes
this.decisions = [ this.decisions = [];
Decision.neutral, // empty this.decisions[CellStates.empty.name] = Decision.neutral;
Decision.chase, // food this.decisions[CellStates.food.name] = Decision.chase;
Decision.neutral, // wall this.decisions[CellStates.wall.name] = Decision.neutral;
Decision.neutral, // mouth this.decisions[CellStates.mouth.name] = Decision.neutral;
Decision.neutral, // producer this.decisions[CellStates.producer.name] = Decision.neutral;
Decision.neutral, // mover this.decisions[CellStates.mover.name] = Decision.neutral;
Decision.retreat, // killer this.decisions[CellStates.killer.name] = Decision.retreat;
Decision.neutral, // armor this.decisions[CellStates.armor.name] = Decision.neutral;
Decision.neutral, // eye this.decisions[CellStates.eye.name] = Decision.neutral;
];
} }
observe(observation) { observe(observation) {
@@ -43,7 +43,9 @@ class Brain {
continue; continue;
} }
if (obs.distance < closest) { if (obs.distance < closest) {
decision = this.decisions[obs.cell.type]; // console.log(obs.cell.state)
decision = this.decisions[obs.cell.state.name];
// console.log(decision)
move_direction = obs.direction; move_direction = obs.direction;
closest = obs.distance; closest = obs.distance;
} }
@@ -61,8 +63,8 @@ class Brain {
} }
mutate() { mutate() {
var selection = Math.floor(Math.random() * (this.decisions.length-1))+1; this.decisions[CellStates.getRandomName()] = Decision.getRandom();
this.decisions[selection] = Decision.getRandom(); this.decisions[CellStates.empty.name] = Decision.neutral; // if the empty cell has a decision it gets weird
} }
} }

View File

@@ -1,6 +1,6 @@
const Directions = require("../Directions"); const Directions = require("../Directions");
const Hyperparams = require("../../Hyperparameters"); const Hyperparams = require("../../Hyperparameters");
const CellTypes = require("../Cell/CellTypes"); const CellStates = require("../Cell/CellStates");
const Observation = require("./Observation") const Observation = require("./Observation")
class Eye { class Eye {
@@ -46,7 +46,7 @@ class Eye {
if (cell == null) { if (cell == null) {
break; break;
} }
if (cell.type != CellTypes.empty){ if (cell.state != CellStates.empty){
var distance = Math.abs(start_col-col) + Math.abs(start_row-row); var distance = Math.abs(start_col-col) + Math.abs(start_row-row);
return new Observation(cell, distance, direction); return new Observation(cell, distance, direction);
} }

View File

@@ -1,4 +1,5 @@
const CellTypes = require("../Organism/Cell/CellTypes"); // const CellTypes = require("../Organism/Cell/CellTypes");
const CellStates = require("../Organism/Cell/CellStates");
const Directions = require("../Organism/Directions"); const Directions = require("../Organism/Directions");
// Renderer controls access to a canvas. There is one renderer for each canvas // Renderer controls access to a canvas. There is one renderer for each canvas
@@ -47,36 +48,7 @@ class Renderer {
} }
renderCell(cell) { renderCell(cell) {
this.ctx.fillStyle = cell.getColor(); cell.state.render(this.ctx, cell, this.cell_size);
this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);
if (cell.type == CellTypes.eye) {
this.renderEyeCell(cell);
}
}
renderEyeCell(cell) {
if(this.cell_size == 1)
return;
if (this.cell_size % 2 == 0){
//even
var w = 2;
}
else{
//odd
var w = 1;
}
var halfInt = Math.ceil(this.cell_size/2);
var halfFloat = this.cell_size/2;
var h = this.cell_size/2 + this.cell_size/4;
var x = cell.x + h - Math.floor(w/2);
var y = cell.y;
this.ctx.translate(cell.x+halfFloat, cell.y+halfFloat);
this.ctx.rotate((cell.direction * 90) * Math.PI / 180);
this.ctx.fillStyle = '#121D29';
this.ctx.fillRect(-(this.cell_size)/8, -halfFloat, this.cell_size/4, h);
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
} }
renderOrganism(org) { renderOrganism(org) {