From ebb39df34af0cbcf090ce2e4b69ac7220bd5d98a Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 18 Jul 2020 00:31:46 -0600 Subject: [PATCH] control tabs, organism editor, code refactor --- dist/bundle.js | 256 +----------------- dist/css/style.css | 96 ++++++- dist/html/index.html | 125 ++++++--- .../CanvasController.js} | 57 ++-- src/{ => Controllers}/ControlModes.js | 5 +- src/{ => Controllers}/ControlPanel.js | 76 +++++- src/Controllers/EditorController.js | 53 ++++ src/Controllers/EnvironmentController.js | 73 +++++ src/Engine.js | 21 +- src/{ => Environments}/Environment.js | 20 +- src/Environments/OrganismEditor.js | 84 ++++++ src/{ => Grid}/GridMap.js | 4 +- src/{ => Grid}/Neighbors.js | 0 src/Hyperparameters.js | 2 +- src/{ => Organism/Cell}/Cell.js | 2 +- src/{ => Organism/Cell}/CellTypes.js | 0 src/{ => Organism/Cell}/LocalCell.js | 4 +- src/{ => Organism}/Directions.js | 0 src/{ => Organism}/Organism.js | 32 ++- src/Rendering/Renderer.js | 14 +- src/index.js | 2 +- 21 files changed, 524 insertions(+), 402 deletions(-) rename src/{EnvironmentController.js => Controllers/CanvasController.js} (54%) rename src/{ => Controllers}/ControlModes.js (60%) rename src/{ => Controllers}/ControlPanel.js (63%) create mode 100644 src/Controllers/EditorController.js create mode 100644 src/Controllers/EnvironmentController.js rename src/{ => Environments}/Environment.js (81%) create mode 100644 src/Environments/OrganismEditor.js rename src/{ => Grid}/GridMap.js (94%) rename src/{ => Grid}/Neighbors.js (100%) rename src/{ => Organism/Cell}/Cell.js (97%) rename src/{ => Organism/Cell}/CellTypes.js (100%) rename src/{ => Organism/Cell}/LocalCell.js (91%) rename src/{ => Organism}/Directions.js (100%) rename src/{ => Organism}/Organism.js (91%) diff --git a/dist/bundle.js b/dist/bundle.js index 4d0508d..19c5b73 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -1,255 +1 @@ -/******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); -/******/ } -/******/ }; -/******/ -/******/ // define __esModule on exports -/******/ __webpack_require__.r = function(exports) { -/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { -/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); -/******/ } -/******/ Object.defineProperty(exports, '__esModule', { value: true }); -/******/ }; -/******/ -/******/ // create a fake namespace object -/******/ // mode & 1: value is a module id, require it -/******/ // mode & 2: merge all properties of value into the ns -/******/ // mode & 4: return value when already ns object -/******/ // mode & 8|1: behave like require -/******/ __webpack_require__.t = function(value, mode) { -/******/ if(mode & 1) value = __webpack_require__(value); -/******/ if(mode & 8) return value; -/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; -/******/ var ns = Object.create(null); -/******/ __webpack_require__.r(ns); -/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); -/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); -/******/ return ns; -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js"); -/******/ }) -/************************************************************************/ -/******/ ({ - -/***/ "./src/Cell.js": -/*!*********************!*\ - !*** ./src/Cell.js ***! - \*********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\nconst Hyperparams = __webpack_require__(/*! ./Hyperparameters */ \"./src/Hyperparameters.js\");\r\n\r\n// A cell exists in a grid system.\r\nclass Cell{\r\n constructor(type, col, row, x, y){\r\n this.owner = null;\r\n this.setType(type);\r\n this.col = col;\r\n this.row = row;\r\n this.x = x;\r\n this.y = y;\r\n }\r\n\r\n setType(type) {\r\n this.type = type;\r\n }\r\n\r\n performFunction(env) {\r\n switch(this.type){\r\n case CellTypes.mouth:\r\n eatFood(this, env);\r\n break;\r\n case CellTypes.producer:\r\n growFood(this, env);\r\n break;\r\n case CellTypes.killer:\r\n killNeighbors(this, env);\r\n break;\r\n }\r\n }\r\n\r\n getColor() {\r\n return CellTypes.colors[this.type];\r\n }\r\n\r\n isLiving() {\r\n return this.type != CellTypes.empty && \r\n this.type != CellTypes.food && \r\n this.type != CellTypes.wall;\r\n }\r\n}\r\n\r\nfunction eatFood(self, env){\r\n for (var loc of Hyperparams.edibleNeighbors){\r\n var cell = env.grid_map.cellAt(self.col+loc[0], self.row+loc[1]);\r\n eatNeighborFood(self, cell, env);\r\n }\r\n}\r\n\r\nfunction eatNeighborFood(self, n_cell, env){\r\n if (n_cell == null)\r\n return;\r\n if (n_cell.type == CellTypes.food){\r\n env.changeCell(n_cell.col, n_cell.row, CellTypes.empty, null);\r\n self.owner.food_collected++;\r\n }\r\n}\r\n\r\nfunction growFood(self, env){\r\n if (self.owner.is_mover)\r\n return;\r\n var prob = Hyperparams.foodProdProb;\r\n if (Math.random() * 100 <= prob){\r\n var loc = Hyperparams.growableNeighbors[Math.floor(Math.random() * Hyperparams.growableNeighbors.length)]\r\n var c=loc[0];\r\n var r=loc[1];\r\n var cell = env.grid_map.cellAt(self.col+c, self.row+r);\r\n if (cell != null && cell.type == CellTypes.empty){\r\n env.changeCell(self.col+c, self.row+r, CellTypes.food, null);\r\n return;\r\n }\r\n }\r\n}\r\n\r\nfunction killNeighbors(self, env) {\r\n for (var loc of Hyperparams.killableNeighbors){\r\n var cell = env.grid_map.cellAt(self.col+loc[0], self.row+loc[1]);\r\n killNeighbor(self, cell);\r\n }\r\n}\r\n\r\nfunction killNeighbor(self, n_cell) {\r\n if(n_cell == null || n_cell.owner == null || self.owner == null || n_cell.owner == self.owner || !n_cell.owner.living || n_cell.type == CellTypes.armor) \r\n return;\r\n var is_hit = n_cell.type == CellTypes.killer; // has to be calculated before death\r\n n_cell.owner.harm();\r\n if (is_hit){\r\n self.owner.harm();\r\n }\r\n}\r\n\r\nmodule.exports = Cell;\r\n\n\n//# sourceURL=webpack:///./src/Cell.js?"); - -/***/ }), - -/***/ "./src/CellTypes.js": -/*!**************************!*\ - !*** ./src/CellTypes.js ***! - \**************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("const CellTypes = {\r\n empty: 0,\r\n food: 1,\r\n wall: 2,\r\n mouth: 3,\r\n producer: 4,\r\n mover: 5,\r\n killer: 6,\r\n armor: 7,\r\n colors: ['#121D29', 'green', 'gray', 'orange', 'white', '#3493eb', 'red', 'purple'],\r\n getRandomLivingType: function() {\r\n return Math.floor(Math.random() * 5) + 3;\r\n }\r\n}\r\n\r\nmodule.exports = CellTypes;\r\n\n\n//# sourceURL=webpack:///./src/CellTypes.js?"); - -/***/ }), - -/***/ "./src/ControlModes.js": -/*!*****************************!*\ - !*** ./src/ControlModes.js ***! - \*****************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("const Modes = {\r\n None: 0,\r\n FoodDrop: 1,\r\n WallDrop: 2,\r\n ClickKill: 3\r\n}\r\n\r\nmodule.exports = Modes;\n\n//# sourceURL=webpack:///./src/ControlModes.js?"); - -/***/ }), - -/***/ "./src/ControlPanel.js": -/*!*****************************!*\ - !*** ./src/ControlPanel.js ***! - \*****************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Hyperparams = __webpack_require__(/*! ./Hyperparameters */ \"./src/Hyperparameters.js\");\r\nconst Modes = __webpack_require__(/*! ./ControlModes */ \"./src/ControlModes.js\");\r\nconst CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\n\r\nclass ControlPanel {\r\n constructor(engine) {\r\n this.engine = engine;\r\n this.defineEngineSpeedControls();\r\n this.defineHyperparameterControls();\r\n this.defineModeControls();\r\n this.fps = engine.fps;\r\n this.organism_record=0;\r\n this.env_controller = this.engine.env.controller;\r\n }\r\n\r\n defineEngineSpeedControls(){\r\n this.slider = document.getElementById(\"slider\");\r\n this.slider.oninput = function() {\r\n this.fps = this.slider.value\r\n if (this.engine.running) {\r\n this.changeEngineSpeed(this.fps);\r\n \r\n }\r\n $('#fps').text(\"Target FPS: \"+this.fps);\r\n }.bind(this);\r\n $('#pause-button').click(function() {\r\n if ($('#pause-button').text() == \"Pause\" && this.engine.running) {\r\n $('#pause-button').text(\"Play\");\r\n this.engine.stop();\r\n }\r\n else if (!this.engine.running){\r\n $('#pause-button').text(\"Pause\");\r\n this.engine.start(this.fps);\r\n }\r\n }.bind(this));\r\n }\r\n\r\n defineHyperparameterControls() {\r\n $('#food-prod-prob').change(function() {\r\n var food_prob = $('#food-prod-prob').val();\r\n if ($('#fixed-ratio').is(\":checked\")) {\r\n Hyperparams.foodProdProb = food_prob;\r\n Hyperparams.calcProducerFoodRatio(false);\r\n $('#lifespan-multiplier').val(Hyperparams.lifespanMultiplier);\r\n }\r\n else{\r\n Hyperparams.foodProdProb = food_prob;\r\n }\r\n }.bind(this));\r\n $('#lifespan-multiplier').change(function() {\r\n var lifespan = $('#lifespan-multiplier').val();\r\n if ($('#fixed-ratio').is(\":checked\")) {\r\n Hyperparams.lifespanMultiplier = lifespan;\r\n Hyperparams.calcProducerFoodRatio(true);\r\n $('#food-prod-prob').val(Hyperparams.foodProdProb);\r\n }\r\n else {\r\n Hyperparams.lifespanMultiplier = lifespan;\r\n }\r\n }.bind(this));\r\n $('.mut-prob').change( function() {\r\n switch(this.id){\r\n case \"add-prob\":\r\n Hyperparams.addProb = this.value;\r\n Hyperparams.balanceMutationProbs(1);\r\n break;\r\n case \"change-prob\":\r\n Hyperparams.changeProb = this.value;\r\n Hyperparams.balanceMutationProbs(2);\r\n break;\r\n case \"remove-prob\":\r\n Hyperparams.removeProb = this.value;\r\n Hyperparams.balanceMutationProbs(3);\r\n break;\r\n }\r\n $('#add-prob').val(Math.floor(Hyperparams.addProb));\r\n $('#change-prob').val(Math.floor(Hyperparams.changeProb));\r\n $('#remove-prob').val(Math.floor(Hyperparams.removeProb));\r\n });\r\n\r\n $('#mover-rot').change(function() {\r\n Hyperparams.moversCanRotate = this.checked;\r\n });\r\n $('#offspring-rot').change(function() {\r\n Hyperparams.offspringRotate = this.checked;\r\n });\r\n $('#insta-kill').change(function() {\r\n Hyperparams.instaKill = this.checked;\r\n });\r\n }\r\n\r\n defineModeControls() {\r\n var self = this;\r\n $('.control-mode-button').click( function() {\r\n switch(this.id){\r\n case \"food-button\":\r\n self.env_controller.mode = Modes.FoodDrop;\r\n break;\r\n case \"wall-button\":\r\n self.env_controller.mode = Modes.WallDrop;\r\n break;\r\n case \"kill-button\":\r\n self.env_controller.mode = Modes.ClickKill;\r\n break;\r\n case \"none-button\":\r\n self.env_controller.mode = Modes.None;\r\n break;\r\n }\r\n $(\".control-mode-button\" ).css( \"background-color\", \"lightgray\" );\r\n $(\"#\"+this.id).css(\"background-color\", \"darkgray\");\r\n });\r\n\r\n\r\n $('#reset-env').click( function() {\r\n this.engine.env.reset();\r\n }.bind(this));\r\n $('#kill-all').click( function() {\r\n this.engine.env.clearOrganisms();\r\n }.bind(this));\r\n $('#clear-walls').click( function() {\r\n this.engine.env.clearWalls();\r\n }.bind(this));\r\n }\r\n\r\n changeEngineSpeed(change_val) {\r\n this.engine.stop();\r\n this.engine.start(change_val)\r\n this.fps = this.engine.fps;\r\n }\r\n\r\n update() {\r\n $('#fps-actual').text(\"Actual FPS: \" + Math.floor(this.engine.actual_fps));\r\n var org_count = this.engine.env.organisms.length;\r\n $('#org-count').text(\"Organism count: \" + org_count);\r\n if (org_count > this.organism_record) \r\n this.organism_record = org_count;\r\n $('#org-record').text(\"Highest count: \" + this.organism_record);\r\n $('#avg-mut').text(\"Average Mutation Rate: \" + Math.round(this.engine.env.averageMutability() * 100) / 100);\r\n }\r\n\r\n}\r\n\r\n\r\nmodule.exports = ControlPanel;\n\n//# sourceURL=webpack:///./src/ControlPanel.js?"); - -/***/ }), - -/***/ "./src/Directions.js": -/*!***************************!*\ - !*** ./src/Directions.js ***! - \***************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("const Directions = {\r\n up:0,\r\n down:1,\r\n left:2,\r\n right:3,\r\n scalars:[[0,-1],[0,1],[-1,0],[1,0]],\r\n getRandomDirection: function() {\r\n return Math.floor(Math.random() * 4);\r\n },\r\n getRandomScalar: function() {\r\n return this.scalars[Math.floor(Math.random() * this.scalars.length)];\r\n }\r\n}\r\n\r\nmodule.exports = Directions;\n\n//# sourceURL=webpack:///./src/Directions.js?"); - -/***/ }), - -/***/ "./src/Engine.js": -/*!***********************!*\ - !*** ./src/Engine.js ***! - \***********************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Environment = __webpack_require__(/*! ./Environment */ \"./src/Environment.js\");\r\nconst ControlPanel = __webpack_require__(/*! ./ControlPanel */ \"./src/ControlPanel.js\");\r\n\r\nconst render_speed = 60;\r\n\r\nclass Engine{\r\n constructor(){\r\n this.fps = 60;\r\n this.env = new Environment(5);\r\n this.controlpanel = new ControlPanel(this);\r\n this.env.OriginOfLife();\r\n this.last_update = Date.now();\r\n this.delta_time = 0;\r\n this.actual_fps = 0;\r\n this.running = false;\r\n }\r\n\r\n start(fps=60) {\r\n if (fps <= 0)\r\n fps = 1;\r\n if (fps > 300)\r\n fps = 300;\r\n this.fps = fps;\r\n this.game_loop = setInterval(function(){this.update();}.bind(this), 1000/fps);\r\n this.running = true;\r\n if (this.fps >= render_speed) {\r\n if (this.render_loop != null) {\r\n clearInterval(this.render_loop);\r\n this.render_loop = null;\r\n }\r\n }\r\n else\r\n this.setRenderLoop();\r\n }\r\n \r\n stop() {\r\n clearInterval(this.game_loop);\r\n this.running = false;\r\n this.setRenderLoop();\r\n }\r\n\r\n setRenderLoop() {\r\n if (this.render_loop == null) {\r\n this.render_loop = setInterval(function(){this.env.render();this.controlpanel.update();}.bind(this), 1000/render_speed);\r\n }\r\n }\r\n\r\n\r\n update() {\r\n this.delta_time = Date.now() - this.last_update;\r\n this.last_update = Date.now();\r\n this.env.update(this.delta_time);\r\n this.actual_fps = 1/this.delta_time*1000;\r\n if(this.render_loop == null){\r\n this.env.render();\r\n this.controlpanel.update();\r\n }\r\n \r\n }\r\n\r\n}\r\n\r\nmodule.exports = Engine;\r\n\n\n//# sourceURL=webpack:///./src/Engine.js?"); - -/***/ }), - -/***/ "./src/Environment.js": -/*!****************************!*\ - !*** ./src/Environment.js ***! - \****************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Grid = __webpack_require__(/*! ./GridMap */ \"./src/GridMap.js\");\r\nconst Renderer = __webpack_require__(/*! ./Rendering/Renderer */ \"./src/Rendering/Renderer.js\");\r\nconst GridMap = __webpack_require__(/*! ./GridMap */ \"./src/GridMap.js\");\r\nconst Organism = __webpack_require__(/*! ./Organism */ \"./src/Organism.js\");\r\nconst CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\nconst Cell = __webpack_require__(/*! ./Cell */ \"./src/Cell.js\");\r\nconst EnvironmentController = __webpack_require__(/*! ./EnvironmentController */ \"./src/EnvironmentController.js\");\r\n\r\nclass Environment{\r\n constructor(cell_size) {\r\n this.renderer = new Renderer('canvas', this, cell_size);\r\n this.controller = new EnvironmentController(this, this.renderer.canvas);\r\n this.grid_rows = Math.floor(this.renderer.height / cell_size);\r\n this.grid_cols = Math.floor(this.renderer.width / cell_size);\r\n this.grid_map = new GridMap(this.grid_cols, this.grid_rows, cell_size);\r\n this.renderer.renderFullGrid();\r\n this.organisms = [];\r\n this.walls = [];\r\n this.total_mutability = 0;\r\n }\r\n\r\n update(delta_time) {\r\n var to_remove = [];\r\n for (var i in this.organisms) {\r\n var org = this.organisms[i];\r\n if (!org.living || !org.update()) {\r\n to_remove.push(i);\r\n }\r\n }\r\n this.removeOrganisms(to_remove);\r\n }\r\n\r\n render() {\r\n this.renderer.renderCells();\r\n this.renderer.renderHighlights();\r\n }\r\n\r\n removeOrganisms(org_indeces) {\r\n for (var i of org_indeces.reverse()){\r\n this.total_mutability -= this.organisms[i].mutability;\r\n this.organisms.splice(i, 1);\r\n }\r\n }\r\n\r\n OriginOfLife() {\r\n var center = this.grid_map.getCenter();\r\n var org = new Organism(center[0], center[1], this);\r\n org.addCell(CellTypes.mouth, 0, 0);\r\n org.addCell(CellTypes.producer, -1, -1);\r\n org.addCell(CellTypes.producer, 1, 1);\r\n this.addOrganism(org);\r\n }\r\n\r\n addOrganism(organism) {\r\n organism.updateGrid();\r\n this.total_mutability += organism.mutability;\r\n this.organisms.push(organism);\r\n }\r\n\r\n averageMutability() {\r\n if (this.organisms.length < 1)\r\n return 0;\r\n return this.total_mutability / this.organisms.length;\r\n }\r\n\r\n changeCell(c, r, type, owner) {\r\n this.grid_map.setCellType(c, r, type);\r\n this.grid_map.setCellOwner(c, r, owner);\r\n this.renderer.addToRender(this.grid_map.cellAt(c, r));\r\n if(type == CellTypes.wall)\r\n this.walls.push(this.grid_map.cellAt(c, r));\r\n }\r\n\r\n clearWalls() {\r\n for(var wall of this.walls)\r\n this.changeCell(wall.col, wall.row, CellTypes.empty, null);\r\n }\r\n\r\n clearOrganisms() {\r\n for (var org of this.organisms)\r\n org.die();\r\n this.organisms = [];\r\n }\r\n\r\n reset() {\r\n this.organisms = [];\r\n this.grid_map.fillGrid(CellTypes.empty);\r\n this.renderer.renderFullGrid();\r\n this.total_mutability = 0;\r\n this.OriginOfLife();\r\n }\r\n}\r\n\r\nmodule.exports = Environment;\r\n\r\n\n\n//# sourceURL=webpack:///./src/Environment.js?"); - -/***/ }), - -/***/ "./src/EnvironmentController.js": -/*!**************************************!*\ - !*** ./src/EnvironmentController.js ***! - \**************************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Modes = __webpack_require__(/*! ./ControlModes */ \"./src/ControlModes.js\");\r\nconst CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\n\r\nclass EnvironmentController{\r\n constructor(env, canvas) {\r\n this.env = env;\r\n this.canvas = canvas;\r\n this.mouse_x;\r\n this.mouse_y;\r\n this.mouse_c;\r\n this.mouse_r;\r\n this.left_click = false;\r\n this.right_click = false;\r\n this.cur_cell = null;\r\n this.cur_org = null;\r\n this.mode = Modes.None;\r\n this.defineEvents();\r\n }\r\n\r\n defineEvents() {\r\n this.canvas.addEventListener('mousemove', e => {\r\n var prev_cell = this.cur_cell;\r\n var prev_org = this.cur_org;\r\n\r\n this.mouse_x = e.offsetX;\r\n this.mouse_y = e.offsetY;\r\n var colRow = this.env.grid_map.xyToColRow(this.mouse_x, this.mouse_y);\r\n this.mouse_c = colRow[0];\r\n this.mouse_r = colRow[1];\r\n this.cur_cell = this.env.grid_map.cellAt(this.mouse_c, this.mouse_r);\r\n this.cur_org = this.cur_cell.owner;\r\n\r\n if (this.cur_org != prev_org || this.cur_cell != prev_cell) {\r\n this.env.renderer.clearAllHighlights(true);\r\n if (this.cur_org != null) {\r\n this.env.renderer.highlightOrganism(this.cur_org);\r\n }\r\n else if (this.cur_cell != null) {\r\n this.env.renderer.highlightCell(this.cur_cell, true);\r\n }\r\n }\r\n this.performModeAction();\r\n });\r\n\r\n this.canvas.addEventListener('mouseup', function(evt) {\r\n evt.preventDefault();\r\n this.left_click=false;\r\n this.right_click=false;\r\n }.bind(this));\r\n\r\n this.canvas.addEventListener('mousedown', function(evt) {\r\n evt.preventDefault();\r\n if (evt.button == 0) {\r\n this.left_click = true;\r\n }\r\n if (evt.button == 2) \r\n this.right_click = true;\r\n this.performModeAction();\r\n }.bind(this));\r\n\r\n this.canvas.addEventListener('contextmenu', function(evt) {\r\n evt.preventDefault();\r\n });\r\n\r\n this.canvas.addEventListener('mouseleave', function(){\r\n this.right_click = false;\r\n this.left_click = false;\r\n }.bind(this));\r\n\r\n }\r\n\r\n performModeAction() {\r\n var mode = this.mode;\r\n var right_click = this.right_click;\r\n var left_click = this.left_click;\r\n if (mode != Modes.None && (right_click || left_click)) {\r\n var cell = this.cur_cell;\r\n if (cell == null){\r\n return;\r\n }\r\n switch(mode) {\r\n case Modes.FoodDrop:\r\n if (left_click && cell.type == CellTypes.empty){\r\n this.env.changeCell(cell.col, cell.row, CellTypes.food, null);\r\n }\r\n else if (right_click && cell.type == CellTypes.food){\r\n this.env.changeCell(cell.col, cell.row, CellTypes.empty, null);\r\n }\r\n break;\r\n case Modes.WallDrop:\r\n if (left_click && (cell.type == CellTypes.empty || cell.type == CellTypes.food)){\r\n this.env.changeCell(cell.col, cell.row, CellTypes.wall, null);\r\n }\r\n else if (right_click && cell.type == CellTypes.wall){\r\n this.env.changeCell(cell.col, cell.row, CellTypes.empty, null);\r\n }\r\n break;\r\n case Modes.ClickKill:\r\n if (this.cur_org != null)\r\n this.cur_org.die();\r\n }\r\n }\r\n }\r\n\r\n\r\n}\r\n\r\nmodule.exports = EnvironmentController;\r\n\n\n//# sourceURL=webpack:///./src/EnvironmentController.js?"); - -/***/ }), - -/***/ "./src/GridMap.js": -/*!************************!*\ - !*** ./src/GridMap.js ***! - \************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Cell = __webpack_require__(/*! ./Cell */ \"./src/Cell.js\");\r\nconst CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\n\r\nclass GridMap {\r\n constructor(cols, rows, cell_size, filltype=CellTypes.empty) {\r\n this.grid = [];\r\n this.cols = cols;\r\n this.rows = rows;\r\n this.cell_size = cell_size;\r\n for(var c=0; c=0 && row>=0;\r\n }\r\n\r\n getCenter(){\r\n return [Math.floor(this.cols/2), Math.floor(this.rows/2)]\r\n }\r\n\r\n xyToColRow(x, y) {\r\n var c = Math.floor(x/this.cell_size);\r\n var r = Math.floor(y/this.cell_size);\r\n if (c >= this.cols)\r\n c = this.cols-1;\r\n else if (c < 0)\r\n c = 0;\r\n if (r >= this.rows)\r\n r = this.rows-1;\r\n else if (r < 0)\r\n r = 0;\r\n return [c, r];\r\n }\r\n}\r\n\r\nmodule.exports = GridMap;\r\n\n\n//# sourceURL=webpack:///./src/GridMap.js?"); - -/***/ }), - -/***/ "./src/Hyperparameters.js": -/*!********************************!*\ - !*** ./src/Hyperparameters.js ***! - \********************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const Neighbors = __webpack_require__(/*! ./Neighbors */ \"./src/Neighbors.js\");\r\n\r\nconst Hyperparams = {\r\n lifespanMultiplier: 100,\r\n foodProdProb: 4,\r\n foodProdProbScalar: 4,\r\n killableNeighbors: Neighbors.adjacent,\r\n edibleNeighbors: Neighbors.adjacent,\r\n growableNeighbors: Neighbors.adjacent,\r\n \r\n useGlobalMutability: false,\r\n globalMutability: 0,\r\n\r\n addProb: 33,\r\n changeProb: 33,\r\n removeProb: 33,\r\n\r\n moversCanRotate: true,\r\n offspringRotate: true,\r\n\r\n instaKill: false,\r\n\r\n // calculates the optimal ratio where a producer cell is most likely to produce 1 food in its lifespan * a scalar of my choice :)\r\n calcProducerFoodRatio : function(lifespan_fixed=true) {\r\n if (lifespan_fixed) {\r\n // change the foodProdProb\r\n this.foodProdProb = (100 / this.lifespanMultiplier) * this.foodProdProbScalar;\r\n }\r\n else {\r\n // change the lifespanMultiplier\r\n this.lifespanMultiplier = Math.floor(100 / (this.foodProdProb/this.foodProdProbScalar));\r\n }\r\n },\r\n\r\n balanceMutationProbs : function(choice) {\r\n if (choice == 1) {\r\n var remaining = 100 - this.addProb;\r\n this.changeProb = remaining/2;\r\n this.removeProb = remaining/2;\r\n }\r\n else if (choice == 2) {\r\n var remaining = 100 - this.changeProb;\r\n this.addProb = remaining/2;\r\n this.removeProb = remaining/2;\r\n }\r\n else {\r\n var remaining = 100 - this.removeProb;\r\n this.changeProb = remaining/2;\r\n this.addProb = remaining/2;\r\n }\r\n }\r\n}\r\n\r\nmodule.exports = Hyperparams;\n\n//# sourceURL=webpack:///./src/Hyperparameters.js?"); - -/***/ }), - -/***/ "./src/LocalCell.js": -/*!**************************!*\ - !*** ./src/LocalCell.js ***! - \**************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\nconst Directions = __webpack_require__(/*! ./Directions */ \"./src/Directions.js\");\r\nconst Hyperparams = __webpack_require__(/*! ./Hyperparameters */ \"./src/Hyperparameters.js\");\r\n\r\n// A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid \r\nclass LocalCell{\r\n constructor(type, loc_col, loc_row){\r\n this.type = type;\r\n this.loc_col = loc_col;\r\n this.loc_row = loc_row;\r\n }\r\n\r\n rotatedCol(dir){\r\n switch(dir){\r\n case Directions.up:\r\n return this.loc_col;\r\n case Directions.down:\r\n return this.loc_col * -1;\r\n case Directions.left:\r\n return this.loc_row;\r\n case Directions.right:\r\n return this.loc_row * -1;\r\n }\r\n }\r\n\r\n rotatedRow(dir){\r\n switch(dir){\r\n case Directions.up:\r\n return this.loc_row;\r\n case Directions.down:\r\n return this.loc_row * -1;\r\n case Directions.left:\r\n return this.loc_col * -1;\r\n case Directions.right:\r\n return this.loc_col;\r\n }\r\n }\r\n}\r\n\r\nmodule.exports = LocalCell;\r\n\n\n//# sourceURL=webpack:///./src/LocalCell.js?"); - -/***/ }), - -/***/ "./src/Neighbors.js": -/*!**************************!*\ - !*** ./src/Neighbors.js ***! - \**************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("// contains local cell values for the following:\r\n\r\n//all ...\r\n// .x.\r\n// ...\r\n\r\n//adjacent .\r\n// .x.\r\n// .\r\n\r\n//corners . .\r\n// x\r\n// . .\r\n\r\nconst Neighbors = {\r\n all: [[0, 1],[0, -1],[1, 0],[-1, 0],[-1, -1],[1, 1],[-1, 1],[1, -1]],\r\n adjacent: [[0, 1],[0, -1],[1, 0],[-1, 0]],\r\n corners: [[-1, -1],[1, 1],[-1, 1],[1, -1]]\r\n}\r\n\r\nmodule.exports = Neighbors;\n\n//# sourceURL=webpack:///./src/Neighbors.js?"); - -/***/ }), - -/***/ "./src/Organism.js": -/*!*************************!*\ - !*** ./src/Organism.js ***! - \*************************/ -/*! no static exports found */ -/***/ (function(module, exports, __webpack_require__) { - -eval("const CellTypes = __webpack_require__(/*! ./CellTypes */ \"./src/CellTypes.js\");\r\nconst Cell = __webpack_require__(/*! ./Cell */ \"./src/Cell.js\");\r\nconst GridMap = __webpack_require__(/*! ./GridMap */ \"./src/GridMap.js\");\r\nconst LocalCell = __webpack_require__(/*! ./LocalCell */ \"./src/LocalCell.js\");\r\nconst Neighbors = __webpack_require__(/*! ./Neighbors */ \"./src/Neighbors.js\");\r\nconst Hyperparams = __webpack_require__(/*! ./Hyperparameters */ \"./src/Hyperparameters.js\");\r\nconst Directions = __webpack_require__(/*! ./Directions */ \"./src/Directions.js\");\r\n\r\nconst directions = [[0,1],[0,-1],[1,0],[-1,0]]\r\n\r\nclass Organism {\r\n constructor(col, row, env, parent=null) {\r\n this.c = col;\r\n this.r = row;\r\n this.env = env;\r\n this.lifetime = 0;\r\n this.food_collected = 0;\r\n this.living = true;\r\n this.cells = [];\r\n this.is_producer = false;\r\n this.is_mover = false;\r\n this.direction = Directions.up;\r\n this.rotation = Directions.up;\r\n this.can_rotate = Hyperparams.moversCanRotate;\r\n this.move_count = 0;\r\n this.move_range = 4;\r\n this.mutability = 5;\r\n this.damage = 0;\r\n if (parent != null) {\r\n this.inherit(parent);\r\n }\r\n }\r\n\r\n addCell(type, c, r) {\r\n for (var cell of this.cells) {\r\n if (cell.loc_col == c && cell.loc_row == r)\r\n return false;\r\n }\r\n this.checkProducerMover(type);\r\n this.cells.push(new LocalCell(type, c, r));\r\n return true;\r\n }\r\n\r\n removeCell(c, r) {\r\n if (c == 0 && r == 0)\r\n return false;\r\n var check_change = false;\r\n for (var i=0; i 1) {\r\n cell = this.cells[Math.floor(Math.random() * this.cells.length)];\r\n mutated = this.removeCell(cell.loc_col, cell.loc_row);\r\n }\r\n }\r\n\r\n if (this.is_mover) {\r\n this.move_range += Math.floor(Math.random() * 4) - 2;\r\n if (this.move_range <= 0){\r\n this.move_range = 1;\r\n }\r\n }\r\n return mutated;\r\n }\r\n\r\n attemptMove() {\r\n var direction = Directions.scalars[this.direction];\r\n var direction_c = direction[0];\r\n var direction_r = direction[1];\r\n var new_c = this.c + direction_c;\r\n var new_r = this.r + direction_r;\r\n if (this.isClear(new_c, new_r)) {\r\n for (var cell of this.cells) {\r\n var real_c = this.c + cell.rotatedCol(this.rotation);\r\n var real_r = this.r + cell.rotatedRow(this.rotation);\r\n this.env.changeCell(real_c, real_r, CellTypes.empty, null);\r\n }\r\n this.c = new_c;\r\n this.r = new_r;\r\n this.updateGrid();\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n attemptRotate() {\r\n if(!this.can_rotate){\r\n this.direction = Directions.getRandomDirection();\r\n this.move_count = 0;\r\n return true;\r\n }\r\n var new_rotation = Directions.getRandomDirection();\r\n if(this.isClear(this.c, this.r, new_rotation)){\r\n for (var cell of this.cells) {\r\n var real_c = this.c + cell.rotatedCol(this.rotation);\r\n var real_r = this.r + cell.rotatedRow(this.rotation);\r\n this.env.changeCell(real_c, real_r, CellTypes.empty, null);\r\n }\r\n this.rotation = new_rotation;\r\n this.direction = Directions.getRandomDirection();\r\n this.updateGrid();\r\n this.move_count = 0;\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n // assumes either c1==c2 or r1==r2, returns true if there is a clear path from point a to b\r\n isStraightPath(c1, r1, c2, r2, parent){\r\n if (c1 == c2) {\r\n if (r1 > r2){\r\n var temp = r2;\r\n r2 = r1;\r\n r1 = temp;\r\n }\r\n for (var i=r1; i!=r2; i++) {\r\n var cell = this.env.grid_map.cellAt(c1, i)\r\n if (!this.isPassableCell(cell, parent)){\r\n return false;\r\n }\r\n }\r\n return true;\r\n }\r\n else {\r\n if (c1 > c2){\r\n var temp = c2;\r\n c2 = c1;\r\n c1 = temp;\r\n }\r\n for (var i=c1; i!=c2; i++) {\r\n var cell = this.env.grid_map.cellAt(i, r1);\r\n if (!this.isPassableCell(cell, parent)){\r\n return false;\r\n }\r\n }\r\n return true;\r\n }\r\n }\r\n\r\n isPassableCell(cell, parent){\r\n return cell != null && (cell.type == CellTypes.empty || cell.owner == this || cell.owner == parent || cell.type == CellTypes.food);\r\n }\r\n\r\n isClear(col, row, rotation=this.rotation) {\r\n for(var loccell of this.cells) {\r\n var cell = this.getRealCell(loccell, col, row, rotation);\r\n if(cell == null || cell.type != CellTypes.empty && cell.owner != this) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n }\r\n\r\n harm() {\r\n this.damage++;\r\n if (this.damage >= this.maxHealth() || Hyperparams.instaKill) {\r\n this.die();\r\n }\r\n }\r\n\r\n die() {\r\n for (var cell of this.cells) {\r\n var real_c = this.c + cell.rotatedCol(this.rotation);\r\n var real_r = this.r + cell.rotatedRow(this.rotation);\r\n this.env.changeCell(real_c, real_r, CellTypes.food, null);\r\n }\r\n this.living = false;\r\n }\r\n\r\n updateGrid() {\r\n for (var cell of this.cells) {\r\n var real_c = this.c + cell.rotatedCol(this.rotation);\r\n var real_r = this.r + cell.rotatedRow(this.rotation);\r\n this.env.changeCell(real_c, real_r, cell.type, this);\r\n }\r\n }\r\n\r\n update() {\r\n this.lifetime++;\r\n if (this.lifetime > this.lifespan()) {\r\n this.die();\r\n return this.living;\r\n }\r\n if (this.food_collected >= this.foodNeeded()) {\r\n this.reproduce();\r\n }\r\n for (var cell of this.cells) {\r\n this.getRealCell(cell).performFunction(this.env);\r\n }\r\n if (!this.living){\r\n return this.living\r\n }\r\n if (this.is_mover) {\r\n this.move_count++;\r\n var moved = this.attemptMove();\r\n if (this.move_count > this.move_range){\r\n this.attemptRotate();\r\n }\r\n }\r\n\r\n return this.living;\r\n }\r\n\r\n getRealCell(local_cell, c=this.c, r=this.r, rotation=this.rotation){\r\n var real_c = c + local_cell.rotatedCol(rotation);\r\n var real_r = r + local_cell.rotatedRow(rotation);\r\n return this.env.grid_map.cellAt(real_c, real_r);\r\n }\r\n\r\n}\r\n\r\nmodule.exports = Organism;\r\n\n\n//# sourceURL=webpack:///./src/Organism.js?"); - -/***/ }), - -/***/ "./src/Rendering/Renderer.js": -/*!***********************************!*\ - !*** ./src/Rendering/Renderer.js ***! - \***********************************/ -/*! no static exports found */ -/***/ (function(module, exports) { - -eval("\r\n// Renderer controls access to a canvas. There is one renderer for each canvas\r\nclass Renderer {\r\n constructor(canvas_id, env, cell_size) {\r\n this.cell_size = cell_size;\r\n this.env = env;\r\n this.canvas = document.getElementById(canvas_id);\r\n this.ctx = this.canvas.getContext(\"2d\");\r\n this.canvas.width = $('.env').width();\r\n this.canvas.height = $('.env').height();\r\n\t\tthis.height = canvas.height;\r\n this.width = canvas.width;\r\n this.cells_to_render = new Set();\r\n this.cells_to_highlight = new Set();\r\n this.highlighted_cells = new Set();\r\n }\r\n\r\n clear() {\r\n this.ctx.fillStyle = 'white';\r\n this.ctx.fillRect(0, 0, this.height, this.width);\r\n }\r\n\r\n renderFullGrid() {\r\n var grid = this.env.grid_map.grid;\r\n for (var col of grid) {\r\n for (var cell of col){\r\n this.ctx.fillStyle = cell.getColor();\r\n this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);\r\n }\r\n }\r\n }\r\n\r\n renderCells() {\r\n for (var cell of this.cells_to_render) {\r\n this.renderCell(cell);\r\n }\r\n this.cells_to_render.clear();\r\n }\r\n\r\n renderCell(cell) {\r\n this.ctx.fillStyle = cell.getColor();\r\n this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);\r\n }\r\n\r\n renderOrganism(org) {\r\n for(var org_cell of org.cells) {\r\n var cell = org.getRealCell(org_cell);\r\n this.renderCell(cell);\r\n }\r\n }\r\n\r\n addToRender(cell) {\r\n if (this.highlighted_cells.has(cell)){\r\n this.cells_to_highlight.add(cell);\r\n }\r\n this.cells_to_render.add(cell);\r\n }\r\n\r\n renderHighlights() {\r\n for (var cell of this.cells_to_highlight) {\r\n this.renderCellHighlight(cell);\r\n this.highlighted_cells.add(cell);\r\n }\r\n this.cells_to_highlight.clear();\r\n \r\n }\r\n\r\n highlightOrganism(org) {\r\n for(var org_cell of org.cells) {\r\n var cell = org.getRealCell(org_cell);\r\n this.cells_to_highlight.add(cell);\r\n }\r\n }\r\n\r\n highlightCell(cell) {\r\n this.cells_to_highlight.add(cell);\r\n }\r\n\r\n renderCellHighlight(cell, color=\"yellow\") {\r\n this.renderCell(cell);\r\n this.ctx.fillStyle = color;\r\n this.ctx.globalAlpha = 0.5;\r\n this.ctx.fillRect(cell.x, cell.y, this.cell_size, this.cell_size);\r\n this.ctx.globalAlpha = 1;\r\n this.highlighted_cells.add(cell);\r\n }\r\n\r\n clearAllHighlights(clear_to_highlight=false) {\r\n for (var cell of this.highlighted_cells) {\r\n this.renderCell(cell);\r\n }\r\n this.highlighted_cells.clear();\r\n if (clear_to_highlight) {\r\n this.cells_to_highlight.clear();\r\n }\r\n }\r\n}\r\n\r\n// $(\"body\").mousemove(function(e) {\r\n// console.log(\"hello\");\r\n// });\r\n\r\nmodule.exports = Renderer;\r\n\n\n//# sourceURL=webpack:///./src/Rendering/Renderer.js?"); - -/***/ }), - -/***/ "./src/index.js": -/*!**********************!*\ - !*** ./src/index.js ***! - \**********************/ -/*! no exports provided */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _Engine__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Engine */ \"./src/Engine.js\");\n/* harmony import */ var _Engine__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_Engine__WEBPACK_IMPORTED_MODULE_0__);\n'user strict';\r\n\r\n\r\n\r\n$('document').ready(function(){\r\n var engine = new _Engine__WEBPACK_IMPORTED_MODULE_0___default.a();\r\n engine.start(60);\r\n});\r\n\n\n//# sourceURL=webpack:///./src/index.js?"); - -/***/ }) - -/******/ }); \ No newline at end of file +!function(t){var e={};function i(r){if(e[r])return e[r].exports;var s=e[r]={i:r,l:!1,exports:{}};return t[r].call(s.exports,s,s.exports,i),s.l=!0,s.exports}i.m=t,i.c=e,i.d=function(t,e,r){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)i.d(r,s,function(e){return t[e]}.bind(null,s));return r},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=11)}([function(t,e){const i={empty:0,food:1,wall:2,mouth:3,producer:4,mover:5,killer:6,armor:7,colors:["#121D29","green","gray","orange","white","#3493eb","red","purple"],getRandomLivingType:function(){return Math.floor(5*Math.random())+3}};t.exports=i},function(t,e,i){const r=i(2),s=i(0);t.exports=class{constructor(t,e,i,o=s.empty){this.grid=[],this.cols=t,this.rows=e,this.cell_size=i;for(var l=0;l=0&&e>=0}getCenter(){return[Math.floor(this.cols/2),Math.floor(this.rows/2)]}xyToColRow(t,e){var i=Math.floor(t/this.cell_size),r=Math.floor(e/this.cell_size);return i>=this.cols?i=this.cols-1:i<0&&(i=0),r>=this.rows?r=this.rows-1:r<0&&(r=0),[i,r]}}},function(t,e,i){const r=i(0),s=i(3);function o(t,e,i){null!=e&&e.type==r.food&&(i.changeCell(e.col,e.row,r.empty,null),t.owner.food_collected++)}function l(t,e){if(null!=e&&null!=e.owner&&null!=t.owner&&e.owner!=t.owner&&e.owner.living&&e.type!=r.armor){var i=e.type==r.killer;e.owner.harm(),i&&t.owner.harm()}}t.exports=class{constructor(t,e,i,r,s){this.owner=null,this.setType(t),this.col=e,this.row=i,this.x=r,this.y=s}setType(t){this.type=t}performFunction(t){switch(this.type){case r.mouth:!function(t,e){for(var i of s.edibleNeighbors){var r=e.grid_map.cellAt(t.col+i[0],t.row+i[1]);o(t,r,e)}}(this,t);break;case r.producer:!function(t,e){if(t.owner.is_mover)return;var i=s.foodProdProb;if(100*Math.random()<=i){var o=s.growableNeighbors[Math.floor(Math.random()*s.growableNeighbors.length)],l=o[0],n=o[1],h=e.grid_map.cellAt(t.col+l,t.row+n);if(null!=h&&h.type==r.empty)e.changeCell(t.col+l,t.row+n,r.food,null)}}(this,t);break;case r.killer:!function(t,e){for(var i of s.killableNeighbors){var r=e.grid_map.cellAt(t.col+i[0],t.row+i[1]);l(t,r)}}(this,t)}}getColor(){return r.colors[this.type]}isLiving(){return this.type!=r.empty&&this.type!=r.food&&this.type!=r.wall}}},function(t,e,i){const r=i(6),s={lifespanMultiplier:100,foodProdProb:4,foodProdProbScalar:4,killableNeighbors:r.adjacent,edibleNeighbors:r.adjacent,growableNeighbors:r.adjacent,useGlobalMutability:!1,globalMutability:0,addProb:33,changeProb:33,removeProb:33,moversCanRotate:!0,offspringRotate:!0,instaKill:!1,calcProducerFoodRatio:function(t=!0){t?this.foodProdProb=100/this.lifespanMultiplier*this.foodProdProbScalar:this.lifespanMultiplier=Math.floor(100/(this.foodProdProb/this.foodProdProbScalar))},balanceMutationProbs:function(t){if(1==t){var e=100-this.addProb;this.changeProb=e/2,this.removeProb=e/2}else if(2==t){e=100-this.changeProb;this.addProb=e/2,this.removeProb=e/2}else{e=100-this.removeProb;this.changeProb=e/2,this.addProb=e/2}}};t.exports=s},function(t,e,i){const r=i(0),s=(i(2),i(1),i(13)),o=i(6),l=i(3),n=i(8);class h{constructor(t,e,i,r=null){this.c=t,this.r=e,this.env=i,this.lifetime=0,this.food_collected=0,this.living=!0,this.cells=[],this.is_producer=!1,this.is_mover=!1,this.direction=n.up,this.rotation=n.up,this.can_rotate=l.moversCanRotate,this.move_count=0,this.move_range=4,this.mutability=5,this.damage=0,null!=r&&this.inherit(r)}addCell(t,e,i){for(var r of this.cells)if(r.loc_col==e&&r.loc_row==i)return!1;return this.checkProducerMover(t),this.cells.push(new s(t,e,i)),!0}removeCell(t,e){if(0==t&&0==e)return!1;for(var i=!1,s=0;s1&&(c=this.cells[Math.floor(Math.random()*this.cells.length)],e=this.removeCell(c.loc_col,c.loc_row));return this.is_mover&&(this.move_range+=Math.floor(4*Math.random())-2,this.move_range<=0&&(this.move_range=1)),e}attemptMove(){var t=n.scalars[this.direction],e=t[0],i=t[1],s=this.c+e,o=this.r+i;if(this.isClear(s,o)){for(var l of this.cells){var h=this.c+l.rotatedCol(this.rotation),a=this.r+l.rotatedRow(this.rotation);this.env.changeCell(h,a,r.empty,null)}return this.c=s,this.r=o,this.updateGrid(),!0}return!1}attemptRotate(){if(!this.can_rotate)return this.direction=n.getRandomDirection(),this.move_count=0,!0;var t=n.getRandomDirection();if(this.isClear(this.c,this.r,t)){for(var e of this.cells){var i=this.c+e.rotatedCol(this.rotation),s=this.r+e.rotatedRow(this.rotation);this.env.changeCell(i,s,r.empty,null)}return this.rotation=t,this.direction=n.getRandomDirection(),this.updateGrid(),this.move_count=0,!0}return!1}isStraightPath(t,e,i,r,s){if(t==i){if(e>r){var o=r;r=e,e=o}for(var l=e;l!=r;l++){var n=this.env.grid_map.cellAt(t,l);if(!this.isPassableCell(n,s))return!1}return!0}if(t>i){o=i;i=t,t=o}for(l=t;l!=i;l++){n=this.env.grid_map.cellAt(l,e);if(!this.isPassableCell(n,s))return!1}return!0}isPassableCell(t,e){return null!=t&&(t.type==r.empty||t.owner==this||t.owner==e||t.type==r.food)}isClear(t,e,i=this.rotation){for(var s of this.cells){var o=this.getRealCell(s,t,e,i);if(null==o||o.type!=r.empty&&o.owner!=this)return!1}return!0}harm(){this.damage++,(this.damage>=this.maxHealth()||l.instaKill)&&this.die()}die(){for(var t of this.cells){var e=this.c+t.rotatedCol(this.rotation),i=this.r+t.rotatedRow(this.rotation);this.env.changeCell(e,i,r.food,null)}this.living=!1}updateGrid(){for(var t of this.cells){var e=this.c+t.rotatedCol(this.rotation),i=this.r+t.rotatedRow(this.rotation);this.env.changeCell(e,i,t.type,this)}}update(){if(this.lifetime++,this.lifetime>this.lifespan())return this.die(),this.living;for(var t of(this.food_collected>=this.foodNeeded()&&this.reproduce(),this.cells))this.getRealCell(t).performFunction(this.env);if(!this.living)return this.living;if(this.is_mover){this.move_count++;this.attemptMove();this.move_count>this.move_range&&this.attemptRotate()}return this.living}getRealCell(t,e=this.c,i=this.r,r=this.rotation){var s=e+t.rotatedCol(r),o=i+t.rotatedRow(r);return this.env.grid_map.cellAt(s,o)}}t.exports=h},function(t,e){t.exports={None:0,FoodDrop:1,WallDrop:2,ClickKill:3,Select:4,Edit:5,Clone:6}},function(t,e){t.exports={all:[[0,1],[0,-1],[1,0],[-1,0],[-1,-1],[1,1],[-1,1],[1,-1]],adjacent:[[0,1],[0,-1],[1,0],[-1,0]],corners:[[-1,-1],[1,1],[-1,1],[1,-1]]}},function(t,e){t.exports=class{constructor(t,e,i){this.cell_size=i,this.canvas=document.getElementById(t),this.ctx=this.canvas.getContext("2d"),this.canvas.width=$("#"+e).width(),this.canvas.height=$("#"+e).height(),this.height=this.canvas.height,this.width=this.canvas.width,this.cells_to_render=new Set,this.cells_to_highlight=new Set,this.highlighted_cells=new Set}clear(){this.ctx.fillStyle="white",this.ctx.fillRect(0,0,this.height,this.width)}renderFullGrid(t){for(var e of t)for(var i of e)this.ctx.fillStyle=i.getColor(),this.ctx.fillRect(i.x,i.y,this.cell_size,this.cell_size)}renderCells(){for(var t of this.cells_to_render)this.renderCell(t);this.cells_to_render.clear()}renderCell(t){this.ctx.fillStyle=t.getColor(),this.ctx.fillRect(t.x,t.y,this.cell_size,this.cell_size)}renderOrganism(t){for(var e of t.cells){var i=t.getRealCell(e);this.renderCell(i)}}addToRender(t){this.highlighted_cells.has(t)&&this.cells_to_highlight.add(t),this.cells_to_render.add(t)}renderHighlights(){for(var t of this.cells_to_highlight)this.renderCellHighlight(t),this.highlighted_cells.add(t);this.cells_to_highlight.clear()}highlightOrganism(t){for(var e of t.cells){var i=t.getRealCell(e);this.cells_to_highlight.add(i)}}highlightCell(t){this.cells_to_highlight.add(t)}renderCellHighlight(t,e="yellow"){this.renderCell(t),this.ctx.fillStyle=e,this.ctx.globalAlpha=.5,this.ctx.fillRect(t.x,t.y,this.cell_size,this.cell_size),this.ctx.globalAlpha=1,this.highlighted_cells.add(t)}clearAllHighlights(t=!1){for(var e of this.highlighted_cells)this.renderCell(e);this.highlighted_cells.clear(),t&&this.cells_to_highlight.clear()}}},function(t,e){const i={up:0,down:1,left:2,right:3,scalars:[[0,-1],[0,1],[-1,0],[1,0]],getRandomDirection:function(){return Math.floor(4*Math.random())},getRandomScalar:function(){return this.scalars[Math.floor(Math.random()*this.scalars.length)]}};t.exports=i},function(t,e){t.exports=class{constructor(t,e){this.env=t,this.canvas=e,this.mouse_x,this.mouse_y,this.mouse_c,this.mouse_r,this.left_click=!1,this.right_click=!1,this.cur_cell=null,this.cur_org=null,this.highlight_org=!0,this.defineEvents()}setControlPanel(t){this.control_panel=t}defineEvents(){this.canvas.addEventListener("mousemove",t=>{var e=this.cur_cell,i=this.cur_org;this.mouse_x=t.offsetX,this.mouse_y=t.offsetY;var r=this.env.grid_map.xyToColRow(this.mouse_x,this.mouse_y);this.mouse_c=r[0],this.mouse_r=r[1],this.cur_cell=this.env.grid_map.cellAt(this.mouse_c,this.mouse_r),this.cur_org=this.cur_cell.owner,this.cur_org==i&&this.cur_cell==e||(this.env.renderer.clearAllHighlights(!0),null!=this.cur_org&&this.highlight_org?this.env.renderer.highlightOrganism(this.cur_org):null!=this.cur_cell&&this.env.renderer.highlightCell(this.cur_cell,!0)),this.mouseMove()}),this.canvas.addEventListener("mouseup",function(t){t.preventDefault(),this.left_click=!1,this.right_click=!1}.bind(this)),this.canvas.addEventListener("mousedown",function(t){t.preventDefault(),0==t.button&&(this.left_click=!0),2==t.button&&(this.right_click=!0),this.mouseDown()}.bind(this)),this.canvas.addEventListener("contextmenu",(function(t){t.preventDefault()})),this.canvas.addEventListener("mouseleave",function(){this.right_click=!1,this.left_click=!1,this.env.renderer.clearAllHighlights(!0)}.bind(this))}mouseMove(){alert("mouse move must be overriden")}mouseDown(){alert("mouse down must be overriden")}}},function(t,e,i){const r=i(12),s=i(15),o=i(16);t.exports=class{constructor(){this.fps=60,this.env=new r(5),this.organism_editor=new o,this.controlpanel=new s(this),this.env.OriginOfLife(),this.last_update=Date.now(),this.delta_time=0,this.actual_fps=0,this.running=!1}start(t=60){t<=0&&(t=1),t>300&&(t=300),this.fps=t,this.game_loop=setInterval(function(){this.environmentUpdate()}.bind(this),1e3/t),this.running=!0,this.fps>=60?null!=this.render_loop&&(clearInterval(this.render_loop),this.render_loop=null):this.setRenderLoop()}stop(){clearInterval(this.game_loop),this.running=!1,this.setRenderLoop()}setRenderLoop(){null==this.render_loop&&(this.render_loop=setInterval(function(){this.necessaryUpdate()}.bind(this),1e3/60))}environmentUpdate(){this.delta_time=Date.now()-this.last_update,this.last_update=Date.now(),this.env.update(this.delta_time),this.actual_fps=1/this.delta_time*1e3,null==this.render_loop&&this.necessaryUpdate()}necessaryUpdate(){this.env.render(),this.controlpanel.update(),this.organism_editor.update()}}},function(t,e,i){"use strict";i.r(e);var r=i(10),s=i.n(r);$("document").ready((function(){(new s.a).start(60)}))},function(t,e,i){i(1);const r=i(7),s=i(1),o=i(4),l=i(0),n=(i(2),i(14));t.exports=class{constructor(t){this.renderer=new r("env-canvas","env",t),this.controller=new n(this,this.renderer.canvas),this.grid_rows=Math.floor(this.renderer.height/t),this.grid_cols=Math.floor(this.renderer.width/t),this.grid_map=new s(this.grid_cols,this.grid_rows,t),this.renderer.renderFullGrid(this.grid_map.grid),this.organisms=[],this.walls=[],this.total_mutability=0}update(t){var e=[];for(var i in this.organisms){var r=this.organisms[i];r.living&&r.update()||e.push(i)}this.removeOrganisms(e)}render(){this.renderer.renderCells(),this.renderer.renderHighlights()}removeOrganisms(t){for(var e of t.reverse())this.total_mutability-=this.organisms[e].mutability,this.organisms.splice(e,1)}OriginOfLife(){var t=this.grid_map.getCenter(),e=new o(t[0],t[1],this);e.addCell(l.mouth,0,0),e.addCell(l.producer,-1,-1),e.addCell(l.producer,1,1),this.addOrganism(e)}addOrganism(t){t.updateGrid(),this.total_mutability+=t.mutability,this.organisms.push(t)}averageMutability(){return this.organisms.length<1?0:this.total_mutability/this.organisms.length}changeCell(t,e,i,r){this.grid_map.setCellType(t,e,i),this.grid_map.setCellOwner(t,e,r),this.renderer.addToRender(this.grid_map.cellAt(t,e)),i==l.wall&&this.walls.push(this.grid_map.cellAt(t,e))}clearWalls(){for(var t of this.walls)this.changeCell(t.col,t.row,l.empty,null)}clearOrganisms(){for(var t of this.organisms)t.die();this.organisms=[]}reset(){this.organisms=[],this.grid_map.fillGrid(l.empty),this.renderer.renderFullGrid(this.grid_map.grid),this.total_mutability=0,this.OriginOfLife()}}},function(t,e,i){i(0);const r=i(8);i(3);t.exports=class{constructor(t,e,i){this.type=t,this.loc_col=e,this.loc_row=i}rotatedCol(t){switch(t){case r.up:return this.loc_col;case r.down:return-1*this.loc_col;case r.left:return this.loc_row;case r.right:return-1*this.loc_row}}rotatedRow(t){switch(t){case r.up:return this.loc_row;case r.down:return-1*this.loc_row;case r.left:return-1*this.loc_col;case r.right:return this.loc_col}}}},function(t,e,i){const r=i(9),s=i(4),o=i(5),l=i(0);t.exports=class extends r{constructor(t,e){super(t,e),this.mode=o.None,this.org_to_clone=null}mouseMove(){this.performModeAction()}mouseDown(){this.performModeAction()}performModeAction(){var t=this.mode,e=this.right_click,i=this.left_click;if(t!=o.None&&(e||i)){var r=this.cur_cell;if(null==r)return;switch(t){case o.FoodDrop:i&&r.type==l.empty?this.env.changeCell(r.col,r.row,l.food,null):e&&r.type==l.food&&this.env.changeCell(r.col,r.row,l.empty,null);break;case o.WallDrop:!i||r.type!=l.empty&&r.type!=l.food?e&&r.type==l.wall&&this.env.changeCell(r.col,r.row,l.empty,null):this.env.changeCell(r.col,r.row,l.wall,null);break;case o.ClickKill:null!=this.cur_org&&this.cur_org.die();break;case o.Select:null!=this.cur_org&&this.control_panel.setEditorOrganism(this.cur_org);break;case o.Clone:if(null!=this.org_to_clone){var n=new s(this.mouse_c,this.mouse_r,this.env,this.org_to_clone);n.isClear(this.mouse_c,this.mouse_r)&&this.env.addOrganism(n)}}}}}},function(t,e,i){const r=i(3),s=i(5);i(0);t.exports=class{constructor(t){this.engine=t,this.defineEngineSpeedControls(),this.defineTabNavigation(),this.defineHyperparameterControls(),this.defineModeControls(),this.fps=t.fps,this.organism_record=0,this.env_controller=this.engine.env.controller,this.editor_controller=this.engine.organism_editor.controller,this.env_controller.setControlPanel(this),this.editor_controller.setControlPanel(this)}defineEngineSpeedControls(){this.slider=document.getElementById("slider"),this.slider.oninput=function(){this.fps=this.slider.value,this.engine.running&&this.changeEngineSpeed(this.fps),$("#fps").text("Target FPS: "+this.fps)}.bind(this),$("#pause-button").click(function(){"Pause"==$("#pause-button").text()&&this.engine.running?($("#pause-button").text("Play"),this.engine.stop()):this.engine.running||($("#pause-button").text("Pause"),this.engine.start(this.fps))}.bind(this))}defineTabNavigation(){var t=this;$(".tabnav-item").click((function(){$(".tab").css("display","none");var e="#"+this.id+".tab";t.engine.organism_editor.is_active="editor"==this.id,$(e).css("display","grid")}))}defineHyperparameterControls(){$("#food-prod-prob").change(function(){var t=$("#food-prod-prob").val();$("#fixed-ratio").is(":checked")?(r.foodProdProb=t,r.calcProducerFoodRatio(!1),$("#lifespan-multiplier").val(r.lifespanMultiplier)):r.foodProdProb=t}.bind(this)),$("#lifespan-multiplier").change(function(){var t=$("#lifespan-multiplier").val();$("#fixed-ratio").is(":checked")?(r.lifespanMultiplier=t,r.calcProducerFoodRatio(!0),$("#food-prod-prob").val(r.foodProdProb)):r.lifespanMultiplier=t}.bind(this)),$(".mut-prob").change((function(){switch(this.id){case"add-prob":r.addProb=this.value,r.balanceMutationProbs(1);break;case"change-prob":r.changeProb=this.value,r.balanceMutationProbs(2);break;case"remove-prob":r.removeProb=this.value,r.balanceMutationProbs(3)}$("#add-prob").val(Math.floor(r.addProb)),$("#change-prob").val(Math.floor(r.changeProb)),$("#remove-prob").val(Math.floor(r.removeProb))})),$("#mover-rot").change((function(){r.moversCanRotate=this.checked})),$("#offspring-rot").change((function(){r.offspringRotate=this.checked})),$("#insta-kill").change((function(){r.instaKill=this.checked}))}defineModeControls(){var t=this;$("#editor-mode").change((function(e){var i=$(this).children("option:selected").val(),r=t.env_controller.mode;switch($("#cell-selections").css("display","none"),i){case"none":t.setMode(s.None);break;case"food":t.setMode(s.FoodDrop);break;case"wall":t.setMode(s.WallDrop);break;case"kill":t.setMode(s.ClickKill);break;case"select":r==s.Edit||r==s.Clone&&t.engine.organism_editor.organism.cells.length>1?confirm("Selecting a new organism will clear the current organism. Are you sure you wish to switch?")?t.setMode(s.Select):$("#editor-mode").val("edit"):t.setMode(s.Select);break;case"edit":t.setMode(s.Edit),$("#cell-selections").css("display","grid");break;case"clone":t.setMode(s.Clone),t.env_controller.org_to_clone=t.engine.organism_editor.getCopyOfOrg()}})),$("#reset-env").click(function(){this.engine.env.reset()}.bind(this)),$("#kill-all").click(function(){this.engine.env.clearOrganisms()}.bind(this)),$("#clear-walls").click(function(){this.engine.env.clearWalls()}.bind(this)),$("#clear-editor").click(function(){this.engine.organism_editor.clear()}.bind(this))}setMode(t){this.env_controller.mode=t,this.editor_controller.mode=t}setEditorOrganism(t){this.engine.organism_editor.setOrganismToCopyOf(t)}changeEngineSpeed(t){this.engine.stop(),this.engine.start(t),this.fps=this.engine.fps}update(){$("#fps-actual").text("Actual FPS: "+Math.floor(this.engine.actual_fps));var t=this.engine.env.organisms.length;$("#org-count").text("Organism count: "+t),t>this.organism_record&&(this.organism_record=t),$("#org-record").text("Highest count: "+this.organism_record),$("#avg-mut").text("Average Mutation Rate: "+Math.round(100*this.engine.env.averageMutability())/100)}}},function(t,e,i){const r=i(4),s=i(1),o=i(7),l=i(0),n=i(17);i(2);t.exports=class{constructor(){this.is_active=!0;this.grid_map=new s(11,11,20),this.renderer=new o("editor-canvas","editor-env",20),this.controller=new n(this,this.renderer.canvas),this.clear(),this.renderer.renderFullGrid(this.grid_map.grid)}update(){this.is_active&&this.renderer.renderHighlights()}changeCell(t,e,i,r){this.grid_map.setCellType(t,e,i),this.grid_map.setCellOwner(t,e,r),this.renderer.renderFullGrid(this.grid_map.grid)}addCellToOrg(t,e,i){var r=this.grid_map.getCenter(),s=t-r[0],o=e-r[1],l=this.organism.getLocalCell(s,o);null!=l?(l.type=i,this.changeCell(t,e,i,this.organism)):this.organism.addCell(i,s,o)&&this.changeCell(t,e,i,this.organism)}removeCellFromOrg(t,e){var i=this.grid_map.getCenter(),r=t-i[0],s=e-i[1];0!=r||0!=s?null!=this.organism.getLocalCell(r,s)&&this.organism.removeCell(r,s)&&this.changeCell(t,e,l.empty,null):alert("Cannot remove center cell")}setOrganismToCopyOf(t){this.grid_map.fillGrid(l.empty);var e=this.grid_map.getCenter();this.organism=new r(e[0],e[1],this,t),this.organism.updateGrid()}getCopyOfOrg(){return new r(0,0,null,this.organism)}clear(){this.grid_map.fillGrid(l.empty);var t=this.grid_map.getCenter();this.organism=new r(t[0],t[1],this,null),this.organism.addCell(l.mouth,0,0),this.organism.updateGrid()}}},function(t,e,i){const r=i(9),s=i(5),o=i(0);t.exports=class extends r{constructor(t,e){super(t,e),this.mode=s.None,this.edit_cell_type=null,this.highlight_org=!1,this.defineCellTypeSelection()}mouseMove(){}mouseDown(){null!=this.edit_cell_type&&this.mode==s.Edit&&(this.left_click&&this.env.addCellToOrg(this.mouse_c,this.mouse_r,this.edit_cell_type),this.right_click&&this.env.removeCellFromOrg(this.mouse_c,this.mouse_r))}defineCellTypeSelection(){var t=this;$(".cell-type").click((function(){switch(this.id){case"mouth":t.edit_cell_type=o.mouth;break;case"producer":t.edit_cell_type=o.producer;break;case"mover":t.edit_cell_type=o.mover;break;case"killer":t.edit_cell_type=o.killer;break;case"armor":t.edit_cell_type=o.armor}$(".cell-type").css("border-color","black"),$("#"+this.id).css("border-color","yellow")}))}}}]); \ No newline at end of file diff --git a/dist/css/style.css b/dist/css/style.css index f5614f0..e00438f 100644 --- a/dist/css/style.css +++ b/dist/css/style.css @@ -8,7 +8,6 @@ body{ canvas { display: block; - } * { @@ -16,7 +15,7 @@ canvas { padding: 0; } -.env { +#env { position: absolute; top: 0; bottom: 300px; /* must correspond to control-panel height*/ @@ -52,15 +51,49 @@ canvas { grid-row: 2; } -#hyperparameters { - grid-column: 2; +#tab-container { + grid-column: 2 / 4; grid-row: 1 / 3; +} + +.tabnav { + overflow: hidden; + background-color: #333; +} +.tabnav p { + float: left; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; + font-size: 17px; + user-select: none; +} +.tabnav p:hover { + background-color: rgb(121, 121, 121); + color: black; +} + +.tab { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: 1; +} + +.tab#hyperparameters { + display: none; } -#abilities { - grid-column: 3; - grid-row: 1 / 3; +.tab#editor { + display: grid; +} + +.left-half { + grid-column: 1; +} + +.right-half { + grid-column: 2; } .control-mode-button { @@ -70,4 +103,53 @@ canvas { #none-button { background-color: gray; +} + + + +#editor-env { + position: absolute; + height: 220px; + width: 220px; + padding: 10px; + float: left; +} +#cell-selections { + display: none; + padding: 10px; + grid-template-columns: 1; + grid-template-rows: 5; + float: right; +} +.cell-type { + grid-column: 1; + width: 30px; + height: 30px; + border: solid black; + border-width: 2px; + margin: 1px; + +} +#mouth{ + background-color: orange; + grid-row: 1; +} +#producer{ + background-color: white; + grid-row: 2; +} +#mover{ + background-color: #3493eb; + grid-row: 3; +} +#killer{ + background-color: red; + grid-row: 4; +} +#armor{ + background-color: purple; + grid-row: 5; +} +#editor-mode-cont{ + padding-top: 20px; } \ No newline at end of file diff --git a/dist/html/index.html b/dist/html/index.html index 5768a25..1735825 100644 --- a/dist/html/index.html +++ b/dist/html/index.html @@ -4,14 +4,13 @@ Evolution Simulator - -
- +
+
@@ -20,6 +19,8 @@

Target FPS: 60

+
+
@@ -28,48 +29,84 @@

Highest count:

Average Mutation Rate:

- -
-

Hyperparameters

- - - - -
- - -

-

Mutation Probabilities

- - -
- - -
- - -
-

Organism Rotation

- - -
- - -
- - -
-
-

Control Mode

- - - - -

- - - +
+ +
+

Editor

+

Hyperparameters

+
+ +
+
+

Editor

+ + +

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

Hyperparameters

+

Food Production vs Lifespan

+ + +
+ + +
+ + +

+

Mutation Probabilities

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

Organism Rotation

+ + +
+ + +
+

Killer Cell Effects

+ + +
+
diff --git a/src/EnvironmentController.js b/src/Controllers/CanvasController.js similarity index 54% rename from src/EnvironmentController.js rename to src/Controllers/CanvasController.js index 8c7b239..112b897 100644 --- a/src/EnvironmentController.js +++ b/src/Controllers/CanvasController.js @@ -1,7 +1,6 @@ -const Modes = require("./ControlModes"); -const CellTypes = require("./CellTypes"); -class EnvironmentController{ + +class CanvasController{ constructor(env, canvas) { this.env = env; this.canvas = canvas; @@ -13,10 +12,14 @@ class EnvironmentController{ this.right_click = false; this.cur_cell = null; this.cur_org = null; - this.mode = Modes.None; + this.highlight_org = true; this.defineEvents(); } + setControlPanel(panel){ + this.control_panel = panel; + } + defineEvents() { this.canvas.addEventListener('mousemove', e => { var prev_cell = this.cur_cell; @@ -32,14 +35,14 @@ class EnvironmentController{ if (this.cur_org != prev_org || this.cur_cell != prev_cell) { this.env.renderer.clearAllHighlights(true); - if (this.cur_org != null) { + if (this.cur_org != null && this.highlight_org) { this.env.renderer.highlightOrganism(this.cur_org); } else if (this.cur_cell != null) { this.env.renderer.highlightCell(this.cur_cell, true); } } - this.performModeAction(); + this.mouseMove(); }); this.canvas.addEventListener('mouseup', function(evt) { @@ -55,7 +58,7 @@ class EnvironmentController{ } if (evt.button == 2) this.right_click = true; - this.performModeAction(); + this.mouseDown(); }.bind(this)); this.canvas.addEventListener('contextmenu', function(evt) { @@ -65,44 +68,18 @@ class EnvironmentController{ this.canvas.addEventListener('mouseleave', function(){ this.right_click = false; this.left_click = false; + this.env.renderer.clearAllHighlights(true); }.bind(this)); } - performModeAction() { - var mode = this.mode; - var right_click = this.right_click; - var left_click = this.left_click; - if (mode != Modes.None && (right_click || left_click)) { - var cell = this.cur_cell; - if (cell == null){ - return; - } - switch(mode) { - case Modes.FoodDrop: - if (left_click && cell.type == CellTypes.empty){ - this.env.changeCell(cell.col, cell.row, CellTypes.food, null); - } - else if (right_click && cell.type == CellTypes.food){ - this.env.changeCell(cell.col, cell.row, CellTypes.empty, null); - } - break; - case Modes.WallDrop: - if (left_click && (cell.type == CellTypes.empty || cell.type == CellTypes.food)){ - this.env.changeCell(cell.col, cell.row, CellTypes.wall, null); - } - else if (right_click && cell.type == CellTypes.wall){ - this.env.changeCell(cell.col, cell.row, CellTypes.empty, null); - } - break; - case Modes.ClickKill: - if (this.cur_org != null) - this.cur_org.die(); - } - } + mouseMove() { + alert("mouse move must be overriden"); } - + mouseDown() { + alert("mouse down must be overriden"); + } } -module.exports = EnvironmentController; +module.exports = CanvasController; \ No newline at end of file diff --git a/src/ControlModes.js b/src/Controllers/ControlModes.js similarity index 60% rename from src/ControlModes.js rename to src/Controllers/ControlModes.js index 9801a62..3afd02d 100644 --- a/src/ControlModes.js +++ b/src/Controllers/ControlModes.js @@ -2,7 +2,10 @@ const Modes = { None: 0, FoodDrop: 1, WallDrop: 2, - ClickKill: 3 + ClickKill: 3, + Select: 4, + Edit: 5, + Clone: 6 } module.exports = Modes; \ No newline at end of file diff --git a/src/ControlPanel.js b/src/Controllers/ControlPanel.js similarity index 63% rename from src/ControlPanel.js rename to src/Controllers/ControlPanel.js index 462dcb4..8e28a85 100644 --- a/src/ControlPanel.js +++ b/src/Controllers/ControlPanel.js @@ -1,16 +1,20 @@ -const Hyperparams = require("./Hyperparameters"); +const Hyperparams = require("../Hyperparameters"); const Modes = require("./ControlModes"); -const CellTypes = require("./CellTypes"); +const CellTypes = require("../Organism/Cell/CellTypes"); class ControlPanel { constructor(engine) { this.engine = engine; this.defineEngineSpeedControls(); + this.defineTabNavigation(); this.defineHyperparameterControls(); this.defineModeControls(); this.fps = engine.fps; this.organism_record=0; this.env_controller = this.engine.env.controller; + this.editor_controller = this.engine.organism_editor.controller; + this.env_controller.setControlPanel(this); + this.editor_controller.setControlPanel(this); } defineEngineSpeedControls(){ @@ -35,6 +39,16 @@ class ControlPanel { }.bind(this)); } + defineTabNavigation() { + var self = this; + $('.tabnav-item').click(function() { + $('.tab').css('display', 'none'); + var tab = '#'+this.id+'.tab'; + self.engine.organism_editor.is_active = (this.id == 'editor'); + $(tab).css('display', 'grid'); + }); + } + defineHyperparameterControls() { $('#food-prod-prob').change(function() { var food_prob = $('#food-prod-prob').val(); @@ -91,23 +105,45 @@ class ControlPanel { defineModeControls() { var self = this; - $('.control-mode-button').click( function() { - switch(this.id){ - case "food-button": - self.env_controller.mode = Modes.FoodDrop; + $('#editor-mode').change( function(el) { + var selection = $(this).children("option:selected").val(); + var prev_mode = self.env_controller.mode; + $('#cell-selections').css('display', 'none'); + switch(selection){ + case "none": + self.setMode(Modes.None); break; - case "wall-button": - self.env_controller.mode = Modes.WallDrop; + case "food": + self.setMode(Modes.FoodDrop); break; - case "kill-button": - self.env_controller.mode = Modes.ClickKill; + case "wall": + self.setMode(Modes.WallDrop); break; - case "none-button": - self.env_controller.mode = Modes.None; + case "kill": + self.setMode(Modes.ClickKill); + break; + case "select": + if (prev_mode==Modes.Edit || prev_mode==Modes.Clone && self.engine.organism_editor.organism.cells.length > 1){ + if (confirm("Selecting a new organism will clear the current organism. Are you sure you wish to switch?")) { + self.setMode(Modes.Select); + } + else { + $("#editor-mode").val('edit'); + } + } + else { + self.setMode(Modes.Select); + } + break; + case "edit": + self.setMode(Modes.Edit); + $('#cell-selections').css('display', 'grid'); + break; + case "clone": + self.setMode(Modes.Clone); + self.env_controller.org_to_clone = self.engine.organism_editor.getCopyOfOrg(); break; } - $(".control-mode-button" ).css( "background-color", "lightgray" ); - $("#"+this.id).css("background-color", "darkgray"); }); @@ -120,6 +156,18 @@ class ControlPanel { $('#clear-walls').click( function() { this.engine.env.clearWalls(); }.bind(this)); + $('#clear-editor').click( function() { + this.engine.organism_editor.clear(); + }.bind(this)); + } + + setMode(mode) { + this.env_controller.mode = mode; + this.editor_controller.mode = mode; + } + + setEditorOrganism(org) { + this.engine.organism_editor.setOrganismToCopyOf(org); } changeEngineSpeed(change_val) { diff --git a/src/Controllers/EditorController.js b/src/Controllers/EditorController.js new file mode 100644 index 0000000..80963f7 --- /dev/null +++ b/src/Controllers/EditorController.js @@ -0,0 +1,53 @@ +const CanvasController = require("./CanvasController"); +const Modes = require("./ControlModes"); +const CellTypes = require("../Organism/Cell/CellTypes"); + +class EditorController extends CanvasController{ + constructor(env, canvas) { + super(env, canvas); + this.mode = Modes.None; + this.edit_cell_type = null; + this.highlight_org = false; + this.defineCellTypeSelection(); + } + + mouseMove() { + + } + + mouseDown() { + if (this.edit_cell_type == null || this.mode != Modes.Edit) + return; + if (this.left_click) + this.env.addCellToOrg(this.mouse_c, this.mouse_r, this.edit_cell_type); + if (this.right_click) + this.env.removeCellFromOrg(this.mouse_c, this.mouse_r); + } + + defineCellTypeSelection() { + var self = this; + $('.cell-type').click( function() { + switch(this.id){ + case "mouth": + self.edit_cell_type = CellTypes.mouth; + break; + case "producer": + self.edit_cell_type = CellTypes.producer; + break; + case "mover": + self.edit_cell_type = CellTypes.mover; + break; + case "killer": + self.edit_cell_type = CellTypes.killer; + break; + case "armor": + self.edit_cell_type = CellTypes.armor; + break; + } + $(".cell-type" ).css( "border-color", "black" ); + $("#"+this.id).css("border-color", "yellow"); + }); + } +} + +module.exports = EditorController; diff --git a/src/Controllers/EnvironmentController.js b/src/Controllers/EnvironmentController.js new file mode 100644 index 0000000..5c908f5 --- /dev/null +++ b/src/Controllers/EnvironmentController.js @@ -0,0 +1,73 @@ +const CanvasController = require("./CanvasController"); +const Organism = require('../Organism/Organism'); +const Modes = require("./ControlModes"); +const CellTypes = require("../Organism/Cell/CellTypes"); + +class EnvironmentController extends CanvasController{ + constructor(env, canvas) { + super(env, canvas); + this.mode = Modes.None; + this.org_to_clone = null; + } + + mouseMove() { + this.performModeAction(); + } + + mouseDown() { + this.performModeAction(); + } + + performModeAction() { + var mode = this.mode; + var right_click = this.right_click; + var left_click = this.left_click; + if (mode != Modes.None && (right_click || left_click)) { + var cell = this.cur_cell; + if (cell == null){ + return; + } + switch(mode) { + case Modes.FoodDrop: + if (left_click && cell.type == CellTypes.empty){ + this.env.changeCell(cell.col, cell.row, CellTypes.food, null); + } + else if (right_click && cell.type == CellTypes.food){ + this.env.changeCell(cell.col, cell.row, CellTypes.empty, null); + } + break; + case Modes.WallDrop: + if (left_click && (cell.type == CellTypes.empty || cell.type == CellTypes.food)){ + this.env.changeCell(cell.col, cell.row, CellTypes.wall, null); + } + else if (right_click && cell.type == CellTypes.wall){ + this.env.changeCell(cell.col, cell.row, CellTypes.empty, null); + } + break; + case Modes.ClickKill: + if (this.cur_org != null) + this.cur_org.die(); + break; + + case Modes.Select: + if (this.cur_org != null){ + this.control_panel.setEditorOrganism(this.cur_org); + } + break; + + 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 (new_org.isClear(this.mouse_c, this.mouse_r)){ + this.env.addOrganism(new_org) + } + } + break; + } + } + } + + +} + +module.exports = EnvironmentController; diff --git a/src/Engine.js b/src/Engine.js index 0e86928..f70501b 100644 --- a/src/Engine.js +++ b/src/Engine.js @@ -1,5 +1,6 @@ -const Environment = require('./Environment'); -const ControlPanel = require('./ControlPanel'); +const Environment = require('./Environments/Environment'); +const ControlPanel = require('./Controllers/ControlPanel'); +const OrganismEditor = require('./Environments/OrganismEditor'); const render_speed = 60; @@ -7,6 +8,7 @@ class Engine{ constructor(){ this.fps = 60; this.env = new Environment(5); + this.organism_editor = new OrganismEditor(); this.controlpanel = new ControlPanel(this); this.env.OriginOfLife(); this.last_update = Date.now(); @@ -21,7 +23,7 @@ class Engine{ if (fps > 300) fps = 300; this.fps = fps; - this.game_loop = setInterval(function(){this.update();}.bind(this), 1000/fps); + this.game_loop = setInterval(function(){this.environmentUpdate();}.bind(this), 1000/fps); this.running = true; if (this.fps >= render_speed) { if (this.render_loop != null) { @@ -41,23 +43,28 @@ class Engine{ setRenderLoop() { if (this.render_loop == null) { - this.render_loop = setInterval(function(){this.env.render();this.controlpanel.update();}.bind(this), 1000/render_speed); + this.render_loop = setInterval(function(){this.necessaryUpdate();}.bind(this), 1000/render_speed); } } - update() { + environmentUpdate() { this.delta_time = Date.now() - this.last_update; this.last_update = Date.now(); this.env.update(this.delta_time); this.actual_fps = 1/this.delta_time*1000; if(this.render_loop == null){ - this.env.render(); - this.controlpanel.update(); + this.necessaryUpdate(); } } + necessaryUpdate() { + this.env.render(); + this.controlpanel.update(); + this.organism_editor.update(); + } + } module.exports = Engine; diff --git a/src/Environment.js b/src/Environments/Environment.js similarity index 81% rename from src/Environment.js rename to src/Environments/Environment.js index e7d1b10..dac06b5 100644 --- a/src/Environment.js +++ b/src/Environments/Environment.js @@ -1,19 +1,19 @@ -const Grid = require('./GridMap'); -const Renderer = require('./Rendering/Renderer'); -const GridMap = require('./GridMap'); -const Organism = require('./Organism'); -const CellTypes = require('./CellTypes'); -const Cell = require('./Cell'); -const EnvironmentController = require('./EnvironmentController'); +const Grid = require('../Grid/GridMap'); +const Renderer = require('../Rendering/Renderer'); +const GridMap = require('../Grid/GridMap'); +const Organism = require('../Organism/Organism'); +const CellTypes = require('../Organism/Cell/CellTypes'); +const Cell = require('../Organism/Cell/Cell'); +const EnvironmentController = require('../Controllers/EnvironmentController'); class Environment{ constructor(cell_size) { - this.renderer = new Renderer('canvas', this, cell_size); + this.renderer = new Renderer('env-canvas', 'env', cell_size); this.controller = new EnvironmentController(this, this.renderer.canvas); this.grid_rows = Math.floor(this.renderer.height / cell_size); this.grid_cols = Math.floor(this.renderer.width / cell_size); this.grid_map = new GridMap(this.grid_cols, this.grid_rows, cell_size); - this.renderer.renderFullGrid(); + this.renderer.renderFullGrid(this.grid_map.grid); this.organisms = []; this.walls = []; this.total_mutability = 0; @@ -85,7 +85,7 @@ class Environment{ reset() { this.organisms = []; this.grid_map.fillGrid(CellTypes.empty); - this.renderer.renderFullGrid(); + this.renderer.renderFullGrid(this.grid_map.grid); this.total_mutability = 0; this.OriginOfLife(); } diff --git a/src/Environments/OrganismEditor.js b/src/Environments/OrganismEditor.js new file mode 100644 index 0000000..0604591 --- /dev/null +++ b/src/Environments/OrganismEditor.js @@ -0,0 +1,84 @@ +const Organism = require("../Organism/Organism"); +const GridMap = require('../Grid/GridMap'); +const Renderer = require('../Rendering/Renderer'); +const CellTypes = require('../Organism/Cell/CellTypes'); +const EditorController = require("../Controllers/EditorController"); +const Cell = require("../Organism/Cell/Cell"); + +class OrganismEditor { + constructor() { + this.is_active = true; + var cell_size = 20; + this.grid_map = new GridMap(11, 11, cell_size); + this.renderer = new Renderer('editor-canvas', 'editor-env', cell_size); + this.controller = new EditorController(this, this.renderer.canvas); + this.clear(); + + this.renderer.renderFullGrid(this.grid_map.grid); + } + + update() { + if (this.is_active){ + this.renderer.renderHighlights(); + } + } + + changeCell(c, r, type, owner) { + this.grid_map.setCellType(c, r, type); + this.grid_map.setCellOwner(c, r, owner); + this.renderer.renderFullGrid(this.grid_map.grid); + } + + // absolute c r, not local + addCellToOrg(c, r, type) { + var center = this.grid_map.getCenter(); + var loc_c = c - center[0]; + var loc_r = r - center[1]; + var prev_cell = this.organism.getLocalCell(loc_c, loc_r) + if (prev_cell != null) { + prev_cell.type = type; + this.changeCell(c, r, type, this.organism); + } + else if (this.organism.addCell(type, loc_c, loc_r)){ + this.changeCell(c, r, type, this.organism); + } + } + + removeCellFromOrg(c, r) { + var center = this.grid_map.getCenter(); + var loc_c = c - center[0]; + var loc_r = r - center[1]; + if (loc_c == 0 && loc_r == 0){ + alert("Cannot remove center cell"); + return; + } + var prev_cell = this.organism.getLocalCell(loc_c, loc_r) + if (prev_cell != null) { + if (this.organism.removeCell(loc_c, loc_r)) { + this.changeCell(c, r, CellTypes.empty, null); + } + } + } + + setOrganismToCopyOf(orig_org){ + this.grid_map.fillGrid(CellTypes.empty); + var center = this.grid_map.getCenter(); + this.organism = new Organism(center[0], center[1], this, orig_org); + this.organism.updateGrid(); + } + + getCopyOfOrg() { + var new_org = new Organism(0, 0, null, this.organism); + return new_org; + } + + clear() { + this.grid_map.fillGrid(CellTypes.empty); + var center = this.grid_map.getCenter(); + this.organism = new Organism(center[0], center[1], this, null); + this.organism.addCell(CellTypes.mouth, 0, 0); + this.organism.updateGrid(); + } +} + +module.exports = OrganismEditor; \ No newline at end of file diff --git a/src/GridMap.js b/src/Grid/GridMap.js similarity index 94% rename from src/GridMap.js rename to src/Grid/GridMap.js index 5d353cd..7611a4a 100644 --- a/src/GridMap.js +++ b/src/Grid/GridMap.js @@ -1,5 +1,5 @@ -const Cell = require('./Cell'); -const CellTypes = require('./CellTypes'); +const Cell = require('../Organism/Cell/Cell'); +const CellTypes = require('../Organism/Cell/CellTypes'); class GridMap { constructor(cols, rows, cell_size, filltype=CellTypes.empty) { diff --git a/src/Neighbors.js b/src/Grid/Neighbors.js similarity index 100% rename from src/Neighbors.js rename to src/Grid/Neighbors.js diff --git a/src/Hyperparameters.js b/src/Hyperparameters.js index f745239..d39dfd1 100644 --- a/src/Hyperparameters.js +++ b/src/Hyperparameters.js @@ -1,4 +1,4 @@ -const Neighbors = require("./Neighbors"); +const Neighbors = require("./Grid/Neighbors"); const Hyperparams = { lifespanMultiplier: 100, diff --git a/src/Cell.js b/src/Organism/Cell/Cell.js similarity index 97% rename from src/Cell.js rename to src/Organism/Cell/Cell.js index 34d74d8..b976bbe 100644 --- a/src/Cell.js +++ b/src/Organism/Cell/Cell.js @@ -1,5 +1,5 @@ const CellTypes = require("./CellTypes"); -const Hyperparams = require("./Hyperparameters"); +const Hyperparams = require("../../Hyperparameters"); // A cell exists in a grid system. class Cell{ diff --git a/src/CellTypes.js b/src/Organism/Cell/CellTypes.js similarity index 100% rename from src/CellTypes.js rename to src/Organism/Cell/CellTypes.js diff --git a/src/LocalCell.js b/src/Organism/Cell/LocalCell.js similarity index 91% rename from src/LocalCell.js rename to src/Organism/Cell/LocalCell.js index 25725e3..5eee5d7 100644 --- a/src/LocalCell.js +++ b/src/Organism/Cell/LocalCell.js @@ -1,6 +1,6 @@ const CellTypes = require("./CellTypes"); -const Directions = require("./Directions"); -const Hyperparams = require("./Hyperparameters"); +const Directions = require("../Directions"); +const Hyperparams = require("../../Hyperparameters"); // A local cell is a lightweight container for a cell in an organism. It does not directly exist in the grid class LocalCell{ diff --git a/src/Directions.js b/src/Organism/Directions.js similarity index 100% rename from src/Directions.js rename to src/Organism/Directions.js diff --git a/src/Organism.js b/src/Organism/Organism.js similarity index 91% rename from src/Organism.js rename to src/Organism/Organism.js index ff7e6f1..a1cf6db 100644 --- a/src/Organism.js +++ b/src/Organism/Organism.js @@ -1,9 +1,9 @@ -const CellTypes = require("./CellTypes"); -const Cell = require("./Cell"); -const GridMap = require("./GridMap"); -const LocalCell = require("./LocalCell"); -const Neighbors = require("./Neighbors"); -const Hyperparams = require("./Hyperparameters"); +const CellTypes = require("./Cell/CellTypes"); +const Cell = require("./Cell/Cell"); +const GridMap = require("../Grid/GridMap"); +const LocalCell = require("./Cell/LocalCell"); +const Neighbors = require("../Grid/Neighbors"); +const Hyperparams = require("../Hyperparameters"); const Directions = require("./Directions"); const directions = [[0,1],[0,-1],[1,0],[-1,0]] @@ -33,9 +33,11 @@ class Organism { addCell(type, c, r) { for (var cell of this.cells) { - if (cell.loc_col == c && cell.loc_row == r) + if (cell.loc_col == c && cell.loc_row == r){ return false; + } } + this.checkProducerMover(type); this.cells.push(new LocalCell(type, c, r)); return true; @@ -65,6 +67,15 @@ class Organism { return true; } + getLocalCell(c, r) { + for (var cell of this.cells) { + if (cell.loc_col == c && cell.loc_row == r){ + return cell; + } + } + return null; + } + checkProducerMover(type) { if (type == CellTypes.producer) this.is_producer = true; @@ -125,8 +136,11 @@ class Organism { var direction_r = direction[1]; var offset = (Math.floor(Math.random() * 2)) * 2; - var new_c = this.c + (direction_c*this.cells.length*2) + (direction_c*offset); - var new_r = this.r + (direction_r*this.cells.length*2) + (direction_r*offset); + var new_c = this.c + (direction_c*Math.min(this.cells.length*2, 15)) + (direction_c*offset); + var new_r = this.r + (direction_r*Math.min(this.cells.length*2, 15)) + (direction_r*offset); + + // var new_c = Math.min(this.cells.length*2, 10); + // var new_r = Math.min(this.cells.length*2, 10); if (org.isClear(new_c, new_r) && org.isStraightPath(new_c, new_r, this.c, this.r, this)){ org.c = new_c; org.r = new_r; diff --git a/src/Rendering/Renderer.js b/src/Rendering/Renderer.js index 2eac3d4..75accf5 100644 --- a/src/Rendering/Renderer.js +++ b/src/Rendering/Renderer.js @@ -1,15 +1,14 @@ // Renderer controls access to a canvas. There is one renderer for each canvas class Renderer { - constructor(canvas_id, env, cell_size) { + constructor(canvas_id, container_id, cell_size) { this.cell_size = cell_size; - this.env = env; this.canvas = document.getElementById(canvas_id); this.ctx = this.canvas.getContext("2d"); - this.canvas.width = $('.env').width(); - this.canvas.height = $('.env').height(); - this.height = canvas.height; - this.width = canvas.width; + this.canvas.width = $('#'+container_id).width(); + this.canvas.height = $('#'+container_id).height(); + this.height = this.canvas.height; + this.width = this.canvas.width; this.cells_to_render = new Set(); this.cells_to_highlight = new Set(); this.highlighted_cells = new Set(); @@ -20,8 +19,7 @@ class Renderer { this.ctx.fillRect(0, 0, this.height, this.width); } - renderFullGrid() { - var grid = this.env.grid_map.grid; + renderFullGrid(grid) { for (var col of grid) { for (var cell of col){ this.ctx.fillStyle = cell.getColor(); diff --git a/src/index.js b/src/index.js index a0992bc..ec13089 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -'user strict'; +'use strict'; import Engine from './Engine';