diff --git a/Changelog.md b/Changelog.md index 844c04a..c6df355 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,23 +3,29 @@ ## 1.0.2 (current development) ### UI Enhancements: -- New tab for world controls - - Relocated grid controls, auto reset to this tab +- New tab "World Controls" + - Relocated grid controls and auto reset to this tab - Button to generate random walls with perlin noise - - Options for starting state, including simple producer and empty state - - Option to not clear walls when resetting + - Button to reset the environment with many randomly generated organisms + - Option to not clear walls on reset - Option to pause on total extinction -- Combined `Movers can rotate` and `Offspring rotate` simulation controls into `Rotation enabled` -- Can now drag view while rendering is off +- "Simulation controls" tab renamed to "Evolution Controls" +- Button to save/load Evolution Controls in a `.json` file +- Button to randomize the organism in the editor window +- Can now use drag view tool while rendering is off +- Reorganized "About" tab and left panel, embedded explanation video ### Simulation Enhancements: -- +- New evolution control `Extra Mover Reproduction Cost`, which adds additional food cost for movers to reproduce +- Combined `Movers can rotate` and `Offspring rotate` evolution controls into `Rotation enabled` +- Fully max out simulation speed when slider is all the way to the right ### Bug Fixes: - Armor is no longer ignored when checking for clear reproduction space +- Chart data is now properly loaded/discarded when paused -Thanks to contributors: +Thanks to contributors: @Chrispykins @M4YX0R ## 1.0.1 (12/4/2021) diff --git a/dist/css/style.css b/dist/css/style.css index ea7e16e..5c7d20c 100644 --- a/dist/css/style.css +++ b/dist/css/style.css @@ -275,4 +275,8 @@ button:active{ margin: auto; margin-bottom: 0; padding-bottom: 0; +} + +#reset-with-editor-org{ + margin-top: 5px; } \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index 6342042..d2fe274 100644 --- a/dist/index.html +++ b/dist/index.html @@ -42,9 +42,8 @@

About

Editor

World Controls

-

Simulation Controls

+

Evolution Controls

Statistics

- @@ -96,6 +95,8 @@ + +
@@ -125,7 +126,7 @@ -
+

Brain

@@ -150,7 +151,8 @@

Move Away From: killer

- + +
@@ -166,23 +168,20 @@
-

Reset Options

- -

Auto reset count:


+ + +
+


@@ -192,7 +191,7 @@
-

Simulation Controls

+

Evolution Controls


@@ -236,8 +235,8 @@
- - + +
@@ -264,22 +263,6 @@
-
-
-

Challenges

- - - - - - -
-
-

Select a Challenge

-
-

Challenge yourself to create interesting ecosystems and organisms. There is no formal way to win or lose, its just for fun!

