added world saving/loading
This commit is contained in:
13
dist/index.html
vendored
13
dist/index.html
vendored
@@ -20,7 +20,7 @@
|
||||
<div class='vertical-buttons'>
|
||||
<button class="reset-view" title="Reset View. Hotkey: A"><i class="fa fa-video-camera"></i></button>
|
||||
<button class="edit-mode-btn drag-view" id="drag-view" title="Drag View. Hotkey: S"><i class="fa fa-arrows"></i></button>
|
||||
<button class="edit-mode-btn wall-drop" id="wall-drop" title="Drop/Remove Wall. Hotkey: D"><i class="fa fa-square"></i></button>
|
||||
<button class="edit-mode-btn wall-drop" id="wall-drop" title="Drop/Remove Wall. Hotkey: D"><i class="fa fa-th"></i></button>
|
||||
<button class="edit-mode-btn food-drop selected" id="food-drop" title="Drop/Remove Food. Hotkey: F"><i class="fa fa-cutlery"></i></button>
|
||||
<button class="edit-mode-btn click-kill" id="click-kill" title="Click to kill. Hotkey: G"><i class="fa fa-bolt"></i></button>
|
||||
</div>
|
||||
@@ -189,6 +189,15 @@
|
||||
<br>
|
||||
<label for="clear-walls-reset" title='When on, walls will be cleared when the environment resets'>Clear walls on reset</label>
|
||||
<input type="checkbox" id="clear-walls-reset">
|
||||
<br>
|
||||
<button id='save-env' title="Download save file for entire world">Save World</button>
|
||||
<label for="save-env-name" title='Save World as:'>Save As:</label>
|
||||
<input type="text" id="save-env-name" value="world" style="text-align: center;">.json
|
||||
<br>
|
||||
<button id='load-env' title="Load world save file">Load World</button>
|
||||
<label for="override-controls" title='Override the current evolution controls with those from the loaded world'>Override Evolution Controls</label>
|
||||
<input type="checkbox" id="override-controls" checked>
|
||||
<input id="upload-env" style="display: none;" type="file">
|
||||
</div>
|
||||
</div>
|
||||
<div id='hyperparameters' class='tab'>
|
||||
@@ -270,7 +279,7 @@
|
||||
<div class='hot-controls'>
|
||||
<button class="reset-view" title="Reset View. Hotkey: A"><i class="fa fa-video-camera"></i></button>
|
||||
<button class="edit-mode-btn drag-view" id="drag-view" title="Drag View. Hotkey: S"><i class="fa fa-arrows"></i></button>
|
||||
<button class="edit-mode-btn wall-drop" id="wall-drop" title="Drop/Remove Wall. Hotkey: D"><i class="fa fa-square"></i></button>
|
||||
<button class="edit-mode-btn wall-drop" id="wall-drop" title="Drop/Remove Wall. Hotkey: D"><i class="fa fa-th"></i></button>
|
||||
<button class="edit-mode-btn food-drop selected" id="food-drop" title="Drop/Remove Food. Hotkey: F"><i class="fa fa-cutlery"></i></button>
|
||||
<button class="edit-mode-btn click-kill" id="click-kill" title="Click to kill. Hotkey: G"><i class="fa fa-bolt"></i></button>
|
||||
<button class="headless" title="Toggle rendering. Hotkey: H"><i class="fa fa-eye-slash"></i></button>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,22 +320,18 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user