diff --git a/dist/css/style.css b/dist/css/style.css
index f78c279..f98f464 100644
--- a/dist/css/style.css
+++ b/dist/css/style.css
@@ -9,7 +9,7 @@ body{
* {
margin: 0;
padding: 0;
- font-family: Arial, Helvetica, sans-serif;
+ font-family: Arial, Helvetica, sans-serif;
}
#env {
@@ -39,12 +39,12 @@ body{
background-color: #3a4b68;
display: grid;
grid-template-columns: repeat(3, 1fr);
- opacity: 0.8;
+ /* opacity: 0.8; */
}
-.control-panel:hover {
+/* .control-panel:hover {
opacity: 1;
-}
+} */
.control-set {
margin: 5px;
@@ -226,6 +226,12 @@ button:hover{
margin: 5px;
}
+#chartContainer {
+ height: 200px;
+ width: 100%;
+}
+
+
.hot-controls {
position: fixed;
bottom: 10px;
@@ -233,6 +239,16 @@ button:hover{
display: none;
}
+#headless-notification {
+ display: none;
+ position: fixed;
+ color: white;
+ font-size: 200px;
+ left: 0;
+ top: 0;
+ padding-left: 10px;
+}
+
#minimize {
margin: 10px;
float: right;
diff --git a/dist/index.html b/dist/index.html
index e581c6f..56477dd 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -7,6 +7,7 @@
+
@@ -28,6 +29,7 @@
Simulation Speed
+
Target FPS: 60
@@ -50,10 +52,6 @@
-
+
@@ -220,13 +222,24 @@
-
Stats
-
Organism count:
-
Highest count:
+
Statistics
+
Total Population:
+
Number of Species:
+
Largest Organism Ever:
Average Mutation Rate:
-
Largest Organism:
+
+
+
+
+
+
-
@@ -254,7 +267,10 @@
-
+
+
+
+
diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js
index e283874..ab5eaa5 100644
--- a/src/Controllers/ControlPanel.js
+++ b/src/Controllers/ControlPanel.js
@@ -1,5 +1,6 @@
const Hyperparams = require("../Hyperparameters");
const Modes = require("./ControlModes");
+const StatsPanel = require("../Stats/StatsPanel");
class ControlPanel {
constructor(engine) {
@@ -17,6 +18,10 @@ class ControlPanel {
this.editor_controller = this.engine.organism_editor.controller;
this.env_controller.setControlPanel(this);
this.editor_controller.setControlPanel(this);
+ this.stats_panel = new StatsPanel(this.engine.env);
+ this.headless_opacity = 1;
+ this.opacity_change_rate = -0.8;
+ this.paused=false;
}
defineMinMaxControls(){
@@ -26,11 +31,15 @@ class ControlPanel {
$('.control-panel').css('display', 'none');
$('.hot-controls').css('display', 'block');
this.control_panel_active = false;
+ this.stats_panel.stopAutoRender();
});
$('#maximize').click ( () => {
$('.control-panel').css('display', 'grid');
$('.hot-controls').css('display', 'none');
this.control_panel_active = true;
+ if (this.tab_id == 'stats') {
+ this.stats_panel.startAutoRender();
+ }
});
const V_KEY = 118;
$('body').keypress( (e) => {
@@ -38,6 +47,9 @@ class ControlPanel {
if (this.no_hud) {
let control_panel_display = this.control_panel_active ? 'grid' : 'none';
let hot_control_display = !this.control_panel_active ? 'block' : 'none';
+ if (this.control_panel_active && this.tab_id == 'stats') {
+ this.stats_panel.startAutoRender();
+ };
$('.control-panel').css('display', control_panel_display);
$('.hot-controls').css('display', hot_control_display);
}
@@ -48,6 +60,19 @@ class ControlPanel {
this.no_hud = !this.no_hud;
}
});
+ // var self = this;
+ // $('#minimize').click ( function() {
+ // $('.control-panel').css('display', 'none');
+ // $('.hot-controls').css('display', 'block');
+
+ // }.bind(this));
+ // $('#maximize').click ( function() {
+ // $('.control-panel').css('display', 'grid');
+ // $('.hot-controls').css('display', 'none');
+ // if (self.tab_id == 'stats') {
+ // self.stats_panel.startAutoRender();
+ // }
+ // });
}
defineEngineSpeedControls(){
@@ -56,13 +81,13 @@ class ControlPanel {
this.fps = this.slider.value
if (this.engine.running) {
this.changeEngineSpeed(this.fps);
-
}
$('#fps').text("Target FPS: "+this.fps);
}.bind(this);
$('.pause-button').click(function() {
$('.pause-button').find("i").toggleClass("fa fa-pause");
$('.pause-button').find("i").toggleClass("fa fa-play");
+ this.paused = !this.paused;
if (this.engine.running) {
this.engine.stop();
}
@@ -70,6 +95,18 @@ class ControlPanel {
this.engine.start(this.fps);
}
}.bind(this));
+ $('.headless').click(function() {
+ $('.headless').find("i").toggleClass("fa fa-eye");
+ $('.headless').find("i").toggleClass("fa fa-eye-slash");
+ if (Hyperparams.headless){
+ $('#headless-notification').css('display', 'none');
+ this.engine.env.renderFull();
+ }
+ else {
+ $('#headless-notification').css('display', 'block');
+ }
+ Hyperparams.headless = !Hyperparams.headless;
+ }.bind(this));
}
defineGridSizeControls() {
@@ -91,24 +128,31 @@ class ControlPanel {
var rows = $('#row-input').val();
this.engine.env.resizeGridColRow(cell_size, cols, rows);
}
+ this.engine.env.reset();
+ this.stats_panel.reset();
}.bind(this));
}
defineTabNavigation() {
+ this.tab_id = 'about';
var self = this;
$('.tabnav-item').click(function() {
$('.tab').css('display', 'none');
var tab = '#'+this.id+'.tab';
- self.engine.organism_editor.is_active = (this.id == 'editor');
$(tab).css('display', 'grid');
+ self.engine.organism_editor.is_active = (this.id == 'editor');
+ self.stats_panel.stopAutoRender();
+ if (this.id == 'stats') {
+ self.stats_panel.startAutoRender();
+ }
+ self.tab_id = this.id;
});
}
defineHyperparameterControls() {
$('#food-prod-prob').change(function() {
- var food_prob =
- Hyperparams.foodProdProb = $('#food-prod-prob').val();;
+ Hyperparams.foodProdProb = $('#food-prod-prob').val();
}.bind(this));
$('#lifespan-multiplier').change(function() {
Hyperparams.lifespanMultiplier = $('#lifespan-multiplier').val();
@@ -142,7 +186,7 @@ class ControlPanel {
Hyperparams.useGlobalMutability = !this.checked;
});
$('#global-mutation').change( function() {
- Hyperparams.globalMutability = $('#global-mutation').val();
+ Hyperparams.globalMutability = parseInt($('#global-mutation').val());
});
$('.mut-prob').change( function() {
switch(this.id){
@@ -182,6 +226,9 @@ class ControlPanel {
$('#remove-prob').val(Hyperparams.removeProb);
$('#movers-produce').prop('checked', Hyperparams.moversCanProduce);
$('#food-blocks').prop('checked', Hyperparams.foodBlocksReproduction);
+ $('#food-drop-rate').val(Hyperparams.foodDropProb);
+ $('#look-range').val(Hyperparams.lookRange);
+
if (!Hyperparams.useGlobalMutability) {
$('.global-mutation-in').css('display', 'none');
$('#avg-mut').css('display', 'block');
@@ -199,7 +246,7 @@ class ControlPanel {
$('#cell-selections').css('display', 'none');
$('#organism-options').css('display', 'none');
self.editor_controller.setDetailsPanel();
- switch(this.id){
+ switch(this.id) {
case "food-drop":
self.setMode(Modes.FoodDrop);
break;
@@ -219,6 +266,9 @@ class ControlPanel {
case "drop-org":
self.setMode(Modes.Clone);
self.env_controller.org_to_clone = self.engine.organism_editor.getCopyOfOrg();
+ self.env_controller.add_new_species = self.editor_controller.new_species;
+ self.editor_controller.new_species = false;
+ // console.log(self.env_controller.add_new_species)
break;
case "drag-view":
self.setMode(Modes.Drag);
@@ -234,6 +284,7 @@ class ControlPanel {
var env = this.engine.env;
$('#reset-env').click( function() {
this.engine.env.reset();
+ this.stats_panel.reset();
}.bind(this));
$('#auto-reset').change(function() {
env.auto_reset = this.checked;
@@ -273,16 +324,29 @@ class ControlPanel {
this.fps = this.engine.fps;
}
- update() {
+ updateHeadlessIcon(delta_time) {
+ if (this.paused)
+ return;
+ var op = this.headless_opacity + (this.opacity_change_rate*delta_time/1000);
+ if (op <= 0.4){
+ op=0.4;
+ this.opacity_change_rate = -this.opacity_change_rate;
+ }
+ else if (op >= 1){
+ op=1;
+ this.opacity_change_rate = -this.opacity_change_rate;
+ }
+ this.headless_opacity = op;
+ $('#headless-notification').css('opacity',(op*100)+'%');
+ }
+
+ update(delta_time) {
$('#fps-actual').text("Actual FPS: " + Math.floor(this.engine.actual_fps));
- var org_count = this.engine.env.organisms.length;
- $('#org-count').text("Organism count: " + org_count);
- if (org_count > this.organism_record)
- this.organism_record = org_count;
- $('#org-record').text("Highest count: " + this.organism_record);
- $('#avg-mut').text("Average Mutation Rate: " + Math.round(this.engine.env.averageMutability() * 100) / 100);
- $('#largest-org').text("Largest Organism: " + this.engine.env.largest_cell_count + " cells");
$('#reset-count').text("Auto reset count: " + this.engine.env.reset_count);
+ this.stats_panel.updateDetails();
+ if (Hyperparams.headless)
+ this.updateHeadlessIcon(delta_time);
+
}
}
diff --git a/src/Controllers/EditorController.js b/src/Controllers/EditorController.js
index 5b55c6d..e7c2152 100644
--- a/src/Controllers/EditorController.js
+++ b/src/Controllers/EditorController.js
@@ -10,6 +10,7 @@ class EditorController extends CanvasController{
this.mode = Modes.None;
this.edit_cell_type = null;
this.highlight_org = false;
+ this.new_species = false;
this.defineCellTypeSelection();
this.defineEditorDetails();
}
@@ -26,7 +27,7 @@ class EditorController extends CanvasController{
mouseUp(){}
getCurLocalCell(){
- return this.env.organism.getLocalCell(this.mouse_c-this.env.organism.c, this.mouse_r-this.env.organism.r);
+ return this.env.organism.anatomy.getLocalCell(this.mouse_c-this.env.organism.c, this.mouse_r-this.env.organism.r);
}
editOrganism() {
@@ -41,15 +42,17 @@ class EditorController extends CanvasController{
else
this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type);
}
- if (this.right_click)
+ else if (this.right_click)
this.env.removeCellFromOrg(this.mouse_c, this.mouse_r);
+
+ this.new_species = true;
this.setBrainPanelVisibility();
this.setMoveRangeVisibility();
this.updateDetails();
}
updateDetails() {
- $('.cell-count').text("Cell count: "+this.env.organism.cells.length);
+ $('.cell-count').text("Cell count: "+this.env.organism.anatomy.cells.length);
}
defineCellTypeSelection() {
@@ -111,7 +114,7 @@ class EditorController extends CanvasController{
this.clearDetailsPanel();
var org = this.env.organism;
- $('.cell-count').text("Cell count: "+org.cells.length);
+ $('.cell-count').text("Cell count: "+org.anatomy.cells.length);
$('#move-range').text("Move Range: "+org.move_range);
$('#mutation-rate').text("Mutation Rate: "+org.mutability);
if (Hyperparams.useGlobalMutability) {
@@ -134,7 +137,7 @@ class EditorController extends CanvasController{
this.clearDetailsPanel();
var org = this.env.organism;
- $('.cell-count').text("Cell count: "+org.cells.length);
+ $('.cell-count').text("Cell count: "+org.anatomy.cells.length);
if (this.setMoveRangeVisibility()){
$('#move-range-edit').val(org.move_range);
}
@@ -149,7 +152,7 @@ class EditorController extends CanvasController{
setBrainPanelVisibility() {
var org = this.env.organism;
- if (org.has_eyes && org.is_mover) {
+ if (org.anatomy.has_eyes && org.anatomy.is_mover) {
$('.brain-details').css('display', 'block');
return true;
}
@@ -175,7 +178,7 @@ class EditorController extends CanvasController{
setMoveRangeVisibility() {
var org = this.env.organism;
- if (org.is_mover) {
+ if (org.anatomy.is_mover) {
$('#move-range-cont').css('display', 'block');
$('#move-range').css('display', 'block');
return true;
diff --git a/src/Controllers/EnvironmentController.js b/src/Controllers/EnvironmentController.js
index ec4dbae..451d2a9 100644
--- a/src/Controllers/EnvironmentController.js
+++ b/src/Controllers/EnvironmentController.js
@@ -3,12 +3,15 @@ const Organism = require('../Organism/Organism');
const Modes = require("./ControlModes");
const CellStates = require("../Organism/Cell/CellStates");
const Neighbors = require("../Grid/Neighbors");
+const FossilRecord = require("../Stats/FossilRecord");
+const Hyperparams = require("../Hyperparameters");
class EnvironmentController extends CanvasController{
constructor(env, canvas) {
super(env, canvas);
this.mode = Modes.Drag;
this.org_to_clone = null;
+ this.add_new_species = false;
this.defineZoomControls();
this.scale = 1;
}
@@ -76,6 +79,8 @@ class EnvironmentController extends CanvasController{
}
performModeAction() {
+ if (Hyperparams.headless)
+ return;
var mode = this.mode;
var right_click = this.right_click;
var left_click = this.left_click;
@@ -118,8 +123,19 @@ class EnvironmentController extends CanvasController{
case Modes.Clone:
if (this.org_to_clone != null){
var new_org = new Organism(this.mouse_c, this.mouse_r, this.env, this.org_to_clone);
+ if (this.add_new_species){
+ FossilRecord.addSpeciesObj(new_org.species);
+ new_org.species.start_tick = this.env.total_ticks;
+ this.add_new_species = false;
+ new_org.species.population = 0;
+ }
+ else if (this.org_to_clone.species.extinct){
+ FossilRecord.resurrect(this.org_to_clone.species);
+ }
+
if (new_org.isClear(this.mouse_c, this.mouse_r)){
- this.env.addOrganism(new_org)
+ this.env.addOrganism(new_org);
+ new_org.species.addPop();
}
}
break;
diff --git a/src/Engine.js b/src/Engine.js
index 7143629..a74f693 100644
--- a/src/Engine.js
+++ b/src/Engine.js
@@ -5,7 +5,7 @@ const ColorScheme = require('./Rendering/ColorScheme');
const render_speed = 60;
-class Engine{
+class Engine {
constructor(){
this.fps = 60;
this.env = new WorldEnvironment(5);
@@ -23,10 +23,8 @@ class Engine{
start(fps=60) {
if (fps <= 0)
fps = 1;
- if (fps > 300)
- fps = 300;
this.fps = fps;
- this.game_loop = setInterval(function(){this.environmentUpdate();}.bind(this), 1000/fps);
+ this.game_loop = setInterval(function(){this.updateDeltaTime();this.environmentUpdate();}.bind(this), 1000/fps);
this.running = true;
if (this.fps >= render_speed) {
if (this.render_loop != null) {
@@ -46,14 +44,17 @@ class Engine{
setRenderLoop() {
if (this.render_loop == null) {
- this.render_loop = setInterval(function(){this.necessaryUpdate();}.bind(this), 1000/render_speed);
+ this.render_loop = setInterval(function(){this.updateDeltaTime();this.necessaryUpdate();}.bind(this), 1000/render_speed);
}
}
+ updateDeltaTime() {
+ this.delta_time = Date.now() - this.last_update;
+ this.last_update = Date.now();
+ }
+
environmentUpdate() {
- this.delta_time = Date.now() - this.last_update;
- this.last_update = Date.now();
this.env.update(this.delta_time);
this.actual_fps = 1/this.delta_time*1000;
if(this.render_loop == null){
@@ -64,7 +65,7 @@ class Engine{
necessaryUpdate() {
this.env.render();
- this.controlpanel.update();
+ this.controlpanel.update(this.delta_time);
this.organism_editor.update();
}
diff --git a/src/Environments/OrganismEditor.js b/src/Environments/OrganismEditor.js
index 9294549..b969774 100644
--- a/src/Environments/OrganismEditor.js
+++ b/src/Environments/OrganismEditor.js
@@ -4,7 +4,7 @@ const GridMap = require('../Grid/GridMap');
const Renderer = require('../Rendering/Renderer');
const CellStates = require('../Organism/Cell/CellStates');
const EditorController = require("../Controllers/EditorController");
-const Directions = require('../Organism/Directions');
+const Species = require('../Stats/Species');
class OrganismEditor extends Environment{
constructor() {
@@ -37,14 +37,15 @@ class OrganismEditor extends Environment{
var center = this.grid_map.getCenter();
var loc_c = c - center[0];
var loc_r = r - center[1];
- var prev_cell = this.organism.getLocalCell(loc_c, loc_r)
+ var prev_cell = this.organism.anatomy.getLocalCell(loc_c, loc_r)
if (prev_cell != null) {
- var new_cell = this.organism.replaceCell(state, prev_cell.loc_col, prev_cell.loc_row, false);
+ var new_cell = this.organism.anatomy.replaceCell(state, prev_cell.loc_col, prev_cell.loc_row, false);
this.changeCell(c, r, state, new_cell);
}
- else if (this.organism.canAddCellAt(loc_c, loc_r)){
- this.changeCell(c, r, state, this.organism.addDefaultCell(state, loc_c, loc_r));
+ else if (this.organism.anatomy.canAddCellAt(loc_c, loc_r)){
+ this.changeCell(c, r, state, this.organism.anatomy.addDefaultCell(state, loc_c, loc_r));
}
+ this.organism.species = new Species(this.organism.anatomy, null, 0);
}
removeCellFromOrg(c, r) {
@@ -55,20 +56,22 @@ class OrganismEditor extends Environment{
alert("Cannot remove center cell");
return;
}
- var prev_cell = this.organism.getLocalCell(loc_c, loc_r)
+ var prev_cell = this.organism.anatomy.getLocalCell(loc_c, loc_r)
if (prev_cell != null) {
- if (this.organism.removeCell(loc_c, loc_r)) {
+ if (this.organism.anatomy.removeCell(loc_c, loc_r)) {
this.changeCell(c, r, CellStates.empty, null);
+ this.organism.species = new Species(this.organism.anatomy, null, 0);
}
}
}
- setOrganismToCopyOf(orig_org){
+ setOrganismToCopyOf(orig_org) {
this.grid_map.fillGrid(CellStates.empty);
var center = this.grid_map.getCenter();
this.organism = new Organism(center[0], center[1], this, orig_org);
this.organism.updateGrid();
this.controller.updateDetails();
+ this.controller.new_species = false;
}
getCopyOfOrg() {
@@ -80,8 +83,9 @@ class OrganismEditor extends Environment{
this.grid_map.fillGrid(CellStates.empty);
var center = this.grid_map.getCenter();
this.organism = new Organism(center[0], center[1], this, null);
- this.organism.addDefaultCell(CellStates.mouth, 0, 0);
+ this.organism.anatomy.addDefaultCell(CellStates.mouth, 0, 0);
this.organism.updateGrid();
+ this.organism.species = new Species(this.organism.anatomy, null, 0);
}
}
diff --git a/src/Environments/WorldEnvironment.js b/src/Environments/WorldEnvironment.js
index 26a73c1..da7bb40 100644
--- a/src/Environments/WorldEnvironment.js
+++ b/src/Environments/WorldEnvironment.js
@@ -5,7 +5,7 @@ const Organism = require('../Organism/Organism');
const CellStates = require('../Organism/Cell/CellStates');
const EnvironmentController = require('../Controllers/EnvironmentController');
const Hyperparams = require('../Hyperparameters.js');
-const Cell = require('../Organism/Cell/GridCell');
+const FossilRecord = require('../Stats/FossilRecord');
class WorldEnvironment extends Environment{
constructor(cell_size) {
@@ -21,6 +21,9 @@ class WorldEnvironment extends Environment{
this.auto_reset = true;
this.largest_cell_count = 0;
this.reset_count = 0;
+ this.total_ticks = 0;
+ this.data_update_rate = 100;
+ FossilRecord.setEnv(this);
}
update() {
@@ -35,13 +38,25 @@ class WorldEnvironment extends Environment{
this.generateFood();
}
this.removeOrganisms(to_remove);
+ this.total_ticks ++;
+ if (this.total_ticks % this.data_update_rate == 0) {
+ FossilRecord.updateData();
+ }
}
render() {
+ if (Hyperparams.headless) {
+ this.renderer.cells_to_render.clear();
+ return;
+ }
this.renderer.renderCells();
this.renderer.renderHighlights();
}
+ renderFull() {
+ this.renderer.renderFullGrid(this.grid_map.grid);
+ }
+
removeOrganisms(org_indeces) {
for (var i of org_indeces.reverse()){
this.total_mutability -= this.organisms[i].mutability;
@@ -56,26 +71,27 @@ class WorldEnvironment extends Environment{
OriginOfLife() {
var center = this.grid_map.getCenter();
var org = new Organism(center[0], center[1], this);
- // org.addDefaultCell(CellStates.eye, 0, 0);
- // org.addDefaultCell(CellStates.mouth, 1, 1);
- // org.addDefaultCell(CellStates.mover, 1, -1);
- org.addDefaultCell(CellStates.mouth, 0, 0);
- org.addDefaultCell(CellStates.producer, 1, 1);
- org.addDefaultCell(CellStates.producer, -1, -1);
+ org.anatomy.addDefaultCell(CellStates.mouth, 0, 0);
+ org.anatomy.addDefaultCell(CellStates.producer, 1, 1);
+ org.anatomy.addDefaultCell(CellStates.producer, -1, -1);
this.addOrganism(org);
+ FossilRecord.addSpecies(org, null);
}
addOrganism(organism) {
organism.updateGrid();
this.total_mutability += organism.mutability;
this.organisms.push(organism);
- if (organism.cells.length > this.largest_cell_count)
- this.largest_cell_count = organism.cells.length;
+ if (organism.anatomy.cells.length > this.largest_cell_count)
+ this.largest_cell_count = organism.anatomy.cells.length;
}
averageMutability() {
if (this.organisms.length < 1)
return 0;
+ if (Hyperparams.useGlobalMutability) {
+ return Hyperparams.globalMutability;
+ }
return this.total_mutability / this.organisms.length;
}
@@ -114,11 +130,13 @@ class WorldEnvironment extends Environment{
}
}
- reset(clear_walls=true) {
+ reset() {
this.organisms = [];
this.grid_map.fillGrid(CellStates.empty);
this.renderer.renderFullGrid(this.grid_map.grid);
this.total_mutability = 0;
+ this.total_ticks = 0;
+ FossilRecord.clear_record();
this.OriginOfLife();
}
@@ -126,7 +144,6 @@ class WorldEnvironment extends Environment{
this.renderer.cell_size = cell_size;
this.renderer.fillShape(rows*cell_size, cols*cell_size);
this.grid_map.resize(cols, rows, cell_size);
- this.reset();
}
resizeFillWindow(cell_size) {
@@ -135,7 +152,6 @@ class WorldEnvironment extends Environment{
var cols = Math.ceil(this.renderer.width / cell_size);
var rows = Math.ceil(this.renderer.height / cell_size);
this.grid_map.resize(cols, rows, cell_size);
- this.reset();
}
}
diff --git a/src/Hyperparameters.js b/src/Hyperparameters.js
index c5c7be1..d7c34ed 100644
--- a/src/Hyperparameters.js
+++ b/src/Hyperparameters.js
@@ -2,6 +2,8 @@ const Neighbors = require("./Grid/Neighbors");
const Hyperparams = {
setDefaults: function() {
+ this.headless = false;
+
this.lifespanMultiplier = 100;
this.foodProdProb = 4;
this.foodProdProbScalar = 4;
diff --git a/src/Organism/Anatomy.js b/src/Organism/Anatomy.js
new file mode 100644
index 0000000..8175024
--- /dev/null
+++ b/src/Organism/Anatomy.js
@@ -0,0 +1,96 @@
+const CellStates = require("./Cell/CellStates");
+const BodyCellFactory = require("./Cell/BodyCells/BodyCellFactory");
+
+class Anatomy {
+ constructor(owner) {
+ this.owner = owner;
+ this.cells = [];
+ this.is_producer = false;
+ this.is_mover = false;
+ this.has_eyes = false;
+ this.birth_distance = 4;
+ }
+
+ canAddCellAt(c, r) {
+ for (var cell of this.cells) {
+ if (cell.loc_col == c && cell.loc_row == r){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ addDefaultCell(state, c, r) {
+ var new_cell = BodyCellFactory.createDefault(this.owner, state, c, r);
+ this.cells.push(new_cell);
+ return new_cell;
+ }
+
+ addRandomizedCell(state, c, r) {
+ if (state==CellStates.eye && !this.has_eyes) {
+ this.owner.brain.randomizeDecisions();
+ }
+ var new_cell = BodyCellFactory.createRandom(this.owner, state, c, r);
+ this.cells.push(new_cell);
+ return new_cell;
+ }
+
+ addInheritCell(parent_cell) {
+ var new_cell = BodyCellFactory.createInherited(this.owner, 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, allow_center_removal=false) {
+ if (c == 0 && r == 0 && !allow_center_removal)
+ return false;
+ for (var i=0; i 1) {
- var cell = this.cells[Math.floor(Math.random() * this.cells.length)];
- mutated = this.removeCell(cell.loc_col, cell.loc_row);
+ if(this.anatomy.cells.length > 1) {
+ var cell = this.anatomy.getRandomCell();
+ mutated = this.anatomy.removeCell(cell.loc_col, cell.loc_row);
}
}
return mutated;
@@ -237,7 +166,7 @@ class Organism {
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) {
+ 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);
@@ -258,7 +187,7 @@ class Organism {
}
var new_rotation = Directions.getRandomDirection();
if(this.isClear(this.c, this.r, new_rotation)){
- for (var cell of this.cells) {
+ 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);
@@ -277,7 +206,7 @@ class Organism {
this.move_count = 0;
}
- // assumes either c1==c2 or r1==r2, returns true if there is a clear path from point a to b
+ // 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){
@@ -314,11 +243,12 @@ class Organism {
}
isClear(col, row, rotation=this.rotation, ignore_armor=false) {
- for(var loccell of this.cells) {
+ for(var loccell of this.anatomy.cells) {
var cell = this.getRealCell(loccell, col, row, rotation);
- if(cell==null) {
+ if (cell==null) {
return false;
}
+ // console.log(cell.owner == this)
if (cell.owner==this || cell.state==CellStates.empty || (!Hyperparams.foodBlocksReproduction && cell.state==CellStates.food) || (ignore_armor && loccell.state==CellStates.armor && cell.state==CellStates.food)){
continue;
}
@@ -335,16 +265,17 @@ class Organism {
}
die() {
- for (var cell of this.cells) {
+ 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.cells) {
+ 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);
@@ -360,13 +291,13 @@ class Organism {
if (this.food_collected >= this.foodNeeded()) {
this.reproduce();
}
- for (var cell of this.cells) {
+ for (var cell of this.anatomy.cells) {
cell.performFunction();
if (!this.living)
return this.living
}
- if (this.is_mover) {
+ if (this.anatomy.is_mover) {
this.move_count++;
var changed_dir = false;
if (this.ignore_brain_for == 0){
diff --git a/src/Rendering/Renderer.js b/src/Rendering/Renderer.js
index e60e457..647a9a8 100644
--- a/src/Rendering/Renderer.js
+++ b/src/Rendering/Renderer.js
@@ -52,7 +52,7 @@ class Renderer {
}
renderOrganism(org) {
- for(var org_cell of org.cells) {
+ for(var org_cell of org.anatomy.cells) {
var cell = org.getRealCell(org_cell);
this.renderCell(cell);
}
@@ -75,7 +75,7 @@ class Renderer {
}
highlightOrganism(org) {
- for(var org_cell of org.cells) {
+ for(var org_cell of org.anatomy.cells) {
var cell = org.getRealCell(org_cell);
this.cells_to_highlight.add(cell);
}
diff --git a/src/Stats/Charts/CellsChart.js b/src/Stats/Charts/CellsChart.js
new file mode 100644
index 0000000..9a10c5b
--- /dev/null
+++ b/src/Stats/Charts/CellsChart.js
@@ -0,0 +1,55 @@
+const CellStates = require("../../Organism/Cell/CellStates");
+const FossilRecord = require("../FossilRecord");
+const ChartController = require("./ChartController");
+
+class CellsChart extends ChartController {
+ constructor() {
+ super("Organism Size / Composition",
+ "Avg. Number of Cells per Organism",
+ "Note: to maintain efficiency, species with very small populations are discarded when collecting cell statistics.");
+ }
+
+ setData() {
+ this.clear();
+ //this.mouth, this.producer, this.mover, this.killer, this.armor, this.eye
+ this.data.push({
+ type: "line",
+ markerType: "none",
+ color: 'black',
+ showInLegend: true,
+ name: "pop1",
+ legendText: "Avg. organism size",
+ dataPoints: []
+ }
+ );
+ for (var c of CellStates.living) {
+ this.data.push({
+ type: "line",
+ markerType: "none",
+ color: c.color,
+ showInLegend: true,
+ name: c.name,
+ legendText: "Avg. " + c.name + " cells",
+ dataPoints: []
+ }
+ );
+ }
+ this.addAllDataPoints();
+
+
+ }
+
+ addDataPoint(i) {
+ var t = FossilRecord.tick_record[i];
+ var p = FossilRecord.av_cells[i];
+ this.data[0].dataPoints.push({x:t, y:p});
+ var j=1;
+ for (var name in FossilRecord.av_cell_counts[i]) {
+ var count = FossilRecord.av_cell_counts[i][name];
+ this.data[j].dataPoints.push({x:t,y:count})
+ j++;
+ }
+ }
+}
+
+module.exports = CellsChart;
\ No newline at end of file
diff --git a/src/Stats/Charts/ChartController.js b/src/Stats/Charts/ChartController.js
new file mode 100644
index 0000000..999fdf4
--- /dev/null
+++ b/src/Stats/Charts/ChartController.js
@@ -0,0 +1,84 @@
+const FossilRecord = require("../FossilRecord");
+
+class ChartController {
+ constructor(title, y_axis="", note="") {
+ this.data = [];
+ this.chart = new CanvasJS.Chart("chartContainer", {
+ zoomEnabled: true,
+ title:{
+ text: title
+ },
+ axisX:{
+ title: "Ticks",
+ minimum: 0,
+ },
+ axisY:{
+ title: y_axis,
+ minimum: 0,
+ },
+ data: this.data
+ });
+ this.chart.render();
+ $('#chart-note').text(note);
+ }
+
+ setData() {
+ alert("Must override updateData!");
+ }
+
+ setMinimum() {
+ var min = 0;
+ if (this.data[0].dataPoints != [])
+ min = this.data[0].dataPoints[0].x;
+ this.chart.options.axisX.minimum = min;
+ }
+
+ addAllDataPoints(){
+ for (var i in FossilRecord.tick_record) {
+ this.addDataPoint(i)
+ }
+ }
+
+ render() {
+ this.setMinimum();
+ this.chart.render();
+ }
+
+ updateData() {
+ var r_len = FossilRecord.tick_record.length;
+ var newest_t = -1;
+ var oldest_t = 0;
+ if (this.data[0].dataPoints.length>0) {
+ newest_t = this.data[0].dataPoints[this.data[0].dataPoints.length-1].x;
+ newest_t = this.data[0].dataPoints[0].x;
+ }
+ if (newest_t < FossilRecord.tick_record[r_len-1]) {
+ this.addNewest();
+ }
+ if (oldest_t < FossilRecord.tick_record[0]) {
+ this.removeOldest();
+ }
+ }
+
+ addNewest() {
+ var i = FossilRecord.tick_record.length-1;
+ this.addDataPoint(i);
+ }
+
+ addDataPoint(i) {
+ alert("Must override addDataPoint")
+ }
+
+ removeOldest() {
+ for (var dps of this.data) {
+ dps.dataPoints.shift();
+ }
+ }
+
+ clear() {
+ this.data.length = 0;
+ this.chart.render();
+ }
+}
+
+module.exports = ChartController;
\ No newline at end of file
diff --git a/src/Stats/Charts/MutationChart.js b/src/Stats/Charts/MutationChart.js
new file mode 100644
index 0000000..f211bdb
--- /dev/null
+++ b/src/Stats/Charts/MutationChart.js
@@ -0,0 +1,31 @@
+const FossilRecord = require("../FossilRecord");
+const ChartController = require("./ChartController");
+
+class MutationChart extends ChartController {
+ constructor() {
+ super("Mutation Rate");
+ }
+
+ setData() {
+ this.clear();
+ this.data.push({
+ type: "line",
+ markerType: "none",
+ color: 'black',
+ showInLegend: true,
+ name: "pop1",
+ legendText: "Average Mutation Rate",
+ dataPoints: []
+ }
+ );
+ this.addAllDataPoints();
+ }
+
+ addDataPoint(i) {
+ var t = FossilRecord.tick_record[i];
+ var p = FossilRecord.av_mut_rates[i];
+ this.data[0].dataPoints.push({x:t, y:p});
+ }
+}
+
+module.exports = MutationChart;
\ No newline at end of file
diff --git a/src/Stats/Charts/PopulationChart.js b/src/Stats/Charts/PopulationChart.js
new file mode 100644
index 0000000..99a02af
--- /dev/null
+++ b/src/Stats/Charts/PopulationChart.js
@@ -0,0 +1,31 @@
+const FossilRecord = require("../FossilRecord");
+const ChartController = require("./ChartController");
+
+class PopulationChart extends ChartController {
+ constructor() {
+ super("Population");
+ }
+
+ setData() {
+ this.clear();
+ this.data.push({
+ type: "line",
+ markerType: "none",
+ color: 'black',
+ showInLegend: true,
+ name: "pop1",
+ legendText: "Total Population",
+ dataPoints: []
+ }
+ );
+ this.addAllDataPoints();
+ }
+
+ addDataPoint(i) {
+ var t = FossilRecord.tick_record[i];
+ var p = FossilRecord.pop_counts[i];
+ this.data[0].dataPoints.push({x:t, y:p});
+ }
+}
+
+module.exports = PopulationChart;
\ No newline at end of file
diff --git a/src/Stats/Charts/SpeciesChart.js b/src/Stats/Charts/SpeciesChart.js
new file mode 100644
index 0000000..50c6b84
--- /dev/null
+++ b/src/Stats/Charts/SpeciesChart.js
@@ -0,0 +1,31 @@
+const FossilRecord = require("../FossilRecord");
+const ChartController = require("./ChartController");
+
+class SpeciesChart extends ChartController {
+ constructor() {
+ super("Species");
+ }
+
+ setData() {
+ this.clear();
+ this.data.push({
+ type: "line",
+ markerType: "none",
+ color: 'black',
+ showInLegend: true,
+ name: "spec",
+ legendText: "Number of Species",
+ dataPoints: []
+ }
+ );
+ this.addAllDataPoints();
+ }
+
+ addDataPoint(i) {
+ var t = FossilRecord.tick_record[i];
+ var p = FossilRecord.species_counts[i];
+ this.data[0].dataPoints.push({x:t, y:p});
+ }
+}
+
+module.exports = SpeciesChart;
\ No newline at end of file
diff --git a/src/Stats/FossilRecord.js b/src/Stats/FossilRecord.js
new file mode 100644
index 0000000..16e58dd
--- /dev/null
+++ b/src/Stats/FossilRecord.js
@@ -0,0 +1,133 @@
+const CellStates = require("../Organism/Cell/CellStates");
+const Species = require("./Species");
+
+const FossilRecord = {
+ init: function(){
+ this.extant_species = [];
+ this.extinct_species = [];
+
+ // if an organism has fewer than this cumulative pop, discard them on extinction
+ this.min_discard = 10;
+
+ this.record_size_limit = 500; // store this many data points
+ },
+
+ setEnv: function(env) {
+ this.env = env;
+ this.setData();
+ },
+
+ addSpecies: function(org, ancestor) {
+ // console.log("Adding Species")
+ var new_species = new Species(org.anatomy, ancestor, this.env.total_ticks);
+ this.extant_species.push(new_species);
+ org.species = new_species;
+ return new_species;
+ },
+
+ addSpeciesObj: function(species) {
+ // console.log("Adding Species")
+ this.extant_species.push(species);
+ return species;
+ },
+
+ fossilize: function(species) {
+ // console.log("Extinction")
+ species.end_tick = this.env.total_ticks;
+ for (i in this.extant_species) {
+ var s = this.extant_species[i];
+ if (s == species) {
+ this.extant_species.splice(i, 1);
+ if (species.cumulative_pop < this.min_pop) {
+ return false;
+ }
+ this.extinct_species.push(s);
+ // console.log("Extant species:", this.extant_species.length)
+ // console.log("Extinct species:", this.extinct_species.length)
+ return true;
+ }
+ }
+ },
+
+ resurrect: function(species) {
+ // console.log("Resurrecting species")
+ if (species.extinct) {
+ for (i in this.extinct_species) {
+ var s = this.extinct_species[i];
+ if (s == species) {
+ this.extinct_species.splice(i, 1);
+ this.extant_species.push(species);
+ species.extinct = false;
+ }
+ }
+ }
+ },
+
+ setData() {
+ // all parallel arrays
+ this.tick_record = [0];
+ this.pop_counts = [0];
+ this.species_counts = [0];
+ this.av_mut_rates = [0];
+ this.av_cells = [0];
+ this.av_cell_counts = [this.calcCellCountAverages()];
+ },
+
+ updateData() {
+ var tick = this.env.total_ticks;
+ this.tick_record.push(tick);
+ this.pop_counts.push(this.env.organisms.length);
+ this.species_counts.push(this.extant_species.length);
+ this.av_mut_rates.push(this.env.averageMutability());
+ this.calcCellCountAverages();
+
+ if (this.tick_record.length > this.record_size_limit) {
+ this.tick_record.shift();
+ this.pop_counts.shift();
+ this.species_counts.shift();
+ this.av_mut_rates.shift();
+ this.av_cells.shift();
+ this.av_cell_counts.shift();
+ }
+ },
+
+ calcCellCountAverages() {
+ var total_org = 0;
+ var cell_counts = {};
+ for (let c of CellStates.living) {
+ cell_counts[c.name] = 0;
+ }
+ var first=true;
+ for (let s of this.extant_species) {
+ if (s.cumulative_pop < this.min_discard && !first){
+ continue;
+ }
+ for (let name in s.cell_counts) {
+ cell_counts[name] += s.cell_counts[name] * s.population;
+ }
+ total_org += s.population;
+ first=false;
+ }
+ if (total_org == 0)
+ return cell_counts;
+
+ var total_cells = 0;
+ for (let c in cell_counts) {
+ total_cells += cell_counts[c];
+ cell_counts[c] /= total_org;
+ }
+ this.av_cells.push(total_cells / total_org);
+ this.av_cell_counts.push(cell_counts);
+ },
+
+ clear_record: function() {
+ this.extant_species = [];
+ this.extinct_species = [];
+ this.setData();
+ },
+
+}
+
+FossilRecord.init();
+
+module.exports = FossilRecord;
\ No newline at end of file
diff --git a/src/Stats/Species.js b/src/Stats/Species.js
new file mode 100644
index 0000000..37daadd
--- /dev/null
+++ b/src/Stats/Species.js
@@ -0,0 +1,52 @@
+const CellStates = require("../Organism/Cell/CellStates");
+
+class Species {
+ constructor(anatomy, ancestor, start_tick) {
+ this.anatomy = anatomy;
+ this.ancestor = ancestor;
+ this.population = 1;
+ this.cumulative_pop = 1;
+ this.start_tick = start_tick;
+ this.end_tick = -1;
+ this.color = Math.floor(Math.random()*16777215).toString(16);
+ if (ancestor != null) {
+ // needs to be reworked, maybe removed
+ var mutator = Math.floor(Math.random()*16777215)-8000000;
+ this.color = (mutator + parseInt(ancestor.color, 16)).toString(16);
+ }
+ this.name = '_' + Math.random().toString(36).substr(2, 9);
+ this.extinct = false;
+ this.calcAnatomyDetails();
+ }
+
+ calcAnatomyDetails() {
+ var cell_counts = {};
+ for (let c of CellStates.living) {
+ cell_counts[c.name] = 0;
+ }
+ for (let cell of this.anatomy.cells) {
+ cell_counts[cell.state.name]+=1;
+ }
+ this.cell_counts=cell_counts;
+ }
+
+ addPop() {
+ this.population++;
+ this.cumulative_pop++;
+ }
+
+ decreasePop() {
+ this.population--;
+ if (this.population <= 0) {
+ this.extinct = true;
+ const FossilRecord = require("./FossilRecord");
+ FossilRecord.fossilize(this);
+ }
+ }
+
+ lifespan() {
+ return this.end_tick - this.start_tick;
+ }
+}
+
+module.exports = Species;
\ No newline at end of file
diff --git a/src/Stats/StatsPanel.js b/src/Stats/StatsPanel.js
new file mode 100644
index 0000000..4487861
--- /dev/null
+++ b/src/Stats/StatsPanel.js
@@ -0,0 +1,66 @@
+const PopulationChart = require("./Charts/PopulationChart");
+const SpeciesChart = require("./Charts/SpeciesChart");
+const MutationChart = require("./Charts/MutationChart");
+const CellsChart = require("./Charts/CellsChart");
+const FossilRecord = require("./FossilRecord");
+
+
+const ChartSelections = [PopulationChart, SpeciesChart, CellsChart, MutationChart];
+
+class StatsPanel {
+ constructor(env) {
+ this.defineControls();
+ this.chart_selection = 0;
+ this.setChart();
+ this.env = env;
+ this.last_reset_count=env.reset_count;
+ }
+
+ setChart(selection=this.chart_selection) {
+ this.chart_controller = new ChartSelections[selection]();
+ this.chart_controller.setData();
+ this.chart_controller.render();
+ }
+
+ startAutoRender() {
+ this.setChart();
+ this.render_loop = setInterval(function(){this.updateChart();}.bind(this), 1000);
+ }
+
+ stopAutoRender() {
+ clearInterval(this.render_loop);
+ }
+
+ defineControls() {
+ $('#chart-option').change ( function() {
+ this.chart_selection = $("#chart-option")[0].selectedIndex;
+ this.setChart();
+ }.bind(this));
+ }
+
+ updateChart() {
+ if (this.last_reset_count < this.env.reset_count){
+ this.reset()
+ }
+ this.last_reset_count = this.env.reset_count;
+ this.chart_controller.updateData();
+ this.chart_controller.render();
+ }
+
+ updateDetails() {
+ var org_count = this.env.organisms.length;
+ $('#org-count').text("Total Population: " + org_count);
+ $('#species-count').text("Number of Species: " + FossilRecord.extant_species.length);
+ $('#largest-org').text("Largest Organism Ever: " + this.env.largest_cell_count + " cells");
+ $('#avg-mut').text("Average Mutation Rate: " + Math.round(this.env.averageMutability() * 100) / 100);
+
+
+ }
+
+ reset() {
+ this.setChart();
+ }
+
+}
+
+module.exports = StatsPanel;
\ No newline at end of file