-
-
diff --git a/src/Controllers/ControlModes.js b/src/Controllers/ControlModes.js index 882e500..3213e4e 100644 --- a/src/Controllers/ControlModes.js +++ b/src/Controllers/ControlModes.js @@ -6,7 +6,7 @@ const Modes = { Select: 4, Edit: 5, Clone: 6, - Drag: 7 + Drag: 7, } module.exports = Modes; \ No newline at end of file diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js index 59bc4d5..f57b94b 100644 --- a/src/Controllers/ControlPanel.js +++ b/src/Controllers/ControlPanel.js @@ -1,6 +1,7 @@ const Hyperparams = require("../Hyperparameters"); const Modes = require("./ControlModes"); const StatsPanel = require("../Stats/StatsPanel"); +const RandomOrganismGenerator = require("../Organism/RandomOrganismGenerator") const WorldConfig = require("../WorldConfig"); class ControlPanel { @@ -13,7 +14,6 @@ class ControlPanel { this.defineHyperparameterControls(); this.defineWorldControls(); this.defineModeControls(); - this.defineChallenges(); this.fps = engine.fps; this.organism_record=0; this.env_controller = this.engine.env.controller; @@ -124,17 +124,12 @@ class ControlPanel { let text = this.fps >= max_fps ? 'MAX' : this.fps; $('#fps').text("Target FPS: "+text); }.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(); - } - else if (!this.engine.running){ - this.engine.start(this.fps); - } + // toggle pause + this.setPaused(this.engine.running); }.bind(this)); + $('.headless').click(function() { $('.headless').find("i").toggleClass("fa fa-eye"); $('.headless').find("i").toggleClass("fa fa-eye-slash"); @@ -202,11 +197,15 @@ class ControlPanel { }); $('#clear-walls-reset').change(function() { WorldConfig.clear_walls_on_reset = this.checked; - }) - - $('#start-state').change ( function() { - WorldConfig.start_state = $("#start-state").val(); - }.bind(this)); + }); + $('#reset-with-editor-org').click( () => { + let env = this.engine.env; + 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]) + }); } defineHyperparameterControls() { @@ -230,7 +229,6 @@ class ControlPanel { Hyperparams.foodDropProb = $('#food-drop-rate').val(); }); $('#extra-mover-cost').change(function() { - console.log(parseInt($('#extra-mover-cost').val())) Hyperparams.extraMoverFoodCost = parseInt($('#extra-mover-cost').val()); }); @@ -359,7 +357,6 @@ class ControlPanel { $('.edit-mode-btn').removeClass('selected'); $('.'+this.id).addClass('selected'); }); - $('.reset-view').click( function(){ this.env_controller.resetView(); }.bind(this)); @@ -382,7 +379,14 @@ class ControlPanel { $('#clear-editor').click( function() { this.engine.organism_editor.clear(); this.editor_controller.setEditorPanel(); - }.bind(this)) + }.bind(this)); + $('#generate-random').click( function() { + this.engine.organism_editor.createRandom(); + this.editor_controller.refreshDetailsPanel(); + }.bind(this)); + $('.reset-random').click( function() { + this.engine.organism_editor.resetWithRandomOrgs(this.engine.env); + }.bind(this)); window.onbeforeunload = function (e) { e = e || window.event; @@ -394,11 +398,22 @@ class ControlPanel { }; } - defineChallenges() { - $('.challenge-btn').click(function() { - $('#challenge-title').text($(this).text()); - $('#challenge-description').text($(this).val()); - }); + setPaused(paused) { + + if (paused) { + + $('.pause-button').find("i").removeClass("fa-pause"); + $('.pause-button').find("i").addClass("fa-play"); + if (this.engine.running) + this.engine.stop(); + } + else if (!paused) { + + $('.pause-button').find("i").addClass("fa-pause"); + $('.pause-button').find("i").removeClass("fa-play"); + if (!this.engine.running) + this.engine.start(this.fps); + } } setMode(mode) { @@ -429,7 +444,7 @@ class ControlPanel { } updateHeadlessIcon(delta_time) { - if (this.paused) + if (!this.engine.running) return; const min_opacity = 0.4; var op = this.headless_opacity + (this.opacity_change_rate*delta_time/1000); diff --git a/src/Controllers/EditorController.js b/src/Controllers/EditorController.js index f0c101e..d5c2cb7 100644 --- a/src/Controllers/EditorController.js +++ b/src/Controllers/EditorController.js @@ -112,6 +112,7 @@ class EditorController extends CanvasController{ clearDetailsPanel() { $('#organism-details').css('display', 'none'); $('#edit-organism-details').css('display', 'none'); + $('#randomize-organism-details').css('display', 'none'); } refreshDetailsPanel() { @@ -213,6 +214,11 @@ class EditorController extends CanvasController{ var reaction = this.env.organism.brain.decisions[name]; $('#reaction-edit').val(reaction); } + + setRandomizePanel() { + this.clearDetailsPanel(); + $('#randomize-organism-details').css('display', 'block'); + } } module.exports = EditorController; diff --git a/src/Controllers/EnvironmentController.js b/src/Controllers/EnvironmentController.js index 17da00f..93f3ddb 100644 --- a/src/Controllers/EnvironmentController.js +++ b/src/Controllers/EnvironmentController.js @@ -141,21 +141,7 @@ 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); - new_org.species.addPop(); - } + this.dropOrganism(this.org_to_clone, this.mouse_c, this.mouse_r); } break; case Modes.Drag: @@ -179,6 +165,29 @@ class EnvironmentController extends CanvasController{ } } + dropOrganism(organism, col, row) { + + // close the organism and drop it in the world + var new_org = new Organism(col, row, this.env, organism); + + if (new_org.isClear(col, row)) { + 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); + } + + this.env.addOrganism(new_org); + new_org.species.addPop(); + return true; + } + return false; + } + dropCellType(col, row, state, killBlocking=false) { for (var loc of Neighbors.allSelf){ var c=col + loc[0]; diff --git a/src/Environments/OrganismEditor.js b/src/Environments/OrganismEditor.js index b969774..6aadc0b 100644 --- a/src/Environments/OrganismEditor.js +++ b/src/Environments/OrganismEditor.js @@ -5,6 +5,7 @@ const Renderer = require('../Rendering/Renderer'); const CellStates = require('../Organism/Cell/CellStates'); const EditorController = require("../Controllers/EditorController"); const Species = require('../Stats/Species'); +const RandomOrganismGenerator = require('../Organism/RandomOrganismGenerator') class OrganismEditor extends Environment{ constructor() { @@ -87,6 +88,31 @@ class OrganismEditor extends Environment{ this.organism.updateGrid(); this.organism.species = new Species(this.organism.anatomy, null, 0); } + + createRandom() { + this.grid_map.fillGrid(CellStates.empty); + + this.organism = RandomOrganismGenerator.generate(this); + this.organism.updateGrid(); + this.organism.species = new Species(this.organism.anatomy, null, 0); + } + + resetWithRandomOrgs(env) { + let reset_confirmed = env.reset(true, false); + if (!reset_confirmed) return; + let numOrganisms = parseInt($('#num-random-orgs').val()); + + let size = Math.ceil(8); + + for (let i=0; i0) { - newest_t = this.data[0].dataPoints[this.data[0].dataPoints.length-1].x; - newest_t = this.data[0].dataPoints[0].x; + let record_size = FossilRecord.tick_record.length; + let data_points = this.data[0].dataPoints; + let newest_t = -1; + if (data_points.length>0) { + newest_t = this.data[0].dataPoints[data_points.length-1].x; } - if (newest_t < FossilRecord.tick_record[r_len-1]) { - this.addNewest(); + let to_add = 0; + let cur_t = FossilRecord.tick_record[record_size-1]; + // first count up the number of new datapoints the chart is missing + while (cur_t !== newest_t) { + to_add++; + cur_t = FossilRecord.tick_record[record_size-to_add-1] } - if (oldest_t < FossilRecord.tick_record[0]) { + // then add them in order + this.addNewest(to_add) + + // remove oldest datapoints until the chart is the same size as the saved records + while (data_points.length > FossilRecord.tick_record.length) { this.removeOldest(); } } - addNewest() { - var i = FossilRecord.tick_record.length-1; - this.addDataPoint(i); - } - - addDataPoint(i) { - alert("Must override addDataPoint") + addNewest(to_add) { + for (let i=to_add; i>0; i--) { + let j = FossilRecord.tick_record.length-i; + this.addDataPoint(j); + } } removeOldest() { @@ -75,6 +80,10 @@ class ChartController { } } + addDataPoint(i) { + alert("Must override addDataPoint") + } + clear() { this.data.length = 0; this.chart.render(); diff --git a/src/Stats/FossilRecord.js b/src/Stats/FossilRecord.js index c33b822..3189f7a 100644 --- a/src/Stats/FossilRecord.js +++ b/src/Stats/FossilRecord.js @@ -82,8 +82,7 @@ const FossilRecord = { 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) { + while (this.tick_record.length > this.record_size_limit) { this.tick_record.shift(); this.pop_counts.shift(); this.species_counts.shift(); diff --git a/src/WorldConfig.js b/src/WorldConfig.js index 1923453..2c8f432 100644 --- a/src/WorldConfig.js +++ b/src/WorldConfig.js @@ -1,7 +1,6 @@ const WorldConfig = { headless: false, clear_walls_on_reset: false, - start_state: 'simple-prod', auto_reset: true, auto_pause: false, }