diff --git a/package-lock.json b/package-lock.json index 7994f85..cf3651c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -222,9 +222,9 @@ "dev": true }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, "ansi-styles": { @@ -1089,24 +1089,24 @@ } }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -1706,9 +1706,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { @@ -2934,9 +2934,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -3569,9 +3569,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yallist": { diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js index 46cda00..ca87c2b 100644 --- a/src/Controllers/ControlPanel.js +++ b/src/Controllers/ControlPanel.js @@ -204,7 +204,6 @@ class ControlPanel { if (!env.reset(true, false)) return; let center = env.grid_map.getCenter(); let org = this.editor_controller.env.getCopyOfOrg(); - this.env_controller.add_new_species = true; this.env_controller.dropOrganism(org, center[0], center[1]) }); $('#save-env').click( () => { @@ -464,8 +463,6 @@ class ControlPanel { if (mode == Modes.Clone) { this.env_controller.org_to_clone = this.engine.organism_editor.getCopyOfOrg(); - this.env_controller.add_new_species = this.editor_controller.new_species; - this.editor_controller.new_species = false; } } diff --git a/src/Controllers/EditorController.js b/src/Controllers/EditorController.js index 38cec42..0c4ddd1 100644 --- a/src/Controllers/EditorController.js +++ b/src/Controllers/EditorController.js @@ -3,6 +3,7 @@ const Modes = require("./ControlModes"); const CellStates = require("../Organism/Cell/CellStates"); const Directions = require("../Organism/Directions"); const Hyperparams = require("../Hyperparameters"); +const Species = require("../Stats/Species"); class EditorController extends CanvasController{ constructor(env, canvas) { @@ -10,7 +11,6 @@ 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(); this.defineSaveLoad(); @@ -46,7 +46,6 @@ class EditorController extends CanvasController{ else if (this.right_click) this.env.removeCellFromOrg(this.mouse_c, this.mouse_r); - this.new_species = true; this.setBrainPanelVisibility(); this.setMoveRangeVisibility(); this.updateDetails(); @@ -134,6 +133,9 @@ class EditorController extends CanvasController{ this.refreshDetailsPanel(); this.env.organism.updateGrid(); this.env.renderFull(); + this.env.organism.species = new Species(this.env.organism.anatomy, null, 0); + if (org.species_name) + this.env.organism.species.name = org.species_name; 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 diff --git a/src/Controllers/EnvironmentController.js b/src/Controllers/EnvironmentController.js index 93f3ddb..61a9d57 100644 --- a/src/Controllers/EnvironmentController.js +++ b/src/Controllers/EnvironmentController.js @@ -12,7 +12,6 @@ class EnvironmentController extends CanvasController{ super(env, canvas); this.mode = Modes.FoodDrop; this.org_to_clone = null; - this.add_new_species = false; this.defineZoomControls(); this.scale = 1; } @@ -171,15 +170,15 @@ class EnvironmentController extends CanvasController{ var new_org = new Organism(col, row, this.env, organism); if (new_org.isClear(col, row)) { - if (this.add_new_species){ + let new_species = !FossilRecord.speciesIsExtant(new_org.species.name); + if (new_org.species.extinct) { + FossilRecord.resurrect(new_org.species); + } + else if (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); - } this.env.addOrganism(new_org); new_org.species.addPop(); diff --git a/src/Environments/OrganismEditor.js b/src/Environments/OrganismEditor.js index 6aadc0b..f01d9ed 100644 --- a/src/Environments/OrganismEditor.js +++ b/src/Environments/OrganismEditor.js @@ -72,7 +72,6 @@ class OrganismEditor extends Environment{ this.organism = new Organism(center[0], center[1], this, orig_org); this.organism.updateGrid(); this.controller.updateDetails(); - this.controller.new_species = false; } getCopyOfOrg() { @@ -109,7 +108,6 @@ class OrganismEditor extends Environment{ newOrganism.species = new Species(newOrganism.anatomy, null, 0); var col = Math.floor(size + (Math.random() * (env.grid_map.cols-(size*2)) ) ); var row = Math.floor(size + (Math.random() * (env.grid_map.rows-(size*2)) ) ); - env.controller.add_new_species = true; env.controller.dropOrganism(newOrganism, col, row); } } diff --git a/src/Environments/WorldEnvironment.js b/src/Environments/WorldEnvironment.js index 5e89fef..0ca05b5 100644 --- a/src/Environments/WorldEnvironment.js +++ b/src/Environments/WorldEnvironment.js @@ -36,10 +36,10 @@ class WorldEnvironment extends Environment{ to_remove.push(i); } } + this.removeOrganisms(to_remove); if (Hyperparams.foodDropProb > 0) { this.generateFood(); } - this.removeOrganisms(to_remove); this.total_ticks ++; if (this.total_ticks % this.data_update_rate == 0) { FossilRecord.updateData(); @@ -122,6 +122,16 @@ class WorldEnvironment extends Environment{ org.die(); this.organisms = []; } + + clearDeadOrganisms() { + let to_remove = []; + for (let i in this.organisms) { + let org = this.organisms[i]; + if (!org.living) + to_remove.push(i); + } + this.removeOrganisms(to_remove); + } generateFood() { var num_food = Math.max(Math.floor(this.grid_map.cols*this.grid_map.rows*Hyperparams.foodDropProb/50000), 1) @@ -168,6 +178,7 @@ class WorldEnvironment extends Environment{ } serialize() { + this.clearDeadOrganisms(); let env = SerializeHelper.copyNonObjects(this); env.grid = this.grid_map.serialize(); env.organisms = []; @@ -198,7 +209,7 @@ class WorldEnvironment extends Environment{ 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 + if (!s){ // ideally, every organisms species should exists, but there is a bug that misses some species sometimes s = new Species(org.anatomy, null, env.total_ticks); species[orgRaw.species_name] = s; } diff --git a/src/Stats/FossilRecord.js b/src/Stats/FossilRecord.js index 17ec347..2a295ae 100644 --- a/src/Stats/FossilRecord.js +++ b/src/Stats/FossilRecord.js @@ -4,8 +4,8 @@ const Species = require("./Species"); const FossilRecord = { init: function(){ - this.extant_species = []; - this.extinct_species = []; + this.extant_species = {}; + this.extinct_species = {}; // if an organism has fewer than this cumulative pop, discard them on extinction this.min_discard = 10; @@ -20,45 +20,44 @@ const FossilRecord = { addSpecies: function(org, ancestor) { var new_species = new Species(org.anatomy, ancestor, this.env.total_ticks); - this.extant_species.push(new_species); + this.extant_species[new_species.name] = new_species; org.species = new_species; return new_species; }, addSpeciesObj: function(species) { - this.extant_species.push(species); + if (this.extant_species[species.name]) { + console.warn('Tried to add already existing species. Add failed.'); + return; + } + this.extant_species[species.name] = species; return species; }, + numExtantSpecies() {return Object.values(this.extant_species).length}, + numExtinctSpecies() {return Object.values(this.extinct_species).length}, + speciesIsExtant(species_name) {return !!this.extant_species[species_name]}, + fossilize: function(species) { - 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); - species.ancestor = undefined; // garbage collect dead species - // if (species.ancestor) - // species.ancestor.ancestor = undefined; - if (species.cumulative_pop < this.min_pop) { - return false; - } - // disabled for now, causes memory problems on long runs - // this.extinct_species.push(s); - return true; - } + if (!this.extant_species[species.name]) { + console.warn('Tried to fossilize non existing species.'); + return false; } + species.end_tick = this.env.total_ticks; + species.ancestor = undefined; // garbage collect ancestors + delete this.extant_species[species.name]; + if (species.cumulative_pop >= this.min_discard) { + // TODO: store as extinct species + return true; + } + return false; }, resurrect: function(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; - } - } + species.extinct = false; + this.extant_species[species.name] = species; + delete this.extinct_species[species.name]; } }, @@ -77,7 +76,7 @@ const FossilRecord = { 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.species_counts.push(this.numExtantSpecies()); this.av_mut_rates.push(this.env.averageMutability()); this.calcCellCountAverages(); while (this.tick_record.length > this.record_size_limit) { @@ -97,8 +96,8 @@ const FossilRecord = { cell_counts[c.name] = 0; } var first=true; - for (let s of this.extant_species) { - if (s.cumulative_pop < this.min_discard && !first){ + for (let s of Object.values(this.extant_species)) { + if (!first && this.numExtantSpecies() > 10 && s.cumulative_pop < this.min_discard){ continue; } for (let name in s.cell_counts) { @@ -107,8 +106,11 @@ const FossilRecord = { total_org += s.population; first=false; } - if (total_org == 0) - return cell_counts; + if (total_org == 0) { + this.av_cells.push(0); + this.av_cell_counts.push(cell_counts); + return; + } var total_cells = 0; for (let c in cell_counts) { @@ -137,7 +139,7 @@ const FossilRecord = { av_cell_counts:this.av_cell_counts, }; let species = {}; - for (let s of this.extant_species) { + for (let s of Object.values(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 } diff --git a/src/Stats/StatsPanel.js b/src/Stats/StatsPanel.js index 4487861..e7392e4 100644 --- a/src/Stats/StatsPanel.js +++ b/src/Stats/StatsPanel.js @@ -50,7 +50,7 @@ class StatsPanel { 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); + $('#species-count').text("Number of Species: " + FossilRecord.numExtantSpecies()); $('#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);