diff --git a/dist/css/style.css b/dist/css/style.css index f98f464..86af6b0 100644 --- a/dist/css/style.css +++ b/dist/css/style.css @@ -169,6 +169,9 @@ button:hover{ .edit-mode-btn#drag-view { background-color: #81d2c7; } +.randomize-button { + margin-top: 5px; +} #clear-walls { margin-top: 5px; } diff --git a/dist/index.html b/dist/index.html index f833752..bd57ef2 100644 --- a/dist/index.html +++ b/dist/index.html @@ -100,6 +100,7 @@
+
@@ -169,6 +170,24 @@
+
diff --git a/src/Controllers/ControlModes.js b/src/Controllers/ControlModes.js index 882e500..97ecfa2 100644 --- a/src/Controllers/ControlModes.js +++ b/src/Controllers/ControlModes.js @@ -6,7 +6,8 @@ const Modes = { Select: 4, Edit: 5, Clone: 6, - Drag: 7 + Drag: 7, + Randomize: 8, } module.exports = Modes; \ No newline at end of file diff --git a/src/Controllers/ControlPanel.js b/src/Controllers/ControlPanel.js index ab5eaa5..7d92c53 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") class ControlPanel { constructor(engine) { @@ -21,7 +22,7 @@ class ControlPanel { this.stats_panel = new StatsPanel(this.engine.env); this.headless_opacity = 1; this.opacity_change_rate = -0.8; - this.paused=false; + //this.paused=false; } defineMinMaxControls(){ @@ -84,17 +85,12 @@ class ControlPanel { } $('#fps').text("Target FPS: "+this.fps); }.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"); @@ -263,6 +259,9 @@ class ControlPanel { self.setMode(Modes.Edit); self.editor_controller.setEditorPanel(); break; + case "randomize": + self.setMode(Modes.Randomize); + self.editor_controller.setRandomizePanel(); case "drop-org": self.setMode(Modes.Clone); self.env_controller.org_to_clone = self.engine.organism_editor.getCopyOfOrg(); @@ -298,6 +297,24 @@ class ControlPanel { this.engine.organism_editor.clear(); this.editor_controller.setEditorPanel(); }.bind(this)); + document.getElementById("random-width").addEventListener('input', function() { + var width = 2 * this.value + 1; + $('#random-width-display').text(width); + RandomOrganismGenerator.organismLayers = this.value; + }); + document.getElementById("cell-spawn-chance").addEventListener("input", function() { + var value = parseFloat(this.value); + $('#spawn-chance-display').text((value * 100).toFixed(1) + "%"); + RandomOrganismGenerator.cellSpawnChance = value; + }); + $('#generate-random').click( function() { + this.engine.organism_editor.createRandom(); + }.bind(this)); + + $('#create-random-world').click( function() { + this.setPaused(true); + this.engine.organism_editor.createRandomWorld(this.engine.env); + }.bind(this)); } defineChallenges() { @@ -307,6 +324,20 @@ class ControlPanel { }); } + setPaused(paused) { + + if (paused) { + $('.pause-button').find("i").removeClass("fa-pause"); + $('.pause-button').find("i").addClass("fa-play"); + this.engine.stop(); + } + else if (!paused) { + $('.pause-button').find("i").addClass("fa-pause"); + $('.pause-button').find("i").removeClass("fa-play"); + this.engine.start(this.fps); + } + } + setMode(mode) { this.env_controller.mode = mode; this.editor_controller.mode = mode; @@ -325,7 +356,7 @@ class ControlPanel { } updateHeadlessIcon(delta_time) { - if (this.paused) + if (this.engine.running) return; var op = this.headless_opacity + (this.opacity_change_rate*delta_time/1000); if (op <= 0.4){ diff --git a/src/Controllers/EditorController.js b/src/Controllers/EditorController.js index e7c2152..2a47afa 100644 --- a/src/Controllers/EditorController.js +++ b/src/Controllers/EditorController.js @@ -108,6 +108,7 @@ class EditorController extends CanvasController{ clearDetailsPanel() { $('#organism-details').css('display', 'none'); $('#edit-organism-details').css('display', 'none'); + $('#randomize-organism-details').css('display', 'none'); } setDetailsPanel() { @@ -193,6 +194,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 451d2a9..cf0c644 100644 --- a/src/Controllers/EnvironmentController.js +++ b/src/Controllers/EnvironmentController.js @@ -122,21 +122,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: @@ -151,6 +137,26 @@ 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 (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(); + } + } + 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..c9d48e1 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,38 @@ 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); + } + + createRandomWorld(worldEnvironment) { + + worldEnvironment.clear(); + + var numOrganismCols = Math.floor(worldEnvironment.grid_map.cols / this.grid_map.cols); + var numOrganismRows = Math.floor(worldEnvironment.grid_map.rows / this.grid_map.rows); + var center = this.grid_map.getCenter(); + + for (var x = 0; x < numOrganismCols; x++) { + for (var y = 0; y < numOrganismRows; y++) { + + var newOrganism = RandomOrganismGenerator.generate(this); + //newOrganism.updateGrid(); + newOrganism.species = new Species(newOrganism.anatomy, null, 0); + + var col = x * this.grid_map.cols + center[0]; + var row = y * this.grid_map.rows + center[1]; + worldEnvironment.controller.add_new_species = true; + worldEnvironment.controller.dropOrganism(newOrganism, col, row); + } + } + } } module.exports = OrganismEditor; \ No newline at end of file diff --git a/src/Environments/WorldEnvironment.js b/src/Environments/WorldEnvironment.js index da7bb40..1470b0e 100644 --- a/src/Environments/WorldEnvironment.js +++ b/src/Environments/WorldEnvironment.js @@ -131,13 +131,17 @@ class WorldEnvironment extends Environment{ } reset() { + this.clear(); + this.OriginOfLife(); + } + + clear() { this.organisms = []; this.grid_map.fillGrid(CellStates.empty); this.renderer.renderFullGrid(this.grid_map.grid); this.total_mutability = 0; this.total_ticks = 0; FossilRecord.clear_record(); - this.OriginOfLife(); } resizeGridColRow(cell_size, cols, rows) { diff --git a/src/Organism/Anatomy.js b/src/Organism/Anatomy.js index 8175024..b1c3cb6 100644 --- a/src/Organism/Anatomy.js +++ b/src/Organism/Anatomy.js @@ -91,6 +91,22 @@ class Anatomy { getRandomCell() { return this.cells[Math.floor(Math.random() * this.cells.length)]; } + + getNeighborsOfCell(col, row) { + + var neighbors = []; + + for (var x = -1; x <= 1; x++) { + for (var y = -1; y <= 1; y++) { + + var neighbor = this.getLocalCell(col + x, row + y); + if (neighbor) + neighbors.push(neighbor) + } + } + + return neighbors; + } } module.exports = Anatomy; \ No newline at end of file diff --git a/src/Organism/Cell/CellStates.js b/src/Organism/Cell/CellStates.js index 4a01aa7..9bf54f8 100644 --- a/src/Organism/Cell/CellStates.js +++ b/src/Organism/Cell/CellStates.js @@ -95,6 +95,7 @@ const CellStates = { return this.living[Math.floor(Math.random() * this.living.length)]; } } + CellStates.defineLists(); module.exports = CellStates; diff --git a/src/Organism/Perception/Brain.js b/src/Organism/Perception/Brain.js index e52d8a3..6aba956 100644 --- a/src/Organism/Perception/Brain.js +++ b/src/Organism/Perception/Brain.js @@ -79,4 +79,6 @@ class Brain { } } +Brain.Decision = Decision; + module.exports = Brain; \ No newline at end of file diff --git a/src/Organism/RandomOrganismGenerator.js b/src/Organism/RandomOrganismGenerator.js new file mode 100644 index 0000000..3379e6f --- /dev/null +++ b/src/Organism/RandomOrganismGenerator.js @@ -0,0 +1,77 @@ +const CellStates = require("./Cell/CellStates"); +const Organism = require("./Organism"); +const Brain = require("./Perception/Brain") + +class RandomOrganismGenerator { + + static generate(env) { + + var center = env.grid_map.getCenter(); + var organism = new Organism(center[0], center[1], env, null); + organism.anatomy.addDefaultCell(CellStates.mouth, 0, 0); + + var outermostLayer = RandomOrganismGenerator.organismLayers; + var x, y; + + // iterate from center to edge of organism + // layer 0 is the central cell of the organism + for (var layer = 1; layer <= outermostLayer; layer++) { + + var someCellSpawned = false; + var spawnChance = RandomOrganismGenerator.cellSpawnChance * 1 - ((layer - 1) / outermostLayer); + + // top + y = -layer; + for (x = -layer; x <= layer; x++) + someCellSpawned = RandomOrganismGenerator.trySpawnCell(organism, x, y, spawnChance); + + // bottom + y = layer; + for (x = -layer; x <= layer; x++) + someCellSpawned = RandomOrganismGenerator.trySpawnCell(organism, x, y, spawnChance); + + // left + x = -layer; + for (y = -layer + 1; y <= layer - 1; y++) + someCellSpawned = RandomOrganismGenerator.trySpawnCell(organism, x, y, spawnChance); + + // right + x = layer; + for (y = -layer + 1; y < layer - 1; y++) + someCellSpawned = RandomOrganismGenerator.trySpawnCell(organism, x, y, spawnChance); + + if (!someCellSpawned) + break; + } + + // randomize the organism's brain + var decisions = organism.brain.decisions; + decisions[CellStates.empty.name] = Brain.Decision.getRandom(); + decisions[CellStates.food.name] = Brain.Decision.getRandom(); + decisions[CellStates.wall.name] = Brain.Decision.getRandom(); + decisions[CellStates.mouth.name] = Brain.Decision.getRandom(); + decisions[CellStates.producer.name] = Brain.Decision.getRandom(); + decisions[CellStates.mover.name] = Brain.Decision.getRandom(); + decisions[CellStates.killer.name] = Brain.Decision.getRandom(); + decisions[CellStates.armor.name] = Brain.Decision.getRandom(); + decisions[CellStates.eye.name] = Brain.Decision.getRandom(); + + return organism; + } + + static trySpawnCell(organism, x, y, spawnChance) { + + var neighbors = organism.anatomy.getNeighborsOfCell(x, y); + if (neighbors.length && Math.random() < spawnChance) { + organism.anatomy.addRandomizedCell(CellStates.getRandomLivingType(), x, y); + return true; + } + return false; + } + +} + +RandomOrganismGenerator.organismLayers = 2; +RandomOrganismGenerator.cellSpawnChance = 0.75; + +module.exports = RandomOrganismGenerator; \ No newline at end of file