diff --git a/README.md b/README.md index 689382d..8c5723b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # construct [![Build Status](https://travis-ci.org/tmptrash/construct.svg?branch=master)](https://travis-ci.org/tmptrash/construct) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/da2d5c5f53d04df79c9aae3599555b4e)](https://www.codacy.com/app/flatline/construct?utm_source=github.com&utm_medium=referral&utm_content=tmptrash/construct&utm_campaign=Badge_Grade) -

construct is a native JavaScript/ES6 based, digital organisms evolution simulator. It's used for study the evolutionary biology of self-replicating and evolving computer programs (digital organisms). This project similar to Avida, but works with more abstract language (Digital Organism Script - DOS) instead of assembler. It uses special DOSVM for running DOS byte code and distributed computing to speed up the calculations. Generally, it consists of servers, which just a proxies between clients. All calculations are made on a client side only. It's possible to run the system in a "serverless" mode. For this, you have to run index.html (just drop it into the browser) in Chrome without server. More details for russian speaking people on blog and youtube channel. See video presentation here. +

construct is a native JavaScript/ES6 based, digital organisms evolution simulator. It's used for study the evolutionary biology of self-replicating and evolving computer programs (digital organisms). This project similar to Avida, but works with more abstract language (Digital Organism Script - DOS) instead of assembler. It uses special DOSVM for running DOS byte code and distributed computing to speed up the calculations. Generally, it consists of servers, which just a proxies between clients. All calculations are made on a client side only. It's possible to run the system in a "serverless" mode. For this, you have to run index.html (just drop it into the browser) in Chrome without server. More details for russian speaking people on blog and youtube channel. See video presentation here.

# Requirements @@ -27,6 +27,21 @@ - Run server `npm run server` on chosen host - Copy `./client/dist/index.html` and `./client/dist/app.js` on all your remote machines and run it there under Chrome +# Main commands +As an administrator, you may affect the system by command line API. For instance, you may obtain amount of organisms in current population or set new configuration in real time. For this, you have to open Chrome console (press `F12`) and type `man.api[.namespace].xxx()`. Where `namespace` is an optional unit or module and `xxx()` is supported command of this module. It's possible to use `desc` property to get command description. Example: `man.api.getConfig.desc`. Here are all available commands separated by namespace: +- global namespace - `man.api`: + - `man.api.visualize(show:Boolean = true)` - Turns on/off visualization in browser for current instance (world). Turning visualization off, increases application speed. + - `man.api.formatCode(code:Array)` - Converts byte code array into human readable JavaScript based code. This function is low level. For using it you have to get organism's virtual machine reference and then use it's `code` property. For example: `man.api.formatCode(man.api.organisms.getOrganism('128').vm.code)`. This example will find organism with id `128` and shows his byte code. + - `man.api.version` - Returns current app version + - `man.api.getConfig(path:String)` - Returns specified config value. First parameter is a namespace (optional) and config name. For example, to get maximum amount of organisms in current instance/world type: `man.api.getConfig('organisms.orgMaxOrgs')`. Example of organism related configs you may find [here](https://github.com/tmptrash/construct/blob/master/client/src/manager/plugins/organisms/Config.js). Other configuration parameters are located in files with name `Config.js`. + - `man.api.setConfig(path:String, value:Any)` - Sets configuration value in real time. Opposite to `getConfig()`. +- charts namespace - `man.api.charts`. This namespace is related to statistics in charts. There are many parameters like average code size, organisms amount, amount of picked energy and so on. See details [here](https://github.com/tmptrash/construct/blob/master/client/src/manager/plugins/status/charts/Config.js) in charts property. You may show and hide different charts on a canvas, locate them and reset any time you need: + - `man.api.charts.on([[name:String = undefined[, show:Boolean = true]])` - shows chart(s) by name. List of all available names you may find [here](https://github.com/tmptrash/construct/blob/master/client/src/manager/plugins/status/charts/Config.js). Example: `man.api.charts.on('energy')` - will show chart of average organism energy at the moment. Calling this method without parameters shows all available charts. Calling this method with only one string parameter shows specified chart. Calling this method with two parameters shows/hides specified chart depending on second Boolean parameter. Example: `man.api.charts.on()` - shows all charts. `man.api.charts.on('energy')` - shows energy chart only. `man.api.charts.on('energy', false)` - hides energy chart only. + - `man.api.charts.off(name:String = undefined)` - opposite to `on()`. Hides specified or all charts (without parameters) from the canvas. + - `man.api.charts.pos(name:String, pos:String)` - Locates chart according to specified position. Available positions are: `full`, `top`, `down`, `left`, `right`, `topleft`, `downleft`, `topright`, `downright`. Example: `man.api.charts.pos('code', 'full')` - shows `code` trend chart on full screen. All available chart names are [here](https://github.com/tmptrash/construct/blob/master/client/src/manager/plugins/status/charts/Config.js). + - `man.api.charts.pos9(name:String, x:Number, y:Number)` - The same like `pos()`, but with chart coordinates in 3x3 grid. For example: `man.api.charts.pos9('energy', 0, 2)` - will positioning energy chart at the location `x:0, y:2`. + - `man.api.charts.pos16(name:String, x:Number, y:Number)` - The same like `pos9()`, but for grid 4x4. + - `man.api.charts.transparent(name:String, val:Number)` - Sets chart transparency. `val` should be between `0..1`. `val` parameter is optional. In this case all charts will have same transparency. Note: to improve speed, type `man.api.visualize(false)` in Chrome's devtool console during application run ___ -P.S. If you `ES6 js developer` | `Canvas 2D developer` | `Node.js developer` | you just a - join us! \ No newline at end of file +P.S. If you are a `ES6 js developer` | `Canvas 2D developer` | `Node.js developer` | you just a - join us! diff --git a/client/src/manager/Manager.js b/client/src/manager/Manager.js index c39461a..190f614 100644 --- a/client/src/manager/Manager.js +++ b/client/src/manager/Manager.js @@ -20,6 +20,7 @@ const Plugins = require('./Plugins'); const EVENTS = require('./../share/Events').EVENTS; const EVENT_AMOUNT = require('./../share/Events').EVENT_AMOUNT; const Console = require('./../share/Console'); +const Helper = require('./../../../common/src/Helper'); const World = require('./../view/World').World; const WEVENTS = require('./../view/World').EVENTS; const Canvas = require('./../view/Canvas'); @@ -36,6 +37,8 @@ class Manager extends Observer { */ constructor(hasView = true) { super(EVENT_AMOUNT); + const width = Config.worldWidth; + const height = Config.worldHeight; /** * {Queue} Queue of organisms in current Manager. Should be used by plugins. * Organisms plugin walk through this queue and run organism's code all the @@ -46,14 +49,16 @@ class Manager extends Observer { * {Object} positions of organisms in a world. Is used to prevent collisions * and track all world objects */ - this.positions = {}; + this.positions = []; + for (let x = 0; x < width; x++) {this.positions[x] = (new Array(height)).fill(0)} /** * {Object} This field is used as a container for public API of the Manager. * It may be used in a user console by the Operator of construct. Plugins * may add their methods to this map also. */ - this.api = {version: () => '0.2.1'}; - hasView && (this.api.visualize = this._visualize.bind(this)); + this.api = {}; + Helper.setApi(this.api, 'version', '0.2.1.1', 'Shows construct version'); + hasView && Helper.setApi(this.api, 'visualize', this._visualize.bind(this), 'Turns on/off visualization in browser for current instance (world). Turning visualization off, increases application speed.'); /** * {Boolean} Means that this manager instance doesn't contain view(canvas). diff --git a/client/src/manager/ManagerSpec.js b/client/src/manager/ManagerSpec.js index acb5714..254c847 100644 --- a/client/src/manager/ManagerSpec.js +++ b/client/src/manager/ManagerSpec.js @@ -92,7 +92,7 @@ describe("client/src/manager/Manager", () => { it("Checking manager creation and it's properties", (done) => { const man = new Manager(false); expect(man.organisms.size).toBe(0); - expect(Object.keys(man.positions).length).toBe(0); + expect(man.positions.length).toBe(Config.worldWidth); expect(man.codeRuns).toBe(0); expect(!!man.api.version).toBe(true); expect(man.api.visualize).toBe(undefined); @@ -554,12 +554,10 @@ describe("client/src/manager/Manager", () => { man1.on(EVENTS.LOOP, () => { if (iterated1 > 0 && iterated2 > 0 && org1 === null) { - log('loop'); expect(man2.organisms.size).toBe(1); org1 = man1.organisms.first.val; org1.vm.code.push(0b00001011000000000000000000000000); // onStepRight() } else if (man2.organisms.size === 2) { - log('destroy'); destroy(); } if (iterated1 > 10000) {throw 'Error sending organism between Servers'} @@ -568,9 +566,7 @@ describe("client/src/manager/Manager", () => { man2.on(EVENTS.LOOP, () => iterated2++); waitEvent(server1, server1.EVENTS.RUN, () => server1.run(), () => { - log('server1 run'); waitEvent(server2, server2.EVENTS.RUN, () => server2.run(), () => { - log('server2 run'); man1.run(() => man2.run()); }); }); diff --git a/client/src/manager/plugins/Config.js b/client/src/manager/plugins/Config.js index de63ebb..9dafbe4 100644 --- a/client/src/manager/plugins/Config.js +++ b/client/src/manager/plugins/Config.js @@ -3,12 +3,13 @@ * * @author flatline */ -const Api = require('./../../share/Config').api; +const Api = require('./../../share/Config').api; +const Helper = require('./../../../../common/src/Helper'); class Config { constructor(manager) { - manager.api.setConfig = Api.set.bind(Api); - manager.api.getConfig = Api.get.bind(Api); + Helper.setApi(manager.api, 'setConfig', Api.set.bind(Api), 'Sets configuration value by name. Namespaces are also supported. Example: \'setConfig(\'organisms.orgMaxOrgs\', 500)\'. Opposite to getConfig()'); + Helper.setApi(manager.api, 'getConfig', Api.get.bind(Api), 'Returns specified config value. First parameter is a namespace (optional) and config name. For example, to get maximum amount of organisms in current instance/world type: man.api.getConfig(\'organisms.orgMaxOrgs\'). Example of organism related configs you may find here (https://github.com/tmptrash/construct/blob/master/client/src/manager/plugins/status/charts/Config.js). Other configuration parameters are located in files with name Config.js'); } } diff --git a/client/src/manager/plugins/Energy.js b/client/src/manager/plugins/Energy.js index 30c7f81..938057a 100644 --- a/client/src/manager/plugins/Energy.js +++ b/client/src/manager/plugins/Energy.js @@ -8,12 +8,9 @@ const Config = require('./../../share/Config').Config; const Organism = require('./../../manager/plugins/organisms/Organism').Organism; const EVENTS = require('./../../share/Events').EVENTS; -const POSID = Helper.posId; - class Energy { constructor(manager) { this._manager = manager; - this._cleverActive = true; this._onIterationCb = this._onIteration.bind(this); Helper.override(manager, 'onIteration', this._onIterationCb); @@ -26,31 +23,51 @@ class Energy { } _onIteration(counter) { - this._cleverActive && this._addEnergyBlock(); - if (Config.worldEnergyCheckPeriod === 0 || counter % Config.worldEnergyCheckPeriod !== 0) {return} - const energy = this._getEnergyPercent(); + let energy = this._getEnergyPercent(); + this._manager.fire(EVENTS.WORLD_ENERGY, energy); - if (energy < Config.worldEnergyMinPercent) {this._manager.fire(EVENTS.WORLD_ENERGY_UP, this._cleverActive = true)} - else if (energy > Config.worldEnergyMaxPercent) {this._manager.fire(EVENTS.WORLD_ENERGY_UP, this._cleverActive = false)} + if (energy > Config.worldEnergyMinPercent) {return} + + const maxEnergy = Config.worldEnergyMaxPercent * Config.worldWidth * Config.worldHeight; + let amount = 0; + let attempts = 0; + while (amount < maxEnergy && attempts < 100) { + const startAmount = amount; + amount = this._addEnergyBlock(amount, maxEnergy); + if (amount === startAmount) { + attempts++; + } else { + attempts = 0; + } + } } - _addEnergyBlock() { - const width = Config.worldWidth; + _addEnergyBlock(amount, maxEnergy) { + const width = Config.worldWidth; const height = Config.worldHeight; - const color = Helper.rand(Organism.getMaxColors()); - let block = Config.worldEnergyBlockSize; - const world = this._manager.world; - let x = Helper.rand(width); - let y = Helper.rand(height); + const color = Organism.getColor(Config.worldEnergyColorIndex); + let block = Config.worldEnergyBlockSize; + const world = this._manager.world; + let x = Helper.rand(width); + let y = Helper.rand(height); for (let i = 0; i < block; i++) { x = x + Helper.rand(3) - 1; y = y + Helper.rand(3) - 1; - if (x < 0 || x >= width || y < 0 || y >= height) {return} - if (world.isFree(x, y)) {world.setDot(x, y, Organism.getColor(color))} + if (x < 0 || x >= width || y < 0 || y >= height) { + return amount + } + if (world.isFree(x, y)) { + world.setDot(x, y, color); + if (++amount > maxEnergy) { + return amount + } + } } + + return amount; } _getEnergyPercent() { @@ -58,15 +75,14 @@ class Energy { const world = this._manager.world; const width = Config.worldWidth; const height = Config.worldHeight; - const poses = this._manager.positions; for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { - if (typeof poses[POSID(x, y)] === 'undefined') {energy += world.getDot(x, y)} + if (world.getDot(x, y) > 0) {++energy} } } - return energy / (width * height * 0xffffffff); + return energy / (width * height); } } diff --git a/client/src/manager/plugins/Stones.js b/client/src/manager/plugins/Stones.js new file mode 100644 index 0000000..344b717 --- /dev/null +++ b/client/src/manager/plugins/Stones.js @@ -0,0 +1,74 @@ +/** + * Manager's plugin, which add stones to the world + * + * @author flatline + */ +const Helper = require('./../../../../common/src/Helper'); +const Config = require('./../../share/Config').Config; +const Organism = require('./../../manager/plugins/organisms/Organism').Organism; +const OBJECT_TYPES = require('./../../view/World').OBJECT_TYPES; + +const STONE_BLOCK_SIZE = 300; +// +// We have to add stone type to global types storage +// +OBJECT_TYPES.TYPE_STONE = -(Object.keys(OBJECT_TYPES).length + 1); + +class Stones { + constructor(manager) { + this._manager = manager; + this._onLoopCb = this._onLoop.bind(this); + + Helper.override(manager, 'onLoop', this._onLoopCb); + } + + destroy() { + Helper.unoverride(this._manager, 'onLoop', this._onLoopCb); + this._manager = null; + this._onLoopCb = null; + } + + _onLoop(counter) { + if (counter > 1 || Config.worldStonesPercent === .0) {return} + + const stones = Config.worldStonesPercent * Config.worldWidth * Config.worldHeight; + let amount = 0; + let attempts = 0; + while (amount < stones && attempts < 100) { + const startAmount = amount; + amount = this._addStoneBlock(amount, stones); + if (startAmount === amount) { + attempts++; + } else { + attempts = 0; + } + } + } + + _addStoneBlock(amount, stones) { + const width = Config.worldWidth; + const height = Config.worldHeight; + const color = Organism.getColor(Config.worldStoneColorIndex); + const man = this._manager; + const world = man.world; + const stone = OBJECT_TYPES.TYPE_STONE; + let x = Helper.rand(width); + let y = Helper.rand(height); + + for (let i = 0; i < STONE_BLOCK_SIZE; i++) { + x = x + Helper.rand(3) - 1; + y = y + Helper.rand(3) - 1; + if (x < 0 || x >= width || y < 0 || y >= height) {return amount} + if (world.isFree(x, y)) { + if (world.setDot(x, y, color)) { + man.positions[x][y] = stone; + if (++amount >= stones) {return amount} + } + } + } + + return amount; + } +} + +module.exports = Stones; \ No newline at end of file diff --git a/client/src/manager/plugins/organisms/Config.js b/client/src/manager/plugins/organisms/Config.js index 4f28b4d..931954f 100644 --- a/client/src/manager/plugins/organisms/Config.js +++ b/client/src/manager/plugins/organisms/Config.js @@ -15,12 +15,12 @@ const Config = { * implemented. See Operators.operators getter for details. Values may be float. */ orgOperatorWeights: [ - .000001, .000001, .00000001, .00000000001, .0000001, // var, const, if, loop, operator, - .0000001, .0002, .0002, .0002, .0002, // lookAt, eatLeft, eatRight, eatUp, eatDown, - .002, .002, .002, .002, // stepLeft, stepRight, stepUp, stepDown, - .000001, .000001, // fromMem, toMem, - .0000001, .0000001, // myX, myY, - .000001, .000001, .000001, .000001 // checkLeft, checkRight, checkUp, checkDown + .0001, .0001, .00001, .00000001, .0001, // var, const, if, loop, operator, + .00001, 2, 2, 2, 2, // lookAt, eatLeft, eatRight, eatUp, eatDown, + 2, 2, 2, 2, // stepLeft, stepRight, stepUp, stepDown, + .001, .001, // fromMem, toMem, + .0001, .0001, // myX, myY, + .001, .001, .001, .001 // checkLeft, checkRight, checkUp, checkDown ], /** * {Array} Probabilities which used, when mutator decides what to do: @@ -47,12 +47,23 @@ const Config = { * {Boolean} If turned on, then organism will be responsible for changing * mutations probabilities. Otherwise these probabilities will be constant */ - orgMutationPerOrg: false, + orgMutationProbsPerOrg: true, /** * {Number} Minimum age for cloning. Before that, cloning is impossible. It should * be less then orgAlivePeriod config */ - orgCloneMinAge: 5000, + orgCloneMinAge: 3000, + /** + * {Number} Minimum energy for cloning + */ + orgCloneMinEnergy: 20000000, + /** + * {Boolean} If true, then random organism will be killed after new one has + * cloned and amount of organisms is greater then orgMaxOrgs config. false + * mean, that new organism will not be cloned, if amount of organisms is >= + * orgMaxOrgs config. + */ + orgKillOnClone: true, /** * {Number} Amount of iterations between tournament. During tournament one * organism (looser) will be killed @@ -61,9 +72,9 @@ const Config = { /** * {Number} Amount of iterations within organism's life loop, after that we * do mutations according to orgRainMutationPercent config. If 0, then - * mutations will be disabled. Should be less then ORGANISM_MAX_MUTATION_PERIOD + * mutations wilsabled. Should be less then ORGANISM_MAX_MUTATION_PERIOD */ - orgRainMutationPeriod: 10000, + orgRainMutationPeriod: 100, /** * {Number} Percent of mutations from code size. 0 is a possible value if * we want to disable mutations. Should be less then 1.0 (1.0 === 100%) @@ -74,22 +85,22 @@ const Config = { * own mutations period and percent. false - mean, that these values will be * constant for all organisms */ - orgRainPerOrg: false, + orgRainPerOrg: true, /** * {Number} Amount of iterations, after which crossover will be applied * to random organisms. May be set to 0 to turn crossover off */ - orgCrossoverPeriod: 1000, + orgCrossoverPeriod: 4000, /** * {Number} Period of iterations for creation of random organisms. Set it to 0 * to turn off this feature */ - orgRandomOrgPeriod: 8000, + orgRandomOrgPeriod: 5000, /** * {Number} Amount of iterations when organism is alive. It will die after * this period. If 0, then will not be used and organism may leave forever */ - orgAlivePeriod: 30000, + orgAlivePeriod: 0, /** * {Number} Size of organism stack (internal memory) in bits. Real amount of * organism's internal memory will be 2^orgMemBits. Example: if orgMemBits=3, @@ -115,17 +126,17 @@ const Config = { * try to clone itself, when entire amount of organisms are equal * this value, the cloning will not happen. */ - orgMaxOrgs: 500, + orgMaxOrgs: 1000, /** * {Number} Amount of organisms we have to create on program start */ - orgStartAmount: 500, + orgStartAmount: 1000, /** * {Number} Amount of energy for first organisms. They are like Adam and * Eve. It means that these empty (without vm) organism were created * by operator and not by evolution. */ - orgStartEnergy: 10000000, + orgStartEnergy: 90000000, /** * {Number} Amount of bits for storing a numeric constant inside byte code */ @@ -141,7 +152,7 @@ const Config = { * organism without interruption by one VM. Set this value to value bigger * then codeMaxSize, then entire code of organism will be run */ - codeYieldPeriod: 4, + codeYieldPeriod: 5, /** * {Number} Amount of bits per one variable. It affects maximum value, * which this variable may contain. This value shouldn't be less then 2. @@ -156,7 +167,7 @@ const Config = { * {Number} Amount of bits, which stores maximum block length. Under block * length we mean maximum amount of lines in one block like if, for,... */ - codeBitsPerBlock: 8, + codeBitsPerBlock: 10, /** * {Number} Amount of iterations between calls to V8 event loop. See * Manager._initLoop(), Manager.run() methods for details. @@ -169,7 +180,7 @@ const Config = { * it's possible for organisms to go outside the limit by inventing new * effective mechanisms of energy obtaining. */ - codeMaxSize: 1000 + codeMaxSize: 300 }; module.exports = Config; \ No newline at end of file diff --git a/client/src/manager/plugins/organisms/Mutator.js b/client/src/manager/plugins/organisms/Mutator.js index b7f7abe..e7152a5 100644 --- a/client/src/manager/plugins/organisms/Mutator.js +++ b/client/src/manager/plugins/organisms/Mutator.js @@ -15,6 +15,11 @@ const Helper = require('./../../../../../common/src/Helper'); const Num = require('./../../../vm/Num'); const ADD_MUTAION_INDEX = 6; +/** + * {Number} Default alive period, which is used if orgAlivePeriod + * config is set to zero + */ +const MAX_ALIVE_PERIOD = 10000; class Mutator { static _onChange(org) { @@ -54,7 +59,7 @@ class Mutator { static _onMutationPeriod(org) { if (!OConfig.orgRainPerOrg) {return} - org.mutationPeriod = Helper.rand(OConfig.orgAlivePeriod); + org.mutationPeriod = OConfig.orgAlivePeriod < 1 ? Helper.rand(MAX_ALIVE_PERIOD) + 1 : Helper.rand(OConfig.orgAlivePeriod - 1) + 1; org.changes++; } @@ -65,7 +70,7 @@ class Mutator { } static _onProbs(org) { - if (!OConfig.orgMutationPerOrg) {return} + if (!OConfig.orgMutationProbsPerOrg) {return} org.mutationProbs[Helper.rand(org.mutationProbs.length)] = Helper.rand(OConfig.ORG_MUTATION_PROBS_MAX_VAL) || 1; org.changes++; } @@ -107,7 +112,7 @@ class Mutator { } _onOrganism(org) { - if (org.iterations % org.mutationPeriod !== 0 || OConfig.orgRainMutationPeriod === 0 || OConfig.orgRainMutationPercent === 0.0 || org.mutationPeriod === 0 || org.energy < 1) {return} + if (org.iterations % org.mutationPeriod !== 0 || org.iterations < 1 || OConfig.orgRainMutationPeriod === 0 || OConfig.orgRainMutationPercent === 0.0 || org.mutationPeriod === 0 || org.energy < 1) {return} this._mutate(org); } diff --git a/client/src/manager/plugins/organisms/Organism.js b/client/src/manager/plugins/organisms/Organism.js index 9c96fdb..c1a7ff2 100644 --- a/client/src/manager/plugins/organisms/Organism.js +++ b/client/src/manager/plugins/organisms/Organism.js @@ -25,7 +25,8 @@ const ORG_EVENTS = { ITERATION }; -const MAX_COLORS = 4000; +const MAX_COLORS = 4000; +const UPDATE_COLOR_PERIOD = 50; class Organism extends Observer { /** @@ -46,8 +47,6 @@ class Organism extends Observer { return r << 16 | g << 8 | b; } - static getMaxColors() {return MAX_COLORS} - /** * Is called before every run. Should return true, if everything * is okay and we don't need to interrupt running. If true, then @@ -81,14 +80,14 @@ class Organism extends Observer { if (parent === null) {this._create()} else {this._clone(parent)} - this._id = id; - this._x = x; - this._y = y; - this._iterations = -1; - this._changes = 0; - this._item = item; - this._maxEnergy = 0; - this._fnId = 0; + this._id = id; + this._x = x; + this._y = y; + this._iterations = -1; + this._changes = 0; + this._item = item; + this._energyChanges = 0; + this._fnId = 0; } get id() {return this._id} @@ -110,10 +109,9 @@ class Organism extends Observer { set y(newY) {this._y = newY} set mutationPeriod(m) {this._mutationPeriod = m} set mutationPercent(p) {this._mutationPercent = p} - set energy(e) {if (this.vm !== null) { this._energy = e; this._updateColor()}} + set energy(e) {if (this.vm !== null) { this._energy = e; ++this._energyChanges % UPDATE_COLOR_PERIOD === 0 && this._updateColor()}} set startEnergy(e) {this._startEnergy = e} set changes(c) {this._changes = c} - set maxEnergy(e) {this._maxEnergy = e} /** * Runs one code iteration (amount of lines set in Config.codeYieldPeriod) and returns @@ -127,8 +125,10 @@ class Organism extends Observer { this._updateEnergy(); if (this._energy > 0) { this._updateClone(); - this._energy > 0 && this.fire(ITERATION, lines, this); - this._energy > 0 && this._updateAge(); + if (this._energy > 0) { + this.fire(ITERATION, lines, this); + this._energy > 0 && this._updateAge(); + } } return true; @@ -189,6 +189,7 @@ class Organism extends Observer { fitness() { // TODO: check these variants //return (OConfig.codeMaxSize + 1 - this.vm.size) * (this._energy - this._startEnergy) * (this._changes || 1); + //return (OConfig.codeMaxSize + 1 - this.vm.size) * (this._energy - this._startEnergy) * (this._changes || 1); //return (OConfig.codeMaxSize + 1 - this.vm.size) * (this._energy - this._startEnergy); //return (OConfig.codeMaxSize + 1 - this.vm.size) * ((this._energy - this._startEnergy) / this._iterations); return this._energy / (this.vm.size || 1); @@ -235,11 +236,11 @@ class Organism extends Observer { } _updateColor() { - this._color = Organism.getColor((this._energy * MAX_COLORS) / this._maxEnergy); + this._color = Organism.getColor(OConfig.orgAlivePeriod === 0 ? MAX_COLORS : this._iterations * (MAX_COLORS / OConfig.orgAlivePeriod)); } _updateClone() { - if (this._iterations > OConfig.orgCloneMinAge && this._energy > 0) {this.fire(CLONE, this)} + if (this._iterations > OConfig.orgCloneMinAge && this._energy > OConfig.orgCloneMinEnergy) {this.fire(CLONE, this)} } /** diff --git a/client/src/manager/plugins/organisms/Organisms.js b/client/src/manager/plugins/organisms/Organisms.js index ccbf40a..28e0631 100644 --- a/client/src/manager/plugins/organisms/Organisms.js +++ b/client/src/manager/plugins/organisms/Organisms.js @@ -16,9 +16,10 @@ const ORG_EVENTS = require('./../../../../src/manager/plugins/organisms/Organi //const Backup = require('./../backup/Backup'); const Mutator = require('./Mutator'); const Num = require('./../../../vm/Num'); - -const RAND_OFFS = 3; -const POSID = Helper.posId; +/** + * {Number} Random range for selection of random organism from a Queue + */ +const RAND_RANGE = 5; // TODO: inherit this class from Configurable class Organisms extends Configurable { @@ -56,7 +57,10 @@ class Organisms extends Configurable { createEmptyOrg(...args) {} constructor(manager) { - super(manager, {Config, cfg: OConfig}, {getAmount: ['_apiGetAmount', 'Shows amount of organisms within current Client(Manager)']}); + super(manager, {Config, cfg: OConfig}, { + getAmount : ['_apiGetAmount', 'Shows amount of organisms within current Client(Manager)'], + getOrganism: ['_apiGetOrganism', 'Returns organism instance by id or int\'s index in a Queue'] + }); this.organisms = manager.organisms; this.randOrgItem = this.organisms.first; this.positions = manager.positions; @@ -89,7 +93,7 @@ class Organisms extends Configurable { } /** - * Is called at the end of run() method + * Is called at the end of run() method. Updates maxEnergy value for population * @param {Organism} org Current organism */ onOrganism(org) { @@ -98,7 +102,6 @@ class Organisms extends Configurable { this._maxEnergy = this._oldMaxEnergy; this._oldMaxEnergy = 0; } - org.maxEnergy = this._maxEnergy; } addOrgHandlers(org) { @@ -123,34 +126,34 @@ class Organisms extends Configurable { if (x1 === x2 && y1 === y2) {return false} world.setDot(x1, y1, 0); - this.positions[POSID(x1, y1)] = undefined; - this.positions[POSID(x2, y2)] = org; + this.positions[x1][y1] = 0; + this.positions[x2][y2] = org; return true; } createOrg(x, y, parent = null) { if (x === -1) {return false} - const orgs = this.organisms; - orgs.add(null); - let org = this.createEmptyOrg(++this._orgId + '', x, y, orgs.last, parent); + this._randOrg(); + const item = this.organisms.addAfter(this.randOrgItem, null); + let org = this.createEmptyOrg(++this._orgId + '', x, y, item, parent); - orgs.last.val = org; + item.val = org; this.addOrgHandlers(org); this.world.setDot(x, y, org.color); - this.positions[org.posId] = org; + this.positions[x][y] = org; this.parent.fire(EVENTS.BORN_ORGANISM, org); //Console.info(org.id, ' born'); - return true; + return item; } /** * Returns random organism of current population * @return {Organism|null} */ - randOrg() { - const offs = Helper.rand(RAND_OFFS) + 1; + _randOrg() { + const offs = Helper.rand(RAND_RANGE) + 1; let item = this.randOrgItem; for (let i = 0; i < offs; i++) { @@ -169,12 +172,12 @@ class Organisms extends Configurable { * @param {Number} stamp Time stamp of current iteration */ _onIteration(counter, stamp) { - let item = this.organisms.first; - let org; + let item = this.organisms.first; + let org; while (org = item && item.val) { org.run(); - org.energy > 0 && this.onOrganism(org); + this.onOrganism(org); item = item.next; } @@ -188,8 +191,8 @@ class Organisms extends Configurable { } _tournament(org1 = null, org2 = null) { - org1 = org1 || this.randOrg(); - org2 = org2 || this.randOrg(); + org1 = org1 || this._randOrg(); + org2 = org2 || this._randOrg(); if (org1.energy < 1 && org2.energy < 1) {return false} if ((org2.energy > 0 && org1.energy < 1) || this.compare(org2, org1)) { @@ -203,25 +206,29 @@ class Organisms extends Configurable { if (this.onBeforeClone(org) === false) {return false} let x; let y; + let item; [x, y] = this.world.getNearFreePos(org.x, org.y); - if (x === -1 || this.createOrg(x, y, org) === false) {return false} - let child = this.organisms.last.val; + if (x === -1 || (item = this.createOrg(x, y, org)) === false) {return false} + let child = item.val; this.onClone(org, child); if (org.energy < 1 || child.energy < 1) {return false} this.parent.fire(EVENTS.CLONE, org, child, isCrossover); - return true; + return item; } _crossover(org1, org2) { - this._clone(org1, true); - const orgs = this.organisms; - let child = orgs.last.val; + const item = this._clone(org1, true); + if (item === false) {return false} + let child = item.val; if (child.energy > 0 && org2.energy > 0) { child.changes += (Math.abs(child.vm.crossover(org2.vm)) * Num.MAX_BITS); + return true; } + + return false; } _createPopulation() { @@ -236,12 +243,35 @@ class Organisms extends Configurable { /** * API method, which will be added to Manager.api interface + * @api * @return {Number} Amount of organisms within current Manager */ _apiGetAmount() { return this.parent.organisms.size; } + /** + * Return organism instance by id or it's index in a Queue + * @param {Number|String} index Index or id + * @return {Organism} Organism instance or null + * @api + */ + _apiGetOrganism(index) { + if (Helper.isNumeric(index)) { + return this.organisms.get(index); + } + + let item = this.organisms.first; + let org; + + while (org = item && item.val) { + if (org.id === index) {return org} + item = item.next; + } + + return null; + } + _onDestroyOrg(org) { if (this.randOrgItem === org.item) { if ((this.randOrgItem = org.item.next) === null) { @@ -250,7 +280,7 @@ class Organisms extends Configurable { } this.organisms.del(org.item); this.world.setDot(org.x, org.y, 0); - this.positions[org.posId] = undefined; + this.positions[org.x][org.y] = 0; this.parent.fire(EVENTS.KILL, org); //Console.info(org.id, ' die'); } @@ -267,12 +297,12 @@ class Organisms extends Configurable { //const orgAmount = this.organisms.size; //if (OConfig.orgKillOnClone && orgAmount >= maxOrgs) {this._killInTour()} - //if (orgAmount >= maxOrgs && (OConfig.orgKillOnClone || Math.random() <= (org.energy / org.vm.size) / this._maxEnergy)) {this.randOrg().destroy()} + //if (orgAmount >= maxOrgs && (OConfig.orgKillOnClone || Math.random() <= (org.energy / org.vm.size) / this._maxEnergy)) {this._randOrg().destroy()} // if (this.organisms.size >= OConfig.orgMaxOrgs && Math.random() <= ((org.energy / 10000000000000) * (org.iterations / OConfig.orgAlivePeriod))) { - // this.randOrg().destroy(); + // this._randOrg().destroy(); // } //if (this.organisms.size < maxOrgs) {this._clone(org)} - //if (this.organisms.size >= maxOrgs && Math.random() <= (org.energy / org.vm.size) / this._maxEnergy) {this.randOrg().destroy()} + //if (this.organisms.size >= maxOrgs && Math.random() <= (org.energy / org.vm.size) / this._maxEnergy) {this._randOrg().destroy()} // // This is very important part of application! Cloning should be available only if // amount of organisms is less then maximum or if current organism has ate other just @@ -281,12 +311,16 @@ class Organisms extends Configurable { // organisms before cloning. They should kill each other to have a possibility // to clone them. // - this.organisms.size < OConfig.orgMaxOrgs && this._clone(org); + if (OConfig.orgKillOnClone && this.organisms.size >= OConfig.orgMaxOrgs) { + const randOrg = this._randOrg(); + if (randOrg !== org && Math.random() <= org.iterations / OConfig.orgAlivePeriod) {randOrg.destroy()} + } + if (this.organisms.size < OConfig.orgMaxOrgs) {this._clone(org)} } _killInTour() { - let org1 = this.randOrg(); - let org2 = this.randOrg(); + let org1 = this._randOrg(); + let org2 = this._randOrg(); if (org1.energy < 1 || org2.energy < 1 || org1 === org2 || this.organisms.size < 1) {return false} const winner = this._tournament(org1, org2); if (winner === false) {return false} @@ -311,7 +345,7 @@ class Organisms extends Configurable { _updateRandomOrgs(counter) { if (counter % OConfig.orgRandomOrgPeriod !== 0 || OConfig.orgRandomOrgPeriod === 0 || this.organisms.size < 1) {return false} - const vm = this.randOrg().vm; + const vm = this._randOrg().vm; const size = Helper.rand(vm.size) + 1; const pos = Helper.rand(vm.size - size); @@ -327,8 +361,8 @@ class Organisms extends Configurable { // We have to have a possibility to crossover not only with best // organisms, but with low fit also // - let org1 = Helper.rand(2) === 0 ? this._tournament() : this.randOrg(); - let org2 = Helper.rand(2) === 0 ? this._tournament() : this.randOrg(); + let org1 = Helper.rand(2) === 0 ? this._tournament() : this._randOrg(); + let org2 = Helper.rand(2) === 0 ? this._tournament() : this._randOrg(); if (org1 === false || org2 === false || org1.energy < 1 || org2.energy < 1) {return false} this._crossover(org1, org2); diff --git a/client/src/manager/plugins/organisms/dos/Code2String.js b/client/src/manager/plugins/organisms/dos/Code2String.js index 013efb2..139b0f4 100644 --- a/client/src/manager/plugins/organisms/dos/Code2String.js +++ b/client/src/manager/plugins/organisms/dos/Code2String.js @@ -7,6 +7,7 @@ */ const Num = require('./../../../../vm/Num'); const OConfig = require('./../Config'); +const Helper = require('./../../../../../../common/src/Helper'); /** * {Function} Just a shortcuts */ @@ -59,7 +60,7 @@ class Code2String { /** * {Array} Contains closing bracket offset for "if", "loop",... operators */ - this._offsets = []; + this._offsets = [0]; Num.init(this._OPERATORS_CB_LEN); @@ -69,7 +70,7 @@ class Code2String { // // API of the Manager for accessing outside. (e.g. from Console) // - manager.api.formatCode = (code) => this.format(code); + Helper.setApi(manager.api, 'formatCode', (code) => this.format(code), 'Converts byte code array into human readable JavaScript based code. This function is low level. For using it you have to get organism\'s virtual machine reference and then use it\'s code property. For example: man.api.formatCode(man.api.organisms.getOrganism(\'128\').vm.code). This example will find organism with id \'128\' and shows his byte code.'); } destroy() { @@ -86,35 +87,28 @@ class Code2String { const operators = this._OPERATORS_CB; const offs = this._offsets; let lines = new Array(len); - let needClose = 0; - + // + // First number always amount of code lines + // + offs.splice(0, offs.length, len); for (let line = 0; line < len; line++) { + lines[line] = operators[Num.getOperator(code[line])](code[line], line); // // We found closing bracket '}' of some loop and have to add // it to output code array // - if (line === offs[offs.length - 1]) { - while (offs.length > 0 && offs[offs.length - 1] === line) { - offs.pop(); - needClose++; - } - } - lines[line] = operators[Num.getOperator(code[line])](code[line], line, len); - if (needClose > 0) { - for (let i = 0; i < needClose; i++) { - lines[line] = '}' + lines[line]; - } - needClose = 0; + while (offs.length > 1 && line === offs[offs.length - 1]) { + line = offs.pop(); + lines[line] += '}'; } } // // All closing brackets st the end of JS script // const length = lines.length - 1; - for (let i = 0; i < offs.length; i++) { + for (let i = 1; i < offs.length; i++) { lines[length] += '}'; } - offs.length = 0; return js_beautify(lines.join(separator), {indent_size: 4}); } @@ -134,22 +128,22 @@ class Code2String { } _onConst(num) { - return `v${Num.getVar0(num)}=${Num.getBits(num, this._BITS_AFTER_THREE_VARS, OConfig.codeConstBits)}`; + return `v${Num.getVar0(num)}=${Num.getBits(num, this._BITS_AFTER_ONE_VAR, OConfig.codeConstBits)}`; } - _onCondition(num, line, lines) { + _onCondition(num, line) { const cond = Num.getBits(num, this._BITS_AFTER_TWO_VARS, CONDITION_BITS); const blockOffs = Num.getBits(num, this._BITS_AFTER_TWO_VARS + CONDITION_BITS, OConfig.codeBitsPerBlock); - this._offsets.push(this._getOffs(line, lines, blockOffs)); + this._offsets.push(this._getOffs(line, blockOffs)); return `if(v${Num.getVar0(num)}${this._CONDITIONS[cond]}v${Num.getVar1(num)}){`; } - _onLoop(num, line, lines) { + _onLoop(num, line) { const cond = Num.getBits(num, this._BITS_AFTER_TWO_VARS, CONDITION_BITS); const blockOffs = Num.getBits(num, this._BITS_AFTER_TWO_VARS + CONDITION_BITS, OConfig.codeBitsPerBlock); - this._offsets.push(this._getOffs(line, lines, blockOffs)); + this._offsets.push(this._getOffs(line, blockOffs)); return `while(v${Num.getVar0(num)}${this._CONDITIONS[cond]}v${Num.getVar1(num)}){`; } @@ -248,20 +242,12 @@ class Code2String { * So it's possible to set it to one of 1...3. So we change it in * real time to fix the overlap problem. * @param {Number} line Current line index - * @param {Number} lines Amount of lines * @param {Number} offs Local offset of closing bracket we want to set * @returns {Number} */ - _getOffs(line, lines, offs) { - let offset = line + offs < lines ? line + offs + 1 : lines; - const offsets = this._offsets; - const length = offsets.length; - - if (length > 0 && offset >= offsets[length - 1]) { - return offsets[length - 1]; - } - - return offset; + _getOffs(line, offs) { + const offsets = this._offsets || [0]; + return line + offs > offsets[offsets.length - 1] ? offsets[offsets.length - 1] : line + offs; } } diff --git a/client/src/manager/plugins/organisms/dos/Operators.js b/client/src/manager/plugins/organisms/dos/Operators.js index f7f36b6..e99548c 100644 --- a/client/src/manager/plugins/organisms/dos/Operators.js +++ b/client/src/manager/plugins/organisms/dos/Operators.js @@ -56,8 +56,8 @@ class OperatorsDos extends Operators { constructor(offs, vars, obs) { super(offs, vars, obs); /** - * {Object} These operator handlers should return string, which - * will be added to the final string script for evaluation. + * {Object} These operator handlers should return next script line + * number VM should step to */ this._OPERATORS_CB = [ this.onVar.bind(this), @@ -217,10 +217,54 @@ class OperatorsDos extends Operators { return ++line; } - onEatLeft(num, line, org) {this.vars[Num.getVar0(num)] = this._eat(org, num, org.x - 1, org.y); return ++line} - onEatRight(num, line, org) {this.vars[Num.getVar0(num)] = this._eat(org, num, org.x + 1, org.y); return ++line} - onEatUp(num, line, org) {this.vars[Num.getVar0(num)] = this._eat(org, num, org.x, org.y - 1); return ++line} - onEatDown(num, line, org) {this.vars[Num.getVar0(num)] = this._eat(org, num, org.x, org.y + 1); return ++line} + onEatLeft(num, line, org) { + const amount = this.vars[Num.getVar1(num)]; + if (amount === 0) {this.vars[Num.getVar0(num)] = 0; return ++line} + const ret = this._ret; + + ret.ret = amount; + this.obs.fire(EVENTS.EAT, org, org.x - 1, org.y, ret); + org.energy += ret.ret; + this.vars[Num.getVar0(num)] = ret.ret; + + return ++line; + } + onEatRight(num, line, org) { + const amount = this.vars[Num.getVar1(num)]; + if (amount === 0) {this.vars[Num.getVar0(num)] = 0; return ++line} + const ret = this._ret; + + ret.ret = amount; + this.obs.fire(EVENTS.EAT, org, org.x + 1, org.y, ret); + org.energy += ret.ret; + this.vars[Num.getVar0(num)] = ret.ret; + + return ++line; + } + onEatUp(num, line, org) { + const amount = this.vars[Num.getVar1(num)]; + if (amount === 0) {this.vars[Num.getVar0(num)] = 0; return ++line} + const ret = this._ret; + + ret.ret = amount; + this.obs.fire(EVENTS.EAT, org, org.x, org.y - 1, ret); + org.energy += ret.ret; + this.vars[Num.getVar0(num)] = ret.ret; + + return ++line; + } + onEatDown(num, line, org) { + const amount = this.vars[Num.getVar1(num)]; + if (amount === 0) {this.vars[Num.getVar0(num)] = 0; return ++line} + const ret = this._ret; + + ret.ret = amount; + this.obs.fire(EVENTS.EAT, org, org.x, org.y + 1, ret); + org.energy += ret.ret; + this.vars[Num.getVar0(num)] = ret.ret; + + return ++line; + } onStepLeft(num, line, org) {this.vars[Num.getVar0(num)] = this._step(org, org.x, org.y, org.x - 1, org.y, org.x - 1); return ++line} onStepRight(num, line, org) {this.vars[Num.getVar0(num)] = this._step(org, org.x, org.y, org.x + 1, org.y, org.x + 1); return ++line} @@ -251,27 +295,25 @@ class OperatorsDos extends Operators { onMyX(num, line, org) {this.vars[Num.getVar0(num)] = org.x; return ++line} onMyY(num, line, org) {this.vars[Num.getVar0(num)] = org.y; return ++line} - onCheckLeft(num, line, org) {return this._checkAt(num, line, org.x - 1, org.y)} - onCheckRight(num, line, org) {return this._checkAt(num, line, org.x + 1, org.y)} - onCheckUp(num, line, org) {return this._checkAt(num, line, org.x, org.y - 1)} - onCheckDown(num, line, org) {return this._checkAt(num, line, org.x, org.y + 1)} - - _checkAt(num, line, x, y) { - this.obs.fire(EVENTS.CHECK_AT, x, y, this._ret); + onCheckLeft(num, line, org) { + this.obs.fire(EVENTS.CHECK_AT, org.x - 1, org.y, this._ret); this.vars[Num.getVar0(num)] = this._ret.ret; return ++line; } - - _eat(org, num, x, y) { - const amount = this.vars[Num.getVar1(num)]; - if (amount === 0) {return 0} - const ret = this._ret; - - ret.ret = amount; - this.obs.fire(EVENTS.EAT, org, x, y, ret); - org.energy += ret.ret; - - return ret.ret; + onCheckRight(num, line, org) { + this.obs.fire(EVENTS.CHECK_AT, org.x + 1, org.y, this._ret); + this.vars[Num.getVar0(num)] = this._ret.ret; + return ++line; + } + onCheckUp(num, line, org) { + this.obs.fire(EVENTS.CHECK_AT, org.x, org.y - 1, this._ret); + this.vars[Num.getVar0(num)] = this._ret.ret; + return ++line; + } + onCheckDown(num, line, org) { + this.obs.fire(EVENTS.CHECK_AT, org.x, org.y + 1, this._ret); + this.vars[Num.getVar0(num)] = this._ret.ret; + return ++line; } _step(org, x1, y1, x2, y2, step) { diff --git a/client/src/manager/plugins/organisms/dos/OrganismSpec.js b/client/src/manager/plugins/organisms/dos/OrganismSpec.js index 27281c9..460d1b0 100644 --- a/client/src/manager/plugins/organisms/dos/OrganismSpec.js +++ b/client/src/manager/plugins/organisms/dos/OrganismSpec.js @@ -82,7 +82,6 @@ describe("client/src/organism/OrganismDos", () => { OConfig.orgStartEnergy = energy; }); - it("Organism should not be dead after loosing some energy", () => { const period = OConfig.orgAlivePeriod; const energy = OConfig.orgStartEnergy; @@ -99,9 +98,9 @@ describe("client/src/organism/OrganismDos", () => { expect(org1.energy).toBe(100); org1.run(); - expect(org1.energy).toBe(89); // 100 - (100 * .1 + 1) = 89 + expect(org1.energy).toBe(99.9); // 100 - .1 = 99.9 org1.run(); - expect(org1.energy).toBe(79.1); // 90 - (90 * .1 + 1) = 79.1 + expect(org1.energy).toBe(99.80000000000001); // 99.9 - .1 = 99.8 org1.destroy(); expect(org1.energy < 1).toBe(true); @@ -161,7 +160,7 @@ describe("client/src/organism/OrganismDos", () => { const color = org.color; expect(org.color).toBe(color); org.energy++; - expect(org.color).not.toBe(color); + expect(org.color).toBe(color); }); }); @@ -227,10 +226,12 @@ describe("client/src/organism/OrganismDos", () => { it('Organism should fire CLONE event if enough age', () => { let flag = false; const minAge = OConfig.orgCloneMinAge; + const minEnergy = OConfig.orgCloneMinEnergy; const yieldPeriod = OConfig.codeYieldPeriod; const weights = OConfig.orgOperatorWeights.slice(); const newWeights = [.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1,.1]; OConfig.orgCloneMinAge = 1; + OConfig.orgCloneMinEnergy = 1; OConfig.codeYieldPeriod = 1; OConfig.orgOperatorWeights.splice(0, OConfig.orgOperatorWeights.length, ...newWeights); const org1 = new OrganismDos('0', 1, 2, null); @@ -247,7 +248,8 @@ describe("client/src/organism/OrganismDos", () => { expect(flag).toBe(true); org1.destroy(); - OConfig.orgCloneMinAge = minAge; + OConfig.orgCloneMinAge = minAge; + OConfig.orgCloneMinEnergy = minEnergy; OConfig.codeYieldPeriod = yieldPeriod; OConfig.orgOperatorWeights.splice(0, OConfig.orgOperatorWeights.length, ...weights); }) diff --git a/client/src/manager/plugins/organisms/dos/Organisms.js b/client/src/manager/plugins/organisms/dos/Organisms.js index 26c0df1..659788d 100644 --- a/client/src/manager/plugins/organisms/dos/Organisms.js +++ b/client/src/manager/plugins/organisms/dos/Organisms.js @@ -23,17 +23,13 @@ const DIR = require('./../../../../../../common/src/Directions').DI const EMPTY = 0; const ENERGY = 1; const ORGANISM = 2; +const OBJECT = 3; /** * {Function} Is created to speed up this function call. constants are run * much faster, then Helper.normalize() */ const NORMALIZE = Helper.normalize; const NORMALIZE_NO_DIR = Helper.normalizeNoDir; -/** - * {Function} Is created to speed up this function call. constants are run - * much faster, then Helper.posId() - */ -const POSID = Helper.posId; class Organisms extends BaseOrganisms { constructor(manager) { @@ -117,28 +113,25 @@ class Organisms extends BaseOrganisms { } _onGetEnergy(x, y, ret) { - const posId = POSID(x, y); - - if (typeof(this.positions[posId]) === 'undefined') { + if (this.positions[x][y] <= 0) { ret.ret = this.world.getDot(x, y) } else { - ret.ret = this.positions[posId].energy; + ret.ret = this.positions[x][y].energy; } } _onEat(org, x, y, ret) { - const positions = this.positions; - // - // Amount of eat energy depends on organism size. Small organisms - // eat less, big - more - // const eat = ret.ret; [x, y] = NORMALIZE_NO_DIR(x, y); - const posId = POSID(x, y); + const victimOrg = this.positions[x][y]; + // + // World object found. We can't eat objects + // + if (victimOrg < 0) {ret.ret = 0; return} // // Energy found // - if (typeof(positions[posId]) === 'undefined') { + if (victimOrg === 0) { if (eat >= 0) { ret.ret = this.world.grabDot(x, y, eat); this.parent.fire(EVENTS.EAT_ENERGY, ret.ret); @@ -147,24 +140,24 @@ class Organisms extends BaseOrganisms { this.world.setDot(x, y, (((-eat + .5) << 0) >>> 0) + this.world.getDot(x, y)); this.parent.fire(EVENTS.PUT_ENERGY, -eat); } + return; + } // // Organism found // + ret.ret = eat < 0 ? 0 : (eat > victimOrg.energy ? victimOrg.energy : eat); + if (victimOrg.energy <= ret.ret) { + this.parent.fire(EVENTS.KILL_EAT, victimOrg); + // + // IMPORTANT: + // We have to do destroy here, to have a possibility for current + // (winner) organism to clone himself after eating other organism. + // This is how organisms compete for an ability to clone + // + victimOrg.destroy(); } else { - const victimOrg = positions[posId]; - ret.ret = eat < 0 ? 0 : (eat > victimOrg.energy ? victimOrg.energy : eat); - if (victimOrg.energy <= ret.ret) { - this.parent.fire(EVENTS.KILL_EAT, victimOrg); - // - // IMPORTANT: - // We have to do destroy here, to have a possibility for current - // (winner) organism to clone himself after eating other organism. - // This is how organisms compete for an ability to clone - // - victimOrg.destroy(); - } else { - victimOrg.energy -= ret.ret; - } + this.parent.fire(EVENTS.EAT_ORG, victimOrg, ret.ret); + victimOrg.energy -= ret.ret; } } @@ -213,9 +206,11 @@ class Organisms extends BaseOrganisms { _onCheckAt(x, y, ret) { [x, y] = NORMALIZE_NO_DIR(x, y); - if (typeof(this.parent.positions[POSID(x, y)]) === 'undefined') { - ret.ret = this.parent.world.getDot(x, y) > 0 ? ENERGY : EMPTY; - } else { + if (this.positions[x][y] < 0) { // world object + ret.ret = OBJECT + -this.positions[x][y]; + } else if (this.positions[x][y] === 0) { // energy + ret.ret = this.world.getDot(x, y) > 0 ? ENERGY : EMPTY; + } else { // organism ret.ret = ORGANISM; } } @@ -231,8 +226,10 @@ class Organisms extends BaseOrganisms { * @param {Object} ret Return object */ _onStepIn(x, y, orgJson, ret) { - if (ret.ret = this.world.isFree(x, y) && this.organisms.size < (OConfig.orgMaxOrgs + OConfig.orgMaxOrgs * OConfig.orgStepOverflowPercent) && this.createOrg(x, y)) { - const org = this.organisms.last.val; + if (ret.ret = this.world.isFree(x, y) && this.organisms.size < (OConfig.orgMaxOrgs + OConfig.orgMaxOrgs * OConfig.orgStepOverflowPercent)) { + const item = this.createOrg(x, y); + if (item === false) {return} + const org = item.val; org.unserialize(orgJson); const energy = (org.energy * OConfig.orgStepEnergySpendPercent + .5) << 0; // diff --git a/client/src/manager/plugins/status/Status.js b/client/src/manager/plugins/status/Status.js index fae69d8..0ed60b6 100644 --- a/client/src/manager/plugins/status/Status.js +++ b/client/src/manager/plugins/status/Status.js @@ -19,7 +19,6 @@ const Helper = require('./../../../../../common/src/Helper'); const EVENTS = require('./../../../share/Events').EVENTS; const Configurable = require('./../../../../../common/src/Configurable'); const Config = require('./../../../share/Config').Config; -const OConfig = require('./../../../manager/plugins/organisms/Config'); class Status extends Configurable { static _toFixed(val, fixed) { @@ -38,7 +37,7 @@ class Status extends Configurable { super(manager, {Config, cfg: statCfg}, apiCfg); this._status = { - lps :0, ips :0, orgs :0, energy :0, penergy :0, eenergy:0, wenergy:0, wenergyup:true, changes:0, fit:0, age:0, code:0, + lps :0, ips :0, orgs :0, energy :0, oenergy :0, eenergy:0, wenergy:0, changes :0, fit:0, age:0, code:0, kill:0, killenergy:0, killage:0, killeat:0, killover:0, killout:0, killin :0, killclone:0 }; this._stamp = 0; @@ -57,13 +56,13 @@ class Status extends Configurable { this._times = 0; this._kill = new Array(9); this._worldEnergy = 0.0; - this._worldEnergyUp = true; this._statusCfg = statCfg; this._firstCall = true; this._onLoopCb = this._onLoop.bind(this); this._onIpsCb = this._onIps.bind(this); this._onEatEnergyCb = this._onEatEnergy.bind(this); + this._onEatOrgCb = this._onEatOrg.bind(this); this._onPutEnergyCb = this._onPutEnergy.bind(this); this._onKillOrgCb = this._onKillOrg.bind(this); this._onKillEnergyCb = this._onKillHandlerOrg.bind(this, 1); @@ -75,11 +74,11 @@ class Status extends Configurable { this._onKillInCb = this._onKillHandlerOrg.bind(this, 7); this._onKillCloneCb = this._onKillHandlerOrg.bind(this, 8); this._onWorldEnergyCb = this._onWorldEnergy.bind(this); - this._onWorldEnergyUpCb = this._onWorldEnergyUp.bind(this); Helper.override(manager, 'onLoop', this._onLoopCb); manager.on(EVENTS.IPS, this._onIpsCb); manager.on(EVENTS.EAT_ENERGY, this._onEatEnergyCb); + manager.on(EVENTS.EAT_ORG, this._onEatOrgCb); manager.on(EVENTS.PUT_ENERGY, this._onPutEnergyCb); manager.on(EVENTS.KILL, this._onKillOrgCb); manager.on(EVENTS.KILL_TOUR, this._onKillTourCb); @@ -91,7 +90,6 @@ class Status extends Configurable { manager.on(EVENTS.KILL_STEP_IN, this._onKillInCb); manager.on(EVENTS.KILL_CLONE, this._onKillCloneCb); manager.on(EVENTS.WORLD_ENERGY, this._onWorldEnergyCb); - manager.on(EVENTS.WORLD_ENERGY_UP, this._onWorldEnergyUpCb); _fill(this._kill, 0); } @@ -99,7 +97,6 @@ class Status extends Configurable { destroy() { const man = this.parent; - man.off(EVENTS.WORLD_ENERGY_UP, this._onWorldEnergyUpCb); man.off(EVENTS.WORLD_ENERGY, this._onWorldEnergyCb); man.off(EVENTS.KILL_CLONE, this._onKillCloneCb); man.off(EVENTS.KILL_STEP_IN, this._onKillInCb); @@ -111,15 +108,16 @@ class Status extends Configurable { man.off(EVENTS.KILL_TOUR, this._onKillTourCb); man.off(EVENTS.KILL, this._onKillOrgCb); man.off(EVENTS.PUT_ENERGY, this._onPutEnergyCb); + man.off(EVENTS.EAT_ORG, this._onEatOrgCb); man.off(EVENTS.EAT_ENERGY, this._onEatEnergyCb); man.off(EVENTS.IPS, this._onIpsCb); Helper.unoverride(man, 'onLoop', this._onLoopCb); - this._onWorldEnergyUpCb = null; this._onWorldEnergyCb = null; this._onKillOrgCb = null; this._onKillTourCb = null; this._onPutEnergyCb = null; + this._onEatOrgCb = null; this._onEatEnergyCb = null; this._onKillCloneCb = null; this._onKillInCb = null; @@ -155,7 +153,6 @@ class Status extends Configurable { item = item.next; } - this._pickEnergy = ((energy - startEnergy) / iterations) / size; this._energy = energy / size; this._changes = changes / size; this._fitness = fitness / size; @@ -174,11 +171,11 @@ class Status extends Configurable { this._onBeforeLoop(orgs); - status.ips = fix(this._ips / this._ipsTimes * OConfig.codeYieldPeriod, 2); + status.ips = fix(this._ips / this._ipsTimes, 2); status.lps = fix((this.parent.codeRuns - this._codeRuns) / ((stamp - this._stamp) / 1000), 0); status.orgs = orgAmount; status.energy = fix(this._energy, 2); - status.penergy = fix(this._pickEnergy * 1000, 2); + status.oenergy = fix(this._pickEnergy / orgAmount, 2); status.eenergy = fix(this._eatEnergy / orgAmount, 2); status.puenergy = fix(this._putEnergy / orgAmount, 2); status.changes = +(this._changes).toFixed(1); @@ -197,7 +194,6 @@ class Status extends Configurable { status.killclone = fix(this._kill[8], 2); status.wenergy = fix(this._worldEnergy, 5); - status.wenergyup = this._worldEnergyUp; !this._firstCall && this.onStatus(status, orgs.size); this._onAfterLoop(stamp); @@ -230,6 +226,10 @@ class Status extends Configurable { this._eatEnergy += eat; } + _onEatOrg(org, eat) { + this._pickEnergy += eat; + } + /** * Calculates putting of energy by organisms to the world * @param {Number} put Amount of put energy @@ -245,17 +245,14 @@ class Status extends Configurable { this._kill[0] ++; } - _onKillHandlerOrg(index) { + _onKillHandlerOrg(index, org) { this._kill[index]++; + index === 4 && (this._pickEnergy += org.energy); } _onWorldEnergy(percent) { this._worldEnergy = percent; } - - _onWorldEnergyUp(up) { - this._worldEnergyUp = up; - } } module.exports = Status; \ No newline at end of file diff --git a/client/src/manager/plugins/status/charts/Charts.js b/client/src/manager/plugins/status/charts/Charts.js index b02b51d..3066047 100644 --- a/client/src/manager/plugins/status/charts/Charts.js +++ b/client/src/manager/plugins/status/charts/Charts.js @@ -18,7 +18,7 @@ const API = { pos9 : ['_pos9', 'Sets chart position in 3x3 grid' ], pos16 : ['_pos16', 'Sets chart position in 4x4 grid' ], active : ['_active', 'Activates/Deactivates chart' ], - on : ['_on', 'Activates/Deactivates chart' ], + on : ['_on', 'Activates/Deactivates chart by name' ], off : ['_off', 'Deactivates chart' ], reset : ['_reset', 'Resets chart data' ], preset : ['_preset', 'Positioning charts according to preset'] @@ -47,7 +47,7 @@ const PRESETS = { orgs : '0-1|16', energy : '0-2|16', eenergy : '0-3|16', - penergy : '1-0|16', + oenergy : '1-0|16', changes : '1-1|16', age : '1-2|16', code : '1-3|16', @@ -71,7 +71,7 @@ const PRESETS = { lps : 'topleft', energy : 'downleft', eenergy : 'topright', - penergy : 'downright' + oenergy : 'downright' } }; @@ -108,7 +108,7 @@ class Charts extends Status { ips : new Chart('IPS - Iterations Per Second', Config.charts.ips), orgs : new Chart('Amount of organisms', Config.charts.orgs), energy : new Chart('Average organism energy', Config.charts.energy), - penergy : new Chart('Average organism\'s picked energy (all)', Config.charts.penergy), + oenergy : new Chart('Average organism\'s eat energy from other organisms', Config.charts.oenergy), eenergy : new Chart('Average organism\'s picked energy (energy only)', Config.charts.eenergy), puenergy : new Chart('Average organism\'s put energy to the world', Config.charts.puenergy), changes : new Chart('Average organism\'s changes (Mutations)', Config.charts.changes), @@ -165,15 +165,15 @@ class Charts extends Status { const conns = `${active[0] ? '^' : ''}${active[1] ? '>' : ''}${active[2] ? 'v' : ''}${active[3] ? '<' : ''}`; const ips = `ips:${status.ips}`; const enrg = `enrg:${status.eenergy}`; + const onrg = `onrg:${status.oenergy}`; const wnrg = `wnrg:${status.wenergy}`; - const wnrgup = status.wenergyup ? '\u2191' : '\u2193'; const code = `cod:${status.code}`; const age = `age:${status.age}`; const kill = `kil:${status.kill}`; const kilo = `kilo:${status.killeat}`; const orgs = `org:${status.orgs}`; - this._headerEl.textContent = `${man.clientId ? 'id:' + man.clientId : ''} ${conns === '' ? '' : 'con:' + conns} ${ips} ${wnrg} ${wnrgup} ${enrg} ${code} ${age} ${kill} ${kilo} ${orgs}`; + this._headerEl.textContent = `${man.clientId ? 'id:' + man.clientId : ''} ${conns === '' ? '' : 'con:' + conns} ${ips} ${wnrg} ${enrg} ${onrg} ${code} ${age} ${kill} ${kilo} ${orgs}`; } /** diff --git a/client/src/manager/plugins/status/charts/Config.js b/client/src/manager/plugins/status/charts/Config.js index 3facf59..00871a7 100644 --- a/client/src/manager/plugins/status/charts/Config.js +++ b/client/src/manager/plugins/status/charts/Config.js @@ -25,7 +25,7 @@ const Config = { killout : {pos: 'topright', active: false, transparent: 0.8}, energy : {pos: 'downright', active: false, transparent: 0.8}, orgs : {pos: '0-0|16', active: false, transparent: 0.8}, - penergy : {pos: '0-1|16', active: false, transparent: 0.8}, + oenergy : {pos: '0-1|16', active: false, transparent: 0.8}, eenergy : {pos: '0-2|16', active: false, transparent: 0.8}, puenergy : {pos: '0-3|16', active: false, transparent: 0.8}, fit : {pos: '1-0|16', active: false, transparent: 0.8}, diff --git a/client/src/manager/plugins/status/console/Console.js b/client/src/manager/plugins/status/console/Console.js index 37aef97..07ea2f4 100644 --- a/client/src/manager/plugins/status/console/Console.js +++ b/client/src/manager/plugins/status/console/Console.js @@ -48,17 +48,15 @@ class Console extends Status { const slps = format(status.lps, 'lps', 14); const sorgs = format(orgs, 'org', 10); const senergy = format(status.energy, 'nrg', 19); - const spenergy = format(status.penergy, 'pnrg', 15); + const spenergy = format(status.oenergy, 'onrg', 15); const seenergy = format(status.eenergy, 'enrg', 16); - const wenergy = format(status.wenergy + (status.wenergyup ? '\u2191' : '\u2193'), 'wnrg', 14); const skill = format(status.kill, 'kil', 12); const schanges = format(status.changes, 'che', 12); - const sfit = format(status.fit, 'fit', 13); const sage = format(status.age, 'age', 11); const scode = format(status.code, 'cod', 12); // TODO: under Node.js should use Server/Console.xxx() - console.log(`%c${conns}${sips}${slps}${sorgs}%c${senergy}${spenergy}${seenergy}${wenergy}${skill}${schanges}${sfit}${sage}${scode}`, GREEN, RED); + console.log(`%c${conns}${sips}${slps}${sorgs}%c${senergy}${spenergy}${seenergy}${skill}${schanges}${sage}${scode}`, GREEN, RED); } } diff --git a/client/src/share/Config.js b/client/src/share/Config.js index 63f07c2..d70a4bb 100644 --- a/client/src/share/Config.js +++ b/client/src/share/Config.js @@ -39,6 +39,7 @@ ClientConfig.init({ 'Config', 'client/Client', 'Energy', + 'Stones', 'status/console/Console', IS_NODE_JS ? '' : 'status/charts/Charts', 'ips/Ips', @@ -52,11 +53,11 @@ ClientConfig.init({ /** * {Number} World width */ - worldWidth: 1920 / 2, + worldWidth: 1920, /** * {Number} World height */ - worldHeight: 1080 / 2, + worldHeight: 1080, /** * {Number} Turns on cyclic world mode. It means that organisms may go outside * it's border, but still be inside. For example, if the world has 10x10 @@ -65,29 +66,44 @@ ClientConfig.init({ * coordinate (height). It actual only for one instance mode (no distributed * calculations). */ - worldCyclical: false, + worldCyclical: true, /** * {Number} An amount of iteration, after which we have to check world energy * percent. May be 0 if you want to disable energy generation */ worldEnergyCheckPeriod: 5000, /** - * {Number} size of one clever energy block in dots. + * {Number} size of one clever energy block in dots */ - worldEnergyBlockSize: 200, + worldEnergyBlockSize: 10, + /** + * {Number} Index of energy color. Starts from 0. Ends with 4000. See Organism.MAX_COLORS + * constant for details + */ + worldEnergyColorIndex: 0, /** * {Number} Percent from all energy in a world until clever energy will be added. * After this value clever energy will be stopped to add until it's amount will * be less then worldEnergyMinPercent. These two configs create cyclical * energy adding to the world. */ - worldEnergyMaxPercent: .0009, + worldEnergyMaxPercent: .3, /** * {Number} Opposite to worldEnergyMaxPercent. Sets minimum percent from * all energy in a world after which clever energy will turn on (be added to the * world again). */ - worldEnergyMinPercent: .0001, + worldEnergyMinPercent: .28, + /** + * {Number} Percent of stones in a world. Percent from world size: + * stoneAmount = worldStonesPercent * worldWidth * worldHeight + */ + worldStonesPercent: .25, + /** + * {Number} Color index for stones in a world. See Organism.MAX_COLORS + * constant for details + */ + worldStoneColorIndex: 1800, /** * {Number} Zoom speed 0..1 */ diff --git a/client/src/view/World.js b/client/src/view/World.js index 0c7e880..6b8d43a 100644 --- a/client/src/view/World.js +++ b/client/src/view/World.js @@ -30,6 +30,7 @@ const WEVENTS = { * The same like this.getDot(x, y) === 0 */ const FREE_DOT_ATTEMPTS = 100; +const OBJECT_TYPES = {}; class World extends Observer { constructor (width, height) { @@ -118,4 +119,4 @@ class World extends Observer { } } -module.exports = {World, EVENTS: WEVENTS}; \ No newline at end of file +module.exports = {World, EVENTS: WEVENTS, OBJECT_TYPES}; \ No newline at end of file diff --git a/client/src/vm/VM.js b/client/src/vm/VM.js index 22138f7..df075bd 100644 --- a/client/src/vm/VM.js +++ b/client/src/vm/VM.js @@ -107,7 +107,7 @@ class VM extends Observer { // This is very important peace of logic. As big the organism is // as more energy he spends // - org.energy -= (WEIGHTS[operator] * org.energy + (org.vm ? org.vm.size : 0)); + org.energy -= WEIGHTS[operator]; // // We found closing bracket '}' of some loop and have to return // to the beginning of operator (e.g.: for) diff --git a/client/src/vm/VMSpec.js b/client/src/vm/VMSpec.js index ba7e2b1..08fe69d 100644 --- a/client/src/vm/VMSpec.js +++ b/client/src/vm/VMSpec.js @@ -238,7 +238,7 @@ describe("client/src/organism/VM", () => { vm.updateLine(0, 0x00000000); expect(org.energy).toEqual(100); vm.run(org); - expect(org.energy).toEqual(90); // 100 - (100 * .1 + 0) = 90 + expect(org.energy).toEqual(99.9); // 100 - .1 = 99.9 org.destroy(); vm.destroy(); diff --git a/common/src/Configurable.js b/common/src/Configurable.js index dd7665c..dd516c9 100644 --- a/common/src/Configurable.js +++ b/common/src/Configurable.js @@ -97,8 +97,7 @@ class Configurable { const isStr = typeof key === 'string'; let desc = isStr && 'No description' || key[1]; - cfg[c] = (isStr && this[key] || this[key[0]]).bind(this); - cfg[c].desc = desc; + Helper.setApi(cfg, c, (isStr && this[key] || this[key[0]]).bind(this), desc); } } diff --git a/common/src/FastArray.js b/common/src/FastArray.js new file mode 100644 index 0000000..24b5122 --- /dev/null +++ b/common/src/FastArray.js @@ -0,0 +1,114 @@ +/** + * Implementation of fast array. First assumption of this class is in fixed array + * size. Second that get() method will be called must more times, then set() or + * del() or resize(). Resize is possible, but should be rare to keep it fast. Is + * used for storing organisms population. This class doesn't check size overflow + * due performance issue. Removing element means setting 0 to specified index. + * This class should not be used for storing numbers! + * + * @author flatline + */ +class FastArray { + constructor(size) { + /** + * {Array} Source container for custom objects + */ + this._arr = new Array(size); + /** + * {Array} Array of free indexes in _arr. Every time + * user calls del() method _arr obtains hole in it. + * Index of this hole wil be stored in this array + */ + this._freeIndexes = new Array(size); + /** + * {Number} Index of last free index in _freeIndexes array + */ + this._index = size - 1; + /** + * {Number} Allocated size of array. This is maximum amount + * of elements, which may be stored in FastArray + */ + this._size = size; + + for (let i = 0; i < size; i++) { + this._freeIndexes[i] = i; + this._arr[i] = 0; + } + } + + destroy() { + this._arr = null; + this._freeIndexes = null; + this._size = 0; + } + + /** + * Analog of Array.length + * @returns {Number} Amount of not empty elements in FastArray. + * Not all cells in an array may be filled by values. + */ + get length() {return this._size - this._index - 1} + + /** + * Returns allocated size + * @returns {Number} + */ + get size() {return this._size} + + /** + * Returns next free index in FastArray + * @returns {Number} + */ + get freeIndex() { + return this._freeIndexes[this._index]; + } + + /** + * Sets value to FastArray. You can't set value index due to + * optimization reason. Only a value + * @param {*} v Any value except number + */ + set(v) {this._arr[this._freeIndexes[this._index--]] = v} + + /** + * Returns a value by index + * @param {Number} i Value index + * @returns {*} + */ + get(i) {return this._arr[i]} + + /** + * Removes a value by index + * @param {Number} i Value index + */ + del(i) { + if (this._arr !== 0) + this._arr[i] = 0; + this._freeIndexes[++this._index] = i; + } + + /** + * Returns last added value by set() method + * @returns {*} Value + */ + lastAdded() { + return this._arr[this._freeIndexes[this._index + 1]]; + } + + /** + * Resizes an array. Values will not be removed during resize. + * This method is very slow and should be called not often. + * @param {Number} size New array size + */ + resize(size) { + const indexes = this._freeIndexes; + const arr = this._arr; + this._index = -1; + arr.length = indexes.length = (this._size = size); + for (let i = 0; i < size; i++) { + (arr[i] === 0) && (indexes[++this._index] = i); + } + } +} + +module.exports = FastArray; \ No newline at end of file diff --git a/common/src/Helper.js b/common/src/Helper.js index 71eeef8..21e8d2d 100644 --- a/common/src/Helper.js +++ b/common/src/Helper.js @@ -63,6 +63,18 @@ class Helper { return el; } + /** + * Sets API function, which may be used by users in Chrome console (in DevTools) + * @param {Object} obj Destination object + * @param {String} name Name of function/property + * @param {Function|*} fn Function or value (if property) + * @param {String} desc Description + */ + static setApi(obj, name, fn, desc = '') { + obj[name] = fn; + Helper.isFunc(fn) && (obj[name].desc = desc); + } + /** * Sets first letter to lower case * @param {String} s diff --git a/common/src/Observer.js b/common/src/Observer.js index 97e879c..6e525a0 100644 --- a/common/src/Observer.js +++ b/common/src/Observer.js @@ -53,9 +53,8 @@ class Observer { /** * This method is a most frequently called one. So we have to * optimize it as much as possible - * @param {Number} event Event number + * @param {Number} event Event number. Not string * @param {*} args List of arguments - * @param args */ fire(event, ...args) { const handlers = this._handlers[event] || {}; diff --git a/common/src/Queue.js b/common/src/Queue.js index 9c22dec..58caf91 100644 --- a/common/src/Queue.js +++ b/common/src/Queue.js @@ -50,6 +50,29 @@ class Queue { this._first.val = val; } + /** + * The same like add(), but inserts after specified item in a queue + * @param {Object} item Item, after which val will be inserted + * @param {*} val Value to insert + * @return {Object} Inserted item + */ + addAfter(item, val) { + if (item === this._last) {this.add(val); return this._last} + if (this._size++ > 0) { + const newItem = { + prev: item, + next: item.next, + val + }; + item.next.prev = newItem; + item.next = newItem; + return newItem; + } + this._first.val = val; + + return this._first; + } + /** * Removes specified item from the queue. 'item' parameter is not * the same as value inside this item. Remember remove position may diff --git a/server/src/server/ServerSpec.js b/server/src/server/ServerSpec.js index 161f0c3..d4f7b1f 100644 --- a/server/src/server/ServerSpec.js +++ b/server/src/server/ServerSpec.js @@ -209,13 +209,17 @@ describe("server/src/server/Server", () => { const ws1 = new WebSocket(CLIENT_URL); const ws2 = new WebSocket(CLIENT_URL); Helper.wait(waitObj, () => { - server.on(SEVENTS.STOP, () => waitObj.done = true); - server.stop(); + cons = 0; + server.on(SEVENTS.CLOSE, () => ++cons === 2 && (waitObj.done = true)); ws1.close(); ws2.close(); Helper.wait(waitObj, () => { - server.destroy(); - done(); + server.on(SEVENTS.STOP, () => waitObj.done = true); + server.stop(); + Helper.wait(waitObj, () => { + server.destroy(); + done(); + }); }); }); }); @@ -232,12 +236,12 @@ describe("server/src/server/Server", () => { server.on(SEVENTS.CLOSE, () => waitObj.done = true); ws2.close(); Helper.wait(waitObj, () => { - server.on(SEVENTS.STOP, () => waitObj.done = true); - server.stop(); + server.on(SEVENTS.CLOSE, () => waitObj.done = true); ws1.close(); Helper.wait(waitObj, () => { + server.on(SEVENTS.DESTROY, () => waitObj.done = true); server.destroy(); - done(); + Helper.wait(waitObj, done); }); }) }); @@ -265,12 +269,15 @@ describe("server/src/server/Server", () => { const ws = new WebSocket(CLIENT_URL); Helper.wait(waitObj, () => { expect(server.active).toEqual(true); - server.on(SEVENTS.STOP, () => waitObj.done = true); - server.destroy(); + server.on(SEVENTS.CLOSE, () => waitObj.done = true); ws.close(); Helper.wait(waitObj, () => { - expect(server.active).toEqual(false); - done(); + server.on(SEVENTS.DESTROY, () => waitObj.done = true); + server.destroy(); + Helper.wait(waitObj, () => { + expect(server.active).toEqual(false); + done(); + }); }); }); });