10
Changelog.md
10
Changelog.md
@@ -1,18 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.0.3 (current dev)
|
## 1.0.3 (4/15/2022)
|
||||||
|
|
||||||
### UI Enhancements:
|
### UI Enhancements:
|
||||||
- Improved input box styling
|
- Improved styling
|
||||||
|
|
||||||
### Simulation Enhancements:
|
### Simulation Enhancements:
|
||||||
-
|
- Added ability to save/load organisms
|
||||||
|
- Added ability to save/load worlds
|
||||||
|
|
||||||
### Bug Fixes:
|
### Bug Fixes:
|
||||||
- charste changed to charset
|
- charste changed to charset
|
||||||
|
- Fixed species tracking
|
||||||
|
|
||||||
|
|
||||||
Thanks to contributors:
|
Thanks to contributors: @TerraMaster85
|
||||||
|
|
||||||
## 1.0.2 (12/21/2021)
|
## 1.0.2 (12/21/2021)
|
||||||
|
|
||||||
|
|||||||
19
dist/index.html
vendored
19
dist/index.html
vendored
@@ -6,7 +6,6 @@
|
|||||||
<link rel="icon" href="./img/icon.png">
|
<link rel="icon" href="./img/icon.png">
|
||||||
<link rel="stylesheet" href="./css/style.css">
|
<link rel="stylesheet" href="./css/style.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
|
||||||
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
<script type="text/javascript" src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
<div class='vertical-buttons'>
|
<div class='vertical-buttons'>
|
||||||
<button class="reset-view" title="Reset View. Hotkey: A"><i class="fa fa-video-camera"></i></button>
|
<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 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 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="edit-mode-btn click-kill" id="click-kill" title="Click to kill. Hotkey: G"><i class="fa fa-bolt"></i></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,6 +77,9 @@
|
|||||||
<button class="edit-mode-btn select" id="select" title="Select organism from world. Hotkey: Z"><i class="fa fa-hand-pointer-o"></i></button>
|
<button class="edit-mode-btn select" id="select" title="Select organism from world. Hotkey: Z"><i class="fa fa-hand-pointer-o"></i></button>
|
||||||
<button class="edit-mode-btn edit" id="edit" title="Edit organism. Hotkey: X"><i class="fa fa-pencil"></i></button>
|
<button class="edit-mode-btn edit" id="edit" title="Edit organism. Hotkey: X"><i class="fa fa-pencil"></i></button>
|
||||||
<button class="edit-mode-btn drop-org" id="drop-org" title="Drop organism in world. Hotkey: C"><i class="fa fa-plus"></i></button>
|
<button class="edit-mode-btn drop-org" id="drop-org" title="Drop organism in world. Hotkey: C"><i class="fa fa-plus"></i></button>
|
||||||
|
<button id="save-org" title="Save Organism"><i class="fa fa-save"></i></button>
|
||||||
|
<button id="load-org" title="Load Organism"><i class="fa fa-upload"></i></button>
|
||||||
|
<input id="upload-org" style="display: none;" type="file">
|
||||||
</div>
|
</div>
|
||||||
<div id='editor-env'>
|
<div id='editor-env'>
|
||||||
<canvas id='editor-canvas'></canvas>
|
<canvas id='editor-canvas'></canvas>
|
||||||
@@ -187,6 +189,15 @@
|
|||||||
<br>
|
<br>
|
||||||
<label for="clear-walls-reset" title='When on, walls will be cleared when the environment resets'>Clear walls on reset</label>
|
<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">
|
<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>
|
</div>
|
||||||
<div id='hyperparameters' class='tab'>
|
<div id='hyperparameters' class='tab'>
|
||||||
@@ -238,7 +249,7 @@
|
|||||||
<button id='save-controls' title="Save all Evolution Controls in a .json file">Save & Download</button>
|
<button id='save-controls' title="Save all Evolution Controls in a .json file">Save & Download</button>
|
||||||
<button id='load-controls' title="Load saved Evolution Controls with a .json file">Load</button>
|
<button id='load-controls' title="Load saved Evolution Controls with a .json file">Load</button>
|
||||||
<a id="download-el" style="display: none;"></a>
|
<a id="download-el" style="display: none;"></a>
|
||||||
<input id="upload-el" style="display: none;" type="file">
|
<input id="upload-hyperparams" style="display: none;" type="file">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id='stats' class='tab'>
|
<div id='stats' class='tab'>
|
||||||
@@ -268,7 +279,7 @@
|
|||||||
<div class='hot-controls'>
|
<div class='hot-controls'>
|
||||||
<button class="reset-view" title="Reset View. Hotkey: A"><i class="fa fa-video-camera"></i></button>
|
<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 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 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="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>
|
<button class="headless" title="Toggle rendering. Hotkey: H"><i class="fa fa-eye-slash"></i></button>
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -198,9 +198,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.2",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@@ -2048,9 +2048,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mississippi": {
|
"mississippi": {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
const Hyperparams = require("../Hyperparameters");
|
const Hyperparams = require("../Hyperparameters");
|
||||||
const Modes = require("./ControlModes");
|
const Modes = require("./ControlModes");
|
||||||
const StatsPanel = require("../Stats/StatsPanel");
|
const StatsPanel = require("../Stats/StatsPanel");
|
||||||
const RandomOrganismGenerator = require("../Organism/RandomOrganismGenerator")
|
|
||||||
const WorldConfig = require("../WorldConfig");
|
const WorldConfig = require("../WorldConfig");
|
||||||
|
|
||||||
class ControlPanel {
|
class ControlPanel {
|
||||||
@@ -48,6 +47,8 @@ class ControlPanel {
|
|||||||
|
|
||||||
defineHotkeys() {
|
defineHotkeys() {
|
||||||
$('body').keydown( (e) => {
|
$('body').keydown( (e) => {
|
||||||
|
let focused = document.activeElement;
|
||||||
|
if (focused.tagName === "INPUT" && focused.type === "text") return;
|
||||||
switch (e.key.toLowerCase()) {
|
switch (e.key.toLowerCase()) {
|
||||||
// hot bar controls
|
// hot bar controls
|
||||||
case 'a':
|
case 'a':
|
||||||
@@ -206,6 +207,43 @@ class ControlPanel {
|
|||||||
this.env_controller.add_new_species = true;
|
this.env_controller.add_new_species = true;
|
||||||
this.env_controller.dropOrganism(org, center[0], center[1])
|
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() {
|
defineHyperparameterControls() {
|
||||||
@@ -279,9 +317,9 @@ class ControlPanel {
|
|||||||
downloadEl.click();
|
downloadEl.click();
|
||||||
});
|
});
|
||||||
$('#load-controls').click(() => {
|
$('#load-controls').click(() => {
|
||||||
$('#upload-el').click();
|
$('#upload-hyperparams').click();
|
||||||
});
|
});
|
||||||
$('#upload-el').change((e)=>{
|
$('#upload-hyperparams').change((e)=>{
|
||||||
let files = e.target.files;
|
let files = e.target.files;
|
||||||
if (!files.length) {return;};
|
if (!files.length) {return;};
|
||||||
let reader = new FileReader();
|
let reader = new FileReader();
|
||||||
@@ -290,7 +328,7 @@ class ControlPanel {
|
|||||||
Hyperparams.loadJsonObj(result);
|
Hyperparams.loadJsonObj(result);
|
||||||
this.updateHyperparamUIValues();
|
this.updateHyperparamUIValues();
|
||||||
// have to clear the value so change() will be triggered if the same file is uploaded again
|
// have to clear the value so change() will be triggered if the same file is uploaded again
|
||||||
$('#upload-el')[0].value = '';
|
$('#upload-hyperparams')[0].value = '';
|
||||||
};
|
};
|
||||||
reader.readAsText(files[0]);
|
reader.readAsText(files[0]);
|
||||||
});
|
});
|
||||||
@@ -428,7 +466,6 @@ class ControlPanel {
|
|||||||
this.env_controller.org_to_clone = this.engine.organism_editor.getCopyOfOrg();
|
this.env_controller.org_to_clone = this.engine.organism_editor.getCopyOfOrg();
|
||||||
this.env_controller.add_new_species = this.editor_controller.new_species;
|
this.env_controller.add_new_species = this.editor_controller.new_species;
|
||||||
this.editor_controller.new_species = false;
|
this.editor_controller.new_species = false;
|
||||||
// console.log(this.env_controller.add_new_species)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class EditorController extends CanvasController{
|
|||||||
this.new_species = false;
|
this.new_species = false;
|
||||||
this.defineCellTypeSelection();
|
this.defineCellTypeSelection();
|
||||||
this.defineEditorDetails();
|
this.defineEditorDetails();
|
||||||
|
this.defineSaveLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseMove() {
|
mouseMove() {
|
||||||
@@ -109,6 +110,43 @@ class EditorController extends CanvasController{
|
|||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineSaveLoad() {
|
||||||
|
$('#save-org').click(()=>{
|
||||||
|
let org = this.env.organism.serialize();
|
||||||
|
let data = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(org));
|
||||||
|
let downloadEl = document.getElementById('download-el');
|
||||||
|
downloadEl.setAttribute("href", data);
|
||||||
|
downloadEl.setAttribute("download", "organism.json");
|
||||||
|
downloadEl.click();
|
||||||
|
});
|
||||||
|
$('#load-org').click(() => {
|
||||||
|
$('#upload-org').click();
|
||||||
|
});
|
||||||
|
$('#upload-org').change((e)=>{
|
||||||
|
let files = e.target.files;
|
||||||
|
if (!files.length) {return;};
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
let org=JSON.parse(e.target.result);
|
||||||
|
this.env.clear();
|
||||||
|
this.env.organism.loadRaw(org);
|
||||||
|
this.refreshDetailsPanel();
|
||||||
|
this.env.organism.updateGrid();
|
||||||
|
this.env.renderFull();
|
||||||
|
if (this.mode === Modes.Clone)
|
||||||
|
$('#drop-org').click();
|
||||||
|
// have to clear the value so change() will be triggered if the same file is uploaded again
|
||||||
|
$('#upload-org')[0].value = '';
|
||||||
|
} catch(except) {
|
||||||
|
console.error(except)
|
||||||
|
alert('Failed to load organism');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(files[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
clearDetailsPanel() {
|
clearDetailsPanel() {
|
||||||
$('#organism-details').css('display', 'none');
|
$('#organism-details').css('display', 'none');
|
||||||
$('#edit-organism-details').css('display', 'none');
|
$('#edit-organism-details').css('display', 'none');
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const EnvironmentController = require('../Controllers/EnvironmentController');
|
|||||||
const Hyperparams = require('../Hyperparameters.js');
|
const Hyperparams = require('../Hyperparameters.js');
|
||||||
const FossilRecord = require('../Stats/FossilRecord');
|
const FossilRecord = require('../Stats/FossilRecord');
|
||||||
const WorldConfig = require('../WorldConfig');
|
const WorldConfig = require('../WorldConfig');
|
||||||
|
const SerializeHelper = require('../Utils/SerializeHelper');
|
||||||
|
const Species = require('../Stats/Species');
|
||||||
|
|
||||||
class WorldEnvironment extends Environment{
|
class WorldEnvironment extends Environment{
|
||||||
constructor(cell_size) {
|
constructor(cell_size) {
|
||||||
@@ -164,6 +166,57 @@ class WorldEnvironment extends Environment{
|
|||||||
this.num_rows = Math.ceil(this.renderer.height / cell_size);
|
this.num_rows = Math.ceil(this.renderer.height / cell_size);
|
||||||
this.grid_map.resize(this.num_cols, this.num_rows, 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);
|
||||||
|
org.loadRaw(orgRaw);
|
||||||
|
this.addOrganism(org);
|
||||||
|
let s = species[orgRaw.species_name];
|
||||||
|
if (!s){ // ideally, every organisms species should exists, but there is a bug somewhere
|
||||||
|
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;
|
module.exports = WorldEnvironment;
|
||||||
|
|||||||
@@ -78,6 +78,34 @@ class GridMap {
|
|||||||
r = 0;
|
r = 0;
|
||||||
return [c, r];
|
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;
|
module.exports = GridMap;
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
const CellStates = require("./Cell/CellStates");
|
const CellStates = require("./Cell/CellStates");
|
||||||
const BodyCellFactory = require("./Cell/BodyCells/BodyCellFactory");
|
const BodyCellFactory = require("./Cell/BodyCells/BodyCellFactory");
|
||||||
|
const SerializeHelper = require("../Utils/SerializeHelper");
|
||||||
|
|
||||||
class Anatomy {
|
class Anatomy {
|
||||||
constructor(owner) {
|
constructor(owner) {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
this.birth_distance = 4;
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
this.cells = [];
|
this.cells = [];
|
||||||
this.is_producer = false;
|
this.is_producer = false;
|
||||||
this.is_mover = false;
|
this.is_mover = false;
|
||||||
this.has_eyes = false;
|
this.has_eyes = false;
|
||||||
this.birth_distance = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canAddCellAt(c, r) {
|
canAddCellAt(c, r) {
|
||||||
@@ -61,7 +66,7 @@ class Anatomy {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.checkTypeChange(cell.state);
|
this.checkTypeChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +98,7 @@ class Anatomy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNeighborsOfCell(col, row) {
|
getNeighborsOfCell(col, row) {
|
||||||
|
|
||||||
var neighbors = [];
|
var neighbors = [];
|
||||||
|
|
||||||
for (var x = -1; x <= 1; x++) {
|
for (var x = -1; x <= 1; x++) {
|
||||||
for (var y = -1; y <= 1; y++) {
|
for (var y = -1; y <= 1; y++) {
|
||||||
|
|
||||||
@@ -107,6 +110,37 @@ class Anatomy {
|
|||||||
|
|
||||||
return neighbors;
|
return neighbors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEqual(anatomy) { // currently unused helper func. inefficient, avoid usage in prod.
|
||||||
|
if (this.cells.length !== anatomy.cells.length) return false;
|
||||||
|
for (let i in this.cells) {
|
||||||
|
let my_cell = this.cells[i];
|
||||||
|
let their_cell = anatomy.cells[i];
|
||||||
|
if (my_cell.loc_col !== their_cell.loc_col ||
|
||||||
|
my_cell.loc_row !== their_cell.loc_row ||
|
||||||
|
my_cell.state !== their_cell.state)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
let anatomy = SerializeHelper.copyNonObjects(this);
|
||||||
|
anatomy.cells = [];
|
||||||
|
for (let cell of this.cells) {
|
||||||
|
let newcell = SerializeHelper.copyNonObjects(cell);
|
||||||
|
newcell.state = {name: cell.state.name};
|
||||||
|
anatomy.cells.push(newcell)
|
||||||
|
}
|
||||||
|
return anatomy;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRaw(anatomy) {
|
||||||
|
this.clear();
|
||||||
|
for (let cell of anatomy.cells){
|
||||||
|
this.addInheritCell(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Anatomy;
|
module.exports = Anatomy;
|
||||||
@@ -18,7 +18,6 @@ class KillerCell extends BodyCell{
|
|||||||
}
|
}
|
||||||
|
|
||||||
killNeighbor(n_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)
|
if(n_cell == null || n_cell.owner == null || n_cell.owner == this.org || !n_cell.owner.living || n_cell.state == CellStates.armor)
|
||||||
return;
|
return;
|
||||||
var is_hit = n_cell.state == CellStates.killer; // has to be calculated before death
|
var is_hit = n_cell.state == CellStates.killer; // has to be calculated before death
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const Directions = require("./Directions");
|
|||||||
const Anatomy = require("./Anatomy");
|
const Anatomy = require("./Anatomy");
|
||||||
const Brain = require("./Perception/Brain");
|
const Brain = require("./Perception/Brain");
|
||||||
const FossilRecord = require("../Stats/FossilRecord");
|
const FossilRecord = require("../Stats/FossilRecord");
|
||||||
|
const SerializeHelper = require("../Utils/SerializeHelper");
|
||||||
|
|
||||||
class Organism {
|
class Organism {
|
||||||
constructor(col, row, env, parent=null) {
|
constructor(col, row, env, parent=null) {
|
||||||
@@ -33,15 +34,12 @@ class Organism {
|
|||||||
this.move_range = parent.move_range;
|
this.move_range = parent.move_range;
|
||||||
this.mutability = parent.mutability;
|
this.mutability = parent.mutability;
|
||||||
this.species = parent.species;
|
this.species = parent.species;
|
||||||
// this.birth_distance = parent.birth_distance;
|
|
||||||
for (var c of parent.anatomy.cells){
|
for (var c of parent.anatomy.cells){
|
||||||
//deep copy parent cells
|
//deep copy parent cells
|
||||||
this.anatomy.addInheritCell(c);
|
this.anatomy.addInheritCell(c);
|
||||||
}
|
}
|
||||||
if(parent.anatomy.is_mover) {
|
if(parent.anatomy.is_mover && parent.anatomy.has_eyes) {
|
||||||
for (var i in parent.brain.decisions) {
|
this.brain.copy(parent.brain);
|
||||||
this.brain.decisions[i] = parent.brain.decisions[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +102,6 @@ class Organism {
|
|||||||
var new_c = this.c + (direction_c*basemovement) + (direction_c*offset);
|
var new_c = this.c + (direction_c*basemovement) + (direction_c*offset);
|
||||||
var new_r = this.r + (direction_r*basemovement) + (direction_r*offset);
|
var new_r = this.r + (direction_r*basemovement) + (direction_r*offset);
|
||||||
|
|
||||||
// console.log(org.isClear(new_c, new_r, org.rotation, true))
|
|
||||||
if (org.isClear(new_c, new_r, org.rotation, true) && org.isStraightPath(new_c, new_r, this.c, this.r, this)){
|
if (org.isClear(new_c, new_r, org.rotation, true) && org.isStraightPath(new_c, new_r, this.c, this.r, this)){
|
||||||
org.c = new_c;
|
org.c = new_c;
|
||||||
org.r = new_r;
|
org.r = new_r;
|
||||||
@@ -118,11 +115,12 @@ class Organism {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Math.max(this.food_collected -= this.foodNeeded(), 0);
|
Math.max(this.food_collected -= this.foodNeeded(), 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate() {
|
mutate() {
|
||||||
let mutated = false;
|
let added = false;
|
||||||
|
let changed = false;
|
||||||
|
let removed = false;
|
||||||
if (this.calcRandomChance(Hyperparams.addProb)) {
|
if (this.calcRandomChance(Hyperparams.addProb)) {
|
||||||
let branch = this.anatomy.getRandomCell();
|
let branch = this.anatomy.getRandomCell();
|
||||||
let state = CellStates.getRandomLivingType();//branch.state;
|
let state = CellStates.getRandomLivingType();//branch.state;
|
||||||
@@ -130,7 +128,7 @@ class Organism {
|
|||||||
let c = branch.loc_col+growth_direction[0];
|
let c = branch.loc_col+growth_direction[0];
|
||||||
let r = branch.loc_row+growth_direction[1];
|
let r = branch.loc_row+growth_direction[1];
|
||||||
if (this.anatomy.canAddCellAt(c, r)){
|
if (this.anatomy.canAddCellAt(c, r)){
|
||||||
mutated = true;
|
added = true;
|
||||||
this.anatomy.addRandomizedCell(state, c, r);
|
this.anatomy.addRandomizedCell(state, c, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,15 +136,15 @@ class Organism {
|
|||||||
let cell = this.anatomy.getRandomCell();
|
let cell = this.anatomy.getRandomCell();
|
||||||
let state = CellStates.getRandomLivingType();
|
let state = CellStates.getRandomLivingType();
|
||||||
this.anatomy.replaceCell(state, cell.loc_col, cell.loc_row);
|
this.anatomy.replaceCell(state, cell.loc_col, cell.loc_row);
|
||||||
mutated = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
if (this.calcRandomChance(Hyperparams.removeProb)){
|
if (this.calcRandomChance(Hyperparams.removeProb)){
|
||||||
if(this.anatomy.cells.length > 1) {
|
if(this.anatomy.cells.length > 1) {
|
||||||
let cell = this.anatomy.getRandomCell();
|
let cell = this.anatomy.getRandomCell();
|
||||||
mutated = this.anatomy.removeCell(cell.loc_col, cell.loc_row);
|
removed = this.anatomy.removeCell(cell.loc_col, cell.loc_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mutated;
|
return added || changed || removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
calcRandomChance(prob) {
|
calcRandomChance(prob) {
|
||||||
@@ -319,6 +317,22 @@ class Organism {
|
|||||||
return this.env.grid_map.cellAt(real_c, real_r);
|
return this.env.grid_map.cellAt(real_c, real_r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
let org = SerializeHelper.copyNonObjects(this);
|
||||||
|
org.anatomy = this.anatomy.serialize();
|
||||||
|
if (this.anatomy.is_mover && this.anatomy.has_eyes)
|
||||||
|
org.brain = this.brain.serialize();
|
||||||
|
org.species_name = this.species.name;
|
||||||
|
return org;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRaw(org) {
|
||||||
|
SerializeHelper.overwriteNonObjects(org, this);
|
||||||
|
this.anatomy.loadRaw(org.anatomy)
|
||||||
|
if (org.brain)
|
||||||
|
this.brain.copy(org.brain)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Organism;
|
module.exports = Organism;
|
||||||
|
|||||||
@@ -20,16 +20,18 @@ class Brain {
|
|||||||
this.observations = [];
|
this.observations = [];
|
||||||
|
|
||||||
// corresponds to CellTypes
|
// corresponds to CellTypes
|
||||||
this.decisions = [];
|
this.decisions = {};
|
||||||
this.decisions[CellStates.empty.name] = Decision.neutral;
|
for (let cell of CellStates.all) {
|
||||||
|
this.decisions[cell.name] = Decision.neutral;
|
||||||
|
}
|
||||||
this.decisions[CellStates.food.name] = Decision.chase;
|
this.decisions[CellStates.food.name] = Decision.chase;
|
||||||
this.decisions[CellStates.wall.name] = Decision.neutral;
|
|
||||||
this.decisions[CellStates.mouth.name] = Decision.neutral;
|
|
||||||
this.decisions[CellStates.producer.name] = Decision.neutral;
|
|
||||||
this.decisions[CellStates.mover.name] = Decision.neutral;
|
|
||||||
this.decisions[CellStates.killer.name] = Decision.retreat;
|
this.decisions[CellStates.killer.name] = Decision.retreat;
|
||||||
this.decisions[CellStates.armor.name] = Decision.neutral;
|
}
|
||||||
this.decisions[CellStates.eye.name] = Decision.neutral;
|
|
||||||
|
copy(brain) {
|
||||||
|
for (let dec in brain.decisions) {
|
||||||
|
this.decisions[dec] = brain.decisions[dec];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
randomizeDecisions(randomize_all=false) {
|
randomizeDecisions(randomize_all=false) {
|
||||||
@@ -58,9 +60,7 @@ class Brain {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (obs.distance < closest) {
|
if (obs.distance < closest) {
|
||||||
// console.log(obs.cell.state)
|
|
||||||
decision = this.decisions[obs.cell.state.name];
|
decision = this.decisions[obs.cell.state.name];
|
||||||
// console.log(decision)
|
|
||||||
move_direction = obs.direction;
|
move_direction = obs.direction;
|
||||||
closest = obs.distance;
|
closest = obs.distance;
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,10 @@ class Brain {
|
|||||||
this.decisions[CellStates.getRandomName()] = Decision.getRandom();
|
this.decisions[CellStates.getRandomName()] = Decision.getRandom();
|
||||||
this.decisions[CellStates.empty.name] = Decision.neutral; // if the empty cell has a decision it gets weird
|
this.decisions[CellStates.empty.name] = Decision.neutral; // if the empty cell has a decision it gets weird
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serialize() {
|
||||||
|
return {decisions: this.decisions};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Brain.Decision = Decision;
|
Brain.Decision = Decision;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const CellStates = require("../Organism/Cell/CellStates");
|
const CellStates = require("../Organism/Cell/CellStates");
|
||||||
|
const SerializeHelper = require("../Utils/SerializeHelper");
|
||||||
const Species = require("./Species");
|
const Species = require("./Species");
|
||||||
|
|
||||||
const FossilRecord = {
|
const FossilRecord = {
|
||||||
@@ -18,7 +19,6 @@ const FossilRecord = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addSpecies: function(org, ancestor) {
|
addSpecies: function(org, ancestor) {
|
||||||
// console.log("Adding Species")
|
|
||||||
var new_species = new Species(org.anatomy, ancestor, this.env.total_ticks);
|
var new_species = new Species(org.anatomy, ancestor, this.env.total_ticks);
|
||||||
this.extant_species.push(new_species);
|
this.extant_species.push(new_species);
|
||||||
org.species = new_species;
|
org.species = new_species;
|
||||||
@@ -26,33 +26,30 @@ const FossilRecord = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
addSpeciesObj: function(species) {
|
addSpeciesObj: function(species) {
|
||||||
// console.log("Adding Species")
|
|
||||||
this.extant_species.push(species);
|
this.extant_species.push(species);
|
||||||
return species;
|
return species;
|
||||||
},
|
},
|
||||||
|
|
||||||
fossilize: function(species) {
|
fossilize: function(species) {
|
||||||
// console.log("Extinction")
|
|
||||||
species.end_tick = this.env.total_ticks;
|
species.end_tick = this.env.total_ticks;
|
||||||
for (i in this.extant_species) {
|
for (i in this.extant_species) {
|
||||||
var s = this.extant_species[i];
|
var s = this.extant_species[i];
|
||||||
if (s == species) {
|
if (s == species) {
|
||||||
this.extant_species.splice(i, 1);
|
this.extant_species.splice(i, 1);
|
||||||
|
species.ancestor = undefined; // garbage collect dead species
|
||||||
|
// if (species.ancestor)
|
||||||
|
// species.ancestor.ancestor = undefined;
|
||||||
if (species.cumulative_pop < this.min_pop) {
|
if (species.cumulative_pop < this.min_pop) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// disabled for now, causes memory problems on long runs
|
// disabled for now, causes memory problems on long runs
|
||||||
// this.extinct_species.push(s);
|
// this.extinct_species.push(s);
|
||||||
|
|
||||||
// console.log("Extant species:", this.extant_species.length)
|
|
||||||
// console.log("Extinct species:", this.extinct_species.length)
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resurrect: function(species) {
|
resurrect: function(species) {
|
||||||
// console.log("Resurrecting species")
|
|
||||||
if (species.extinct) {
|
if (species.extinct) {
|
||||||
for (i in this.extinct_species) {
|
for (i in this.extinct_species) {
|
||||||
var s = this.extinct_species[i];
|
var s = this.extinct_species[i];
|
||||||
@@ -67,12 +64,13 @@ const FossilRecord = {
|
|||||||
|
|
||||||
setData() {
|
setData() {
|
||||||
// all parallel arrays
|
// all parallel arrays
|
||||||
this.tick_record = [0];
|
this.tick_record = [];
|
||||||
this.pop_counts = [0];
|
this.pop_counts = [];
|
||||||
this.species_counts = [0];
|
this.species_counts = [];
|
||||||
this.av_mut_rates = [0];
|
this.av_mut_rates = [];
|
||||||
this.av_cells = [0];
|
this.av_cells = [];
|
||||||
this.av_cell_counts = [this.calcCellCountAverages()];
|
this.av_cell_counts = [];
|
||||||
|
this.updateData();
|
||||||
},
|
},
|
||||||
|
|
||||||
updateData() {
|
updateData() {
|
||||||
@@ -121,12 +119,39 @@ const FossilRecord = {
|
|||||||
this.av_cell_counts.push(cell_counts);
|
this.av_cell_counts.push(cell_counts);
|
||||||
},
|
},
|
||||||
|
|
||||||
clear_record: function() {
|
clear_record() {
|
||||||
this.extant_species = [];
|
this.extant_species = [];
|
||||||
this.extinct_species = [];
|
this.extinct_species = [];
|
||||||
this.setData();
|
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();
|
FossilRecord.init();
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
const CellStates = require("../Organism/Cell/CellStates");
|
const CellStates = require("../Organism/Cell/CellStates");
|
||||||
|
let FossilRecord = undefined; // workaround to a circular dependency problem
|
||||||
|
const getFossilRecord = () => {
|
||||||
|
if (!FossilRecord)
|
||||||
|
FossilRecord = require("./FossilRecord");
|
||||||
|
return FossilRecord;
|
||||||
|
}
|
||||||
|
|
||||||
class Species {
|
class Species {
|
||||||
constructor(anatomy, ancestor, start_tick) {
|
constructor(anatomy, ancestor, start_tick) {
|
||||||
this.anatomy = anatomy;
|
this.anatomy = anatomy;
|
||||||
// this.ancestor = ancestor; // garbage collect ancestors to avoid memory problems
|
this.ancestor = ancestor; // eventually need to garbage collect ancestors to avoid memory problems
|
||||||
this.population = 1;
|
this.population = 1;
|
||||||
this.cumulative_pop = 1;
|
this.cumulative_pop = 1;
|
||||||
this.start_tick = start_tick;
|
this.start_tick = start_tick;
|
||||||
this.end_tick = -1;
|
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.extinct = false;
|
||||||
this.calcAnatomyDetails();
|
this.calcAnatomyDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
calcAnatomyDetails() {
|
calcAnatomyDetails() {
|
||||||
|
if (!this.anatomy) return;
|
||||||
var cell_counts = {};
|
var cell_counts = {};
|
||||||
for (let c of CellStates.living) {
|
for (let c of CellStates.living) {
|
||||||
cell_counts[c.name] = 0;
|
cell_counts[c.name] = 0;
|
||||||
@@ -33,8 +40,7 @@ class Species {
|
|||||||
this.population--;
|
this.population--;
|
||||||
if (this.population <= 0) {
|
if (this.population <= 0) {
|
||||||
this.extinct = true;
|
this.extinct = true;
|
||||||
const FossilRecord = require("./FossilRecord");
|
getFossilRecord().fossilize(this);
|
||||||
FossilRecord.fossilize(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/Utils/SerializeHelper.js
Normal file
20
src/Utils/SerializeHelper.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const SerializeHelper = {
|
||||||
|
copyNonObjects(obj) {
|
||||||
|
let newobj = {};
|
||||||
|
for (let key in obj) {
|
||||||
|
if (typeof obj[key] !== 'object')
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SerializeHelper;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './src/index.js',
|
entry: './src/index.js',
|
||||||
@@ -6,7 +7,9 @@ module.exports = {
|
|||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
path: path.resolve(__dirname, 'dist/js/'),
|
path: path.resolve(__dirname, 'dist/js/'),
|
||||||
},
|
},
|
||||||
externals: {
|
plugins: [
|
||||||
jquery: 'jQuery'
|
new webpack.ProvidePlugin({
|
||||||
}
|
$: "jquery",
|
||||||
|
})
|
||||||
|
]
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user