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

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 Directions = require("../Directions");
const Hyperparams = require("../../Hyperparameters");
const CellStates = require("./CellStates");
const Directions = require("../Directions");;
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{
constructor(type, loc_col, loc_row, eye=null){
this.type = type;
constructor(state, loc_col, loc_row, eye=null){
this.state = state;
this.loc_col = loc_col;
this.loc_row = loc_row;
if (this.type == CellTypes.eye){
if (this.state == CellStates.eye){
this.eye = new Eye();
if (eye != null) {
this.eye.direction = eye.direction;
@@ -17,8 +16,6 @@ class LocalCell{
}
}
rotatedCol(dir){
switch(dir){
case Directions.up: