Merge pull request #82 from MaxRobinsonTheGreat/new-tab

World Controls tab
This commit is contained in:
Max Robinson
2021-12-18 13:52:36 -06:00
committed by GitHub
11 changed files with 175 additions and 106 deletions

View File

@@ -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:

28
dist/css/style.css vendored
View File

@@ -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;
}

98
dist/index.html vendored
View File

@@ -24,58 +24,35 @@
<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 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 id="clear-walls" title="Clear All Walls. Hotkey: B"><i class="fa fa-window-close"></i></button>
</div>
<h2>Simulation Speed</h2>
<img src="./img/title.png" alt="Life Engine">
<h3>Simulation Speed</h3>
<input id="slider" type="range" min="1" max="300" value="60">
<button class='pause-button' title="Play/Pause. Hotkey: Spacebar"><i class="fa fa-pause"></i></button>
<button class="headless" title="Toggle rendering. Hotkey: H"><i class="fa fa-eye-slash"></i></button>
<p id='fps'>Target FPS: 60</p>
<p id='fps-actual'></p>
<button id='reset-env' title='Restarts simulation with default organism.'>Reset</button>
<button id='clear-env' title="Removes all organisms and walls. Will disable 'reset on extinction'"">Clear</button>
<br>
<button id='random-walls' title="Generates random walls.">Random Walls</button>
<br>
<label for="auto-reset">Reset on total extinction</label>
<input type="checkbox" id="auto-reset" checked>
<br>
<p id='reset-count'>Auto reset count: </p>
<h3>Grid Size</h3>
<label for="cell-size">Cell Size:</label>
<input type="number" id="cell-size" min="1" max="100" value=5 step="1">
<label for="fill-window">Fill Window</label>
<input type="checkbox" id="fill-window" checked>
<div class='col-row-input'>
<label for="col-input">Columns:</label>
<input type="number" id="col-input" min="1" value=100 step="1">
<label for="row-input">Rows:</label>
<input type="number" id="row-input" min="1" value=100 step="1">
</div>
<br>
<button id='resize'>Resize and Reset</button>
<button id='clear-env' title="Removes all organisms.">Clear</button>
</div>
<div id='tab-container' class='control-set'>
<div class="tabnav">
<p class='tabnav-item' id='about'>About</p>
<p class='tabnav-item open-tab' id='about'>About</p>
<p class='tabnav-item' id='editor'>Editor</p>
<p class='tabnav-item' id='world-controls'>World Controls</p>
<p class='tabnav-item' id='hyperparameters'>Simulation Controls</p>
<p class='tabnav-item' id='stats'>Stats</p>
<p class='tabnav-item' id='challenges'>Challenges</p>
<p class='tabnav-item' id='stats'>Statistics</p>
<!-- <p class='tabnav-item' id='challenges'>Challenges</p> -->
<button id="minimize" title="Minimze Control Panel."><i class="fa fa-minus-square"></i></button>
</div>
<div id='about' class='tab'>
<div class='left-half'>
<img src="./img/title.png" alt="Life Engine">
<p>The Life Engine is a virtual ecosystem that allows organisms to grow, spread, and compete.</p>
<p>Each organism is made up by a structure of cells, which provide different benefits based on their color.</p>
</div>
<div class='right-half'>
<h4>Cell Types</h4>
<h3>The Life Engine</h3>
<p>The Life Engine is a virtual ecosystem that allows organisms to reproduce, compete, and evolve.</p>
<p>Each organism is made up of different colored cells. Hover over each color to learn what it does.</p>
<div id='cell-legend'>
<div class='cell-legend-type' id='mouth' title="Mouth: Eats adjacent food."></div>
<div class='cell-legend-type' id='producer' title="Producer: Produces adjacent food."></div>
@@ -85,14 +62,13 @@
<div class='cell-legend-type' id='eye' title="Eye: Observes cells and decides movement."></div>
<div class='cell-legend-type' id='food' title="Food: Not part of an organism. Once an organism has eaten enough food, it will reproduce."></div>
<div class='cell-legend-type' id='wall' title="Wall: Not part of an organism. Blocks movement and reproduction."></div>
</div>
<br>
<p>Hover over each color to learn what it does. For a more in depth explanation of the simulation, view the
<a href="https://github.com/MaxRobinsonTheGreat/EvolutionSimulatorV2#readme">readme</a> and you can explore the source code.
</p>
</div><br>
</div>
<div class='right-half'>
<iframe id="video" src="https://www.youtube.com/embed/iSAKEnRfles"></iframe>
</div>
<div class='icon-links'>
<a href="https://www.youtube.com/channel/UCwBhBDsqiQflTMLy2epbQVw"><i class="fa fa-youtube"></i></a>
<a href=https://github.com/MaxRobinsonTheGreat/EvolutionSimulatorV2 title='View the code'><i class="fa fa-github"></i></a>
<a href="https://twitter.com/max_romana"><i class="fa fa-twitter"></i></a>
</div>
</div>
@@ -177,6 +153,43 @@
</div>
</div>
<div id='world-controls' class='tab'>
<div class='left-half'>
<h3>Grid Size</h3>
<label for="cell-size">Cell Size:</label>
<input type="number" id="cell-size" min="1" max="100" value=5 step="1">
<label for="fill-window">Fill Window</label>
<input type="checkbox" id="fill-window" checked>
<div class='col-row-input'>
<label for="col-input">Columns:</label>
<input type="number" class="grid-size-in" id="col-input" min="1" value=100 step="1">
<label for="row-input">Rows:</label>
<input type="number" class="grid-size-in" id="row-input" min="1" value=100 step="1">
</div>
<br>
<button id='resize'>Resize and Reset</button>
<h3>Reset Options</h3>
<label for="start-state">Starting state:</label>
<select name="start-state" id="start-state">
<option value="simple-prod">Simple producer</option>
<option value="rand-orgs">Random organisms</option>
<option value="no-orgs">No organisms</option>
</select> <br>
<label for="auto-reset">Reset on total extinction</label>
<input type="checkbox" id="auto-reset" checked>
<p id='reset-count'>Auto reset count: </p>
<label for="auto-pause" title='Will override reset on extinction'>Pause on total extinction</label>
<input type="checkbox" id="auto-pause">
<br>
</div>
<div class='right-half'>
<button id='random-walls' title="Generates random walls.">Generate random walls</button> <br>
<button id="clear-walls" title="Clear All Walls. Hotkey: B">Clear all walls</button>
<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">
</div>
</div>
<div id='hyperparameters' class='tab'>
<div class='left-half'>
<h2>Simulation Controls</h2>
@@ -186,11 +199,8 @@
<label for="lifespan-multiplier" title='An organism lives for this many ticks per cell in its body.'>Lifespan multiplier:</label>
<input type="number" id="lifespan-multiplier" min="1" max="10000" value=100 step="1">
<br>
<label for="mover-rot" title='Movers rotate randomly when they change directions.'>Movers can rotate</label>
<input type="checkbox" id="mover-rot" checked>
<br>
<label for="offspring-rot" title='Offspring will randomly rotate'>Offspring rotate</label>
<input type="checkbox" id="offspring-rot" checked>
<label for="rot-enabled" title='Organisms rotate when born and while moving.'>Rotation Enabled</label>
<input type="checkbox" id="rot-enabled" checked>
<br>
<label for="insta-kill" title='When on, killer cells immediatly kill organisms they touch. When off, organisms have as much health as they have cells and only take 1 damage from killer cells.'>One touch kill</label>
<input type="checkbox" id="insta-kill">

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

9
src/WorldConfig.js Normal file
View File

@@ -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;