diff --git a/dist/index.html b/dist/index.html
index 70f160b..8e89ab6 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -20,7 +20,7 @@
@@ -270,7 +279,7 @@
-
+
diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js
index 11e81eb..46cda00 100644
--- a/src/Controllers/ControlPanel.js
+++ b/src/Controllers/ControlPanel.js
@@ -47,6 +47,8 @@ class ControlPanel {
defineHotkeys() {
$('body').keydown( (e) => {
+ let focused = document.activeElement;
+ if (focused.tagName === "INPUT" && focused.type === "text") return;
switch (e.key.toLowerCase()) {
// hot bar controls
case 'a':
@@ -205,6 +207,43 @@ class ControlPanel {
this.env_controller.add_new_species = true;
this.env_controller.dropOrganism(org, center[0], center[1])
});
+ $('#save-env').click( () => {
+ let was_running = this.engine.running;
+ this.setPaused(true);
+ let env = this.engine.env.serialize();
+ let data = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(env));
+ let downloadEl = document.getElementById('download-el');
+ downloadEl.setAttribute("href", data);
+ downloadEl.setAttribute("download", $('#save-env-name').val()+".json");
+ downloadEl.click();
+ if (was_running)
+ this.setPaused(false);
+ });
+ $('#load-env').click(() => {
+ $('#upload-env').click();
+ });
+ $('#upload-env').change((e)=>{
+ let files = e.target.files;
+ if (!files.length) {return;};
+ let reader = new FileReader();
+ reader.onload = (e) => {
+ try {
+ let was_running = this.engine.running;
+ this.setPaused(true);
+ let env = JSON.parse(e.target.result);
+ this.engine.env.loadRaw(env);
+ if (was_running)
+ this.setPaused(false);
+ this.updateHyperparamUIValues();
+ this.env_controller.resetView();
+ } catch(except) {
+ console.error(except)
+ alert('Failed to load world');
+ }
+ $('#upload-env')[0].value = '';
+ };
+ reader.readAsText(files[0]);
+ });
}
defineHyperparameterControls() {
diff --git a/src/Environments/WorldEnvironment.js b/src/Environments/WorldEnvironment.js
index 6f47b27..8ad7276 100644
--- a/src/Environments/WorldEnvironment.js
+++ b/src/Environments/WorldEnvironment.js
@@ -7,6 +7,8 @@ const EnvironmentController = require('../Controllers/EnvironmentController');
const Hyperparams = require('../Hyperparameters.js');
const FossilRecord = require('../Stats/FossilRecord');
const WorldConfig = require('../WorldConfig');
+const SerializeHelper = require('../Utils/SerializeHelper');
+const Species = require('../Stats/Species');
class WorldEnvironment extends Environment{
constructor(cell_size) {
@@ -164,6 +166,60 @@ class WorldEnvironment extends Environment{
this.num_rows = Math.ceil(this.renderer.height / cell_size);
this.grid_map.resize(this.num_cols, this.num_rows, cell_size);
}
+
+ serialize() {
+ let env = SerializeHelper.copyNonObjects(this);
+ env.grid = this.grid_map.serialize();
+ env.organisms = [];
+ for (let org of this.organisms){
+ env.organisms.push(org.serialize());
+ }
+ env.fossil_record = FossilRecord.serialize();
+ env.controls = Hyperparams;
+ return env;
+ }
+
+ loadRaw(env) { // species name->stats map, evolution controls,
+ this.organisms = [];
+ FossilRecord.clear_record();
+ this.resizeGridColRow(this.grid_map.cell_size, env.grid.cols, env.grid.rows)
+ this.grid_map.loadRaw(env.grid);
+
+ // create species map
+ let species = {};
+ for (let name in env.fossil_record.species) {
+ let s = new Species(null, null, 0);
+ SerializeHelper.overwriteNonObjects(env.fossil_record.species[name], s)
+ species[name] = s; // the species needs an anatomy obj still
+ }
+
+ for (let orgRaw of env.organisms) {
+ let org = new Organism(orgRaw.col, orgRaw.row, this);
+ if (!orgRaw.anatomy) console.log(orgRaw)
+ org.loadRaw(orgRaw);
+ this.addOrganism(org);
+ let s = species[orgRaw.species_name];
+ if (!s){
+ console.log("Created:", orgRaw.species_name)
+ console.log(org.anatomy)
+ s = new Species(org.anatomy, null, env.total_ticks);
+ species[orgRaw.species_name] = s;
+ }
+ if (!s.anatomy) {
+ //if the species doesn't have anatomy we need to initialize it
+ s.anatomy = org.anatomy;
+ s.calcAnatomyDetails();
+ }
+ org.species = s;
+ }
+ for (let name in species)
+ FossilRecord.addSpeciesObj(species[name]);
+ FossilRecord.loadRaw(env.fossil_record);
+ SerializeHelper.overwriteNonObjects(env, this);
+ if ($('#override-controls').is(':checked'))
+ Hyperparams.loadJsonObj(env.controls)
+ this.renderer.renderFullGrid(this.grid_map.grid);
+ }
}
module.exports = WorldEnvironment;
diff --git a/src/Grid/GridMap.js b/src/Grid/GridMap.js
index 84db4ca..6c69773 100644
--- a/src/Grid/GridMap.js
+++ b/src/Grid/GridMap.js
@@ -78,6 +78,34 @@ class GridMap {
r = 0;
return [c, r];
}
+
+ serialize() {
+ // Rather than store every single cell, we will store non organism cells (food+walls)
+ // and assume everything else is empty. Organism cells will be set when the organism
+ // list is loaded. This reduces filesize and complexity.
+ let grid = {cols:this.cols, rows:this.rows};
+ grid.food = [];
+ grid.walls = [];
+ for (let col of this.grid) {
+ for (let cell of col) {
+ if (cell.state===CellStates.wall || cell.state===CellStates.food){
+ let c = {c: cell.col, r: cell.row}; // no need to store state
+ if (cell.state===CellStates.food)
+ grid.food.push(c)
+ else
+ grid.walls.push(c)
+ }
+ }
+ }
+ return grid;
+ }
+
+ loadRaw(grid) {
+ for (let f of grid.food)
+ this.setCellType(f.c, f.r, CellStates.food);
+ for (let w of grid.walls)
+ this.setCellType(w.c, w.r, CellStates.wall);
+ }
}
module.exports = GridMap;
diff --git a/src/Organism/Cell/BodyCells/KillerCell.js b/src/Organism/Cell/BodyCells/KillerCell.js
index ca82218..c7dcd92 100644
--- a/src/Organism/Cell/BodyCells/KillerCell.js
+++ b/src/Organism/Cell/BodyCells/KillerCell.js
@@ -18,7 +18,6 @@ class KillerCell extends BodyCell{
}
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
diff --git a/src/Organism/Organism.js b/src/Organism/Organism.js
index 0461f27..80f8404 100644
--- a/src/Organism/Organism.js
+++ b/src/Organism/Organism.js
@@ -34,7 +34,6 @@ class Organism {
this.move_range = parent.move_range;
this.mutability = parent.mutability;
this.species = parent.species;
- // this.birth_distance = parent.birth_distance;
for (var c of parent.anatomy.cells){
//deep copy parent cells
this.anatomy.addInheritCell(c);
@@ -321,21 +320,17 @@ class Organism {
serialize() {
let org = SerializeHelper.copyNonObjects(this);
org.anatomy = this.anatomy.serialize();
- if (this.brain)
+ if (this.anatomy.is_mover && this.anatomy.has_eyes)
org.brain = this.brain.serialize();
+ org.species_name = this.species.name;
return org;
}
loadRaw(org) {
- for (let key in org)
- if (typeof org[key] !== 'object')
- this[key] = org[key];
+ SerializeHelper.overwriteNonObjects(org, this);
this.anatomy.loadRaw(org.anatomy)
- console.log(org)
- if (org.brain) {
- console.log('load brain')
+ if (org.brain)
this.brain.copy(org.brain)
- }
}
}
diff --git a/src/Stats/FossilRecord.js b/src/Stats/FossilRecord.js
index e9007fa..17ec347 100644
--- a/src/Stats/FossilRecord.js
+++ b/src/Stats/FossilRecord.js
@@ -1,4 +1,5 @@
const CellStates = require("../Organism/Cell/CellStates");
+const SerializeHelper = require("../Utils/SerializeHelper");
const Species = require("./Species");
const FossilRecord = {
@@ -30,7 +31,6 @@ const FossilRecord = {
},
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];
@@ -44,16 +44,12 @@ const FossilRecord = {
}
// disabled for now, causes memory problems on long runs
// 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];
@@ -68,12 +64,13 @@ const FossilRecord = {
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()];
+ this.tick_record = [];
+ this.pop_counts = [];
+ this.species_counts = [];
+ this.av_mut_rates = [];
+ this.av_cells = [];
+ this.av_cell_counts = [];
+ this.updateData();
},
updateData() {
@@ -122,12 +119,39 @@ const FossilRecord = {
this.av_cell_counts.push(cell_counts);
},
- clear_record: function() {
+ clear_record() {
this.extant_species = [];
this.extinct_species = [];
this.setData();
},
+ serialize() {
+ this.updateData();
+ let record = SerializeHelper.copyNonObjects(this);
+ record.records = {
+ tick_record:this.tick_record,
+ pop_counts:this.pop_counts,
+ species_counts:this.species_counts,
+ av_mut_rates:this.av_mut_rates,
+ av_cells:this.av_cells,
+ av_cell_counts:this.av_cell_counts,
+ };
+ let species = {};
+ for (let s of this.extant_species) {
+ species[s.name] = SerializeHelper.copyNonObjects(s);
+ delete species[s.name].name; // the name will be used as the key, so remove it from the value
+ }
+ record.species = species;
+ return record;
+ },
+
+ loadRaw(record) {
+ SerializeHelper.overwriteNonObjects(record, this);
+ for (let key in record.records) {
+ this[key] = record.records[key];
+ }
+ }
+
}
FossilRecord.init();
diff --git a/src/Stats/Species.js b/src/Stats/Species.js
index 050e70b..6f5b5f9 100644
--- a/src/Stats/Species.js
+++ b/src/Stats/Species.js
@@ -14,12 +14,13 @@ class Species {
this.cumulative_pop = 1;
this.start_tick = start_tick;
this.end_tick = -1;
- this.name = '_' + Math.random().toString(36).substr(2, 9);
+ this.name = Math.random().toString(36).substr(2, 10);
this.extinct = false;
this.calcAnatomyDetails();
}
calcAnatomyDetails() {
+ if (!this.anatomy) return;
var cell_counts = {};
for (let c of CellStates.living) {
cell_counts[c.name] = 0;
diff --git a/src/Utils/SerializeHelper.js b/src/Utils/SerializeHelper.js
index c96c8c2..62930a9 100644
--- a/src/Utils/SerializeHelper.js
+++ b/src/Utils/SerializeHelper.js
@@ -6,6 +6,14 @@ const SerializeHelper = {
newobj[key] = obj[key];
}
return newobj;
+ },
+ overwriteNonObjects(copyFrom, copyTo) {
+ for (let key in copyFrom) {
+ if (typeof copyFrom[key] !== 'object' && typeof copyTo[key] !== 'object') {
+ // only overwrite if neither are objects
+ copyTo[key] = copyFrom[key];
+ }
+ }
}
}