diff --git a/Changelog.md b/Changelog.md index bf73909..844c04a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,14 +3,20 @@ ## 1.0.2 (current development) ### UI Enhancements: -- Button to generate random walls with perlin noise generator +- New tab for world controls + - Relocated grid controls, 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 + - 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 Enhancements: +- ### Bug Fixes: - Armor is no longer ignored when checking for clear reproduction space -- Thanks to contributors: diff --git a/dist/css/style.css b/dist/css/style.css index a0624dc..f0a008f 100644 --- a/dist/css/style.css +++ b/dist/css/style.css @@ -57,11 +57,15 @@ body{ img { image-rendering: -moz-crisp-edges; - image-rendering: -webkit-crisp-edges; - image-rendering: pixelated; - image-rendering: crisp-edges; - width: 60%; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + object-fit: cover; + width: 85%; + max-width: 500px; + max-height: 40%; border-radius: 10px; + overflow: hidden; } button { @@ -134,6 +138,10 @@ button:active{ background-color: #81d2c7; color: black; } +.open-tab { + background-color: #66a39b; + color: black; +} .tab { grid-template-columns: repeat(2, 1fr); @@ -256,4 +264,16 @@ button:active{ } #maximize-hot-control { right: 10px; +} + +.grid-size-in { + width: 75px; +} + +#video { + height: 100%; + max-height: 190px; + margin: auto; + margin-bottom: 0; + padding-bottom: 0; } \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index 5585f26..0835452 100644 --- a/dist/index.html +++ b/dist/index.html @@ -24,58 +24,35 @@ - -

Simulation Speed

+ Life Engine +

Simulation Speed

Target FPS: 60

- -
- -
- - -
-

Auto reset count:

-

Grid Size

- - - - -
- - - - -
-
- +
-

About

+

About

Editor

+

World Controls

Simulation Controls

-

Stats

-

Challenges

+

Statistics

+
- Life Engine -

The Life Engine is a virtual ecosystem that allows organisms to grow, spread, and compete.

-

Each organism is made up by a structure of cells, which provide different benefits based on their color.

- -
-
-

Cell Types

+

The Life Engine

+

The Life Engine is a virtual ecosystem that allows organisms to reproduce, compete, and evolve.

+

Each organism is made up of different colored cells. Hover over each color to learn what it does.

@@ -85,14 +62,13 @@
-
-
-

Hover over each color to learn what it does. For a more in depth explanation of the simulation, view the - readme and you can explore the source code. -

+

+
+
+
@@ -177,6 +153,43 @@ +
+
+

Grid Size

+ + + + +
+ + + + +
+
+ +

Reset Options

+ +
+ + +

Auto reset count:

+ + +
+
+
+
+ +
+ + +
+

Simulation Controls

@@ -186,11 +199,8 @@
- - -
- - + +
diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js index fcbe8b3..f566692 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 WorldConfig = require("../WorldConfig"); class ControlPanel { constructor(engine) { @@ -8,9 +9,9 @@ class ControlPanel { this.defineMinMaxControls(); this.defineHotkeys(); this.defineEngineSpeedControls(); - this.defineGridSizeControls(); this.defineTabNavigation(); this.defineHyperparameterControls(); + this.defineWorldControls(); this.defineModeControls(); this.defineChallenges(); this.fps = engine.fps; @@ -114,11 +115,14 @@ class ControlPanel { defineEngineSpeedControls(){ this.slider = document.getElementById("slider"); this.slider.oninput = function() { - this.fps = this.slider.value + const max_fps = 300; + this.fps = this.slider.value; + if (this.fps>=max_fps) this.fps = 1000; if (this.engine.running) { this.changeEngineSpeed(this.fps); } - $('#fps').text("Target FPS: "+this.fps); + 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"); @@ -134,18 +138,39 @@ class ControlPanel { $('.headless').click(function() { $('.headless').find("i").toggleClass("fa fa-eye"); $('.headless').find("i").toggleClass("fa fa-eye-slash"); - if (Hyperparams.headless){ + if (WorldConfig.headless){ $('#headless-notification').css('display', 'none'); this.engine.env.renderFull(); } else { $('#headless-notification').css('display', 'block'); } - Hyperparams.headless = !Hyperparams.headless; + WorldConfig.headless = !WorldConfig.headless; }.bind(this)); } - defineGridSizeControls() { + defineTabNavigation() { + this.tab_id = 'about'; + var self = this; + $('.tabnav-item').click(function() { + $('.tab').css('display', 'none'); + var tab = '#'+this.id+'.tab'; + $(tab).css('display', 'grid'); + $('.tabnav-item').removeClass('open-tab') + $('#'+this.id+'.tabnav-item').addClass('open-tab'); + self.engine.organism_editor.is_active = (this.id == 'editor'); + self.stats_panel.stopAutoRender(); + if (this.id === 'stats') { + self.stats_panel.startAutoRender(); + } + else if (this.id === 'editor') { + self.editor_controller.refreshDetailsPanel(); + } + self.tab_id = this.id; + }); + } + + defineWorldControls() { $('#fill-window').change(function() { if (this.checked) $('.col-row-input').css('display' ,'none'); @@ -168,25 +193,20 @@ class ControlPanel { this.stats_panel.reset(); }.bind(this)); - } - defineTabNavigation() { - this.tab_id = 'about'; - var self = this; - $('.tabnav-item').click(function() { - $('.tab').css('display', 'none'); - var tab = '#'+this.id+'.tab'; - $(tab).css('display', 'grid'); - self.engine.organism_editor.is_active = (this.id == 'editor'); - self.stats_panel.stopAutoRender(); - if (this.id === 'stats') { - self.stats_panel.startAutoRender(); - } - else if (this.id === 'editor') { - self.editor_controller.refreshDetailsPanel(); - } - self.tab_id = this.id; + $('#auto-reset').change(function() { + WorldConfig.auto_reset = this.checked; }); + $('#auto-pause').change(function() { + WorldConfig.auto_pause = this.checked; + }); + $('#clear-walls-reset').change(function() { + WorldConfig.clear_walls_on_reset = this.checked; + }) + + $('#start-state').change ( function() { + WorldConfig.start_state = $("#start-state").val(); + }.bind(this)); } defineHyperparameterControls() { @@ -197,11 +217,8 @@ class ControlPanel { Hyperparams.lifespanMultiplier = $('#lifespan-multiplier').val(); }.bind(this)); - $('#mover-rot').change(function() { - Hyperparams.moversCanRotate = this.checked; - }); - $('#offspring-rot').change(function() { - Hyperparams.offspringRotate = this.checked; + $('#rot-enabled').change(function() { + Hyperparams.rotationEnabled = this.checked; }); $('#insta-kill').change(function() { Hyperparams.instaKill = this.checked; @@ -258,8 +275,7 @@ class ControlPanel { Hyperparams.setDefaults(); $('#food-prod-prob').val(Hyperparams.foodProdProb); $('#lifespan-multiplier').val(Hyperparams.lifespanMultiplier); - $('#mover-rot').prop('checked', Hyperparams.moversCanRotate); - $('#offspring-rot').prop('checked', Hyperparams.offspringRotate); + $('#rot-enabled').prop('checked', Hyperparams.rotationEnabled); $('#insta-kill').prop('checked', Hyperparams.instaKill); $('#evolved-mutation').prop('checked', !Hyperparams.useGlobalMutability); $('#add-prob').val(Hyperparams.addProb); @@ -324,15 +340,10 @@ class ControlPanel { $('#clear-env').click( () => { env.reset(true, false); this.stats_panel.reset(); - env.auto_reset = false; - $('#auto-reset').prop('checked', false);; }); $('#random-walls').click( function() { this.env_controller.randomizeWalls(); }.bind(this)); - $('#auto-reset').change(function() { - env.auto_reset = this.checked; - }); $('#clear-walls').click( function() { this.engine.env.clearWalls(); }.bind(this)); @@ -406,7 +417,7 @@ class ControlPanel { $('#fps-actual').text("Actual FPS: " + Math.floor(this.engine.actual_fps)); $('#reset-count').text("Auto reset count: " + this.engine.env.reset_count); this.stats_panel.updateDetails(); - if (Hyperparams.headless) + if (WorldConfig.headless) this.updateHeadlessIcon(delta_time); } diff --git a/src/Controllers/EnvironmentController.js b/src/Controllers/EnvironmentController.js index 5e8b963..17da00f 100644 --- a/src/Controllers/EnvironmentController.js +++ b/src/Controllers/EnvironmentController.js @@ -4,7 +4,7 @@ const Modes = require("./ControlModes"); const CellStates = require("../Organism/Cell/CellStates"); const Neighbors = require("../Grid/Neighbors"); const FossilRecord = require("../Stats/FossilRecord"); -const Hyperparams = require("../Hyperparameters"); +const WorldConfig = require("../WorldConfig"); const Perlin = require("../Utils/Perlin"); class EnvironmentController extends CanvasController{ @@ -98,7 +98,7 @@ class EnvironmentController extends CanvasController{ } performModeAction() { - if (Hyperparams.headless && this.mode != Modes.Drag) + if (WorldConfig.headless && this.mode != Modes.Drag) return; var mode = this.mode; var right_click = this.right_click; diff --git a/src/Environments/WorldEnvironment.js b/src/Environments/WorldEnvironment.js index fdf48c3..5c3f9b9 100644 --- a/src/Environments/WorldEnvironment.js +++ b/src/Environments/WorldEnvironment.js @@ -6,6 +6,7 @@ const CellStates = require('../Organism/Cell/CellStates'); const EnvironmentController = require('../Controllers/EnvironmentController'); const Hyperparams = require('../Hyperparameters.js'); const FossilRecord = require('../Stats/FossilRecord'); +const WorldConfig = require('../WorldConfig'); class WorldEnvironment extends Environment{ constructor(cell_size) { @@ -18,7 +19,6 @@ class WorldEnvironment extends Environment{ this.organisms = []; this.walls = []; this.total_mutability = 0; - this.auto_reset = true; this.largest_cell_count = 0; this.reset_count = 0; this.total_ticks = 0; @@ -45,7 +45,7 @@ class WorldEnvironment extends Environment{ } render() { - if (Hyperparams.headless) { + if (WorldConfig.headless) { this.renderer.cells_to_render.clear(); return; } @@ -58,24 +58,37 @@ class WorldEnvironment extends Environment{ } removeOrganisms(org_indeces) { + let start_pop = this.organisms.length; for (var i of org_indeces.reverse()){ this.total_mutability -= this.organisms[i].mutability; this.organisms.splice(i, 1); } - if (this.organisms.length == 0 && this.auto_reset){ - this.reset_count++; - this.reset(false); + if (this.organisms.length === 0 && start_pop > 0) { + if (WorldConfig.auto_pause) + $('.pause-button')[0].click(); + else if(WorldConfig.auto_reset) { + this.reset_count++; + this.reset(false); + } } } OriginOfLife() { var center = this.grid_map.getCenter(); - var org = new Organism(center[0], center[1], this); - org.anatomy.addDefaultCell(CellStates.mouth, 0, 0); - org.anatomy.addDefaultCell(CellStates.producer, 1, 1); - org.anatomy.addDefaultCell(CellStates.producer, -1, -1); - this.addOrganism(org); - FossilRecord.addSpecies(org, null); + switch (WorldConfig.start_state){ + case 'simple-prod': + var org = new Organism(center[0], center[1], this); + org.anatomy.addDefaultCell(CellStates.mouth, 0, 0); + org.anatomy.addDefaultCell(CellStates.producer, 1, 1); + org.anatomy.addDefaultCell(CellStates.producer, -1, -1); + this.addOrganism(org); + FossilRecord.addSpecies(org, null); + break; + case 'random-orgs': + break; + case 'no-orgs': + break; + } } addOrganism(organism) { @@ -136,7 +149,7 @@ class WorldEnvironment extends Environment{ return; this.organisms = []; - this.grid_map.fillGrid(CellStates.empty); + this.grid_map.fillGrid(CellStates.empty, !WorldConfig.clear_walls_on_reset); this.renderer.renderFullGrid(this.grid_map.grid); this.total_mutability = 0; this.total_ticks = 0; diff --git a/src/Grid/GridMap.js b/src/Grid/GridMap.js index 24f167c..84db4ca 100644 --- a/src/Grid/GridMap.js +++ b/src/Grid/GridMap.js @@ -21,9 +21,10 @@ class GridMap { } } - fillGrid(state) { + fillGrid(state, ignore_walls=false) { for (var col of this.grid) { for (var cell of col) { + if (ignore_walls && cell.state===CellStates.wall) continue; cell.setType(state); cell.owner = null; cell.cell_owner = null; diff --git a/src/Hyperparameters.js b/src/Hyperparameters.js index 4199cd9..67ece3c 100644 --- a/src/Hyperparameters.js +++ b/src/Hyperparameters.js @@ -16,8 +16,7 @@ const Hyperparams = { this.changeProb = 33; this.removeProb = 33; - this.moversCanRotate = true; - this.offspringRotate = true; + this.rotationEnabled = true; this.foodBlocksReproduction = true; this.moversCanProduce = false; diff --git a/src/Organism/Cell/GridCell.js b/src/Organism/Cell/GridCell.js index 10d637b..fd3bdff 100644 --- a/src/Organism/Cell/GridCell.js +++ b/src/Organism/Cell/GridCell.js @@ -5,7 +5,7 @@ const Hyperparams = require("../../Hyperparameters"); class Cell{ constructor(state, col, row, x, y){ this.owner = null; // owner organism - this.cell_owner = null; // owner cell of ^that organism + this.cell_owner = null; // specific body cell of the owner organism that occupies this grid cell this.setType(state); this.col = col; this.row = row; diff --git a/src/Organism/Organism.js b/src/Organism/Organism.js index 589bfb9..ef38f03 100644 --- a/src/Organism/Organism.js +++ b/src/Organism/Organism.js @@ -17,7 +17,7 @@ class Organism { this.anatomy = new Anatomy(this) this.direction = Directions.down; // direction of movement this.rotation = Directions.up; // direction of rotation - this.can_rotate = Hyperparams.moversCanRotate; + this.can_rotate = Hyperparams.rotationEnabled; this.move_count = 0; this.move_range = 4; this.ignore_brain_for = 0; @@ -63,7 +63,7 @@ class Organism { //produce mutated child //check nearby locations (is there room and a direct path) var org = new Organism(0, 0, this.env, this); - if(Hyperparams.offspringRotate){ + if(Hyperparams.rotationEnabled){ org.rotation = Directions.getRandomDirection(); } var prob = this.mutability; diff --git a/src/WorldConfig.js b/src/WorldConfig.js new file mode 100644 index 0000000..1923453 --- /dev/null +++ b/src/WorldConfig.js @@ -0,0 +1,9 @@ +const WorldConfig = { + headless: false, + clear_walls_on_reset: false, + start_state: 'simple-prod', + auto_reset: true, + auto_pause: false, +} + +module.exports = WorldConfig; \ No newline at end of file