var MAKE_STORE_FUNCTION_NAME = MAKE_STORE_FUNCTION_NAME || 'makeNeuronStore' var DBG = DBG || false; var DBG1 = true; // DEFAULT_CONFIG.config_strategy_overcharge: "LEAVE_ALMOST_MAX", // "REMOVE_CHARGE" | "LEAVE_HALF_CHARGE" | "LEAVE_ALMOST_MAX" var DEFAULT_NEURON = { value: '', charge: 0, maxCharge: 1.5, // DEFAULT_CONFIG.config_max_neuron_charge, uiShape: "circle", ui: { cx: 0, cy: 0 }, source: [], next: [], } function makeDefaultNeuronsStoreState() { return { receptor: [], mapReceptorChar: {}, neuron: [], connection: [], doubleConn: [], // connections between two nodes charge: [], // TODO: list of [ { charge: number, nodeType: (receptor|neuron), nodeIdx: int }, ... ] groupd by node (type/idx) inputReadPos: 0, animPos: 0, doAnim: true, uiOutputHeight: 300, } } var p5Utils__ArrayDistinctFilter = function (value, idx, self) { return (idx === self.indexOf(value)); }; var p5Utils__ArrayDiff = function (a, b) { // a - b @return array with items from `a` that are not in `b` return a.filter(function (x) { return (-1 === b.indexOf(x)); }) }; var p5Utils__inputToFlatString = function (input) { return input.reduce(function (ret, line) { return ret.concat(line).concat(['\n']) }, []).join('') }; function makeNeuronStore() { DBG && console.log("DBG:NeuronStore:makeNeuronStore"); var _state = makeDefaultNeuronsStoreState() var _config = {} var _input = [] var _animIntervalID = null var _callback = null function stateReset(input, config) { _input = input _config = config _state = makeDefaultNeuronsStoreState() var foundLetters = _input.reduce(function (ret, line) { return ret.concat(line) }, []).filter(p5Utils__ArrayDistinctFilter); _state.receptor = foundLetters.sort().map(makeReceptor); _state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; }); if (_animIntervalID) clearInterval(_animIntervalID) _notifySubscribers(); } function inputUpdated(newInput) { var oldInput = _input DBG && console.log("DBG:NeuronStore:inputUpdated", { newInput, oldInput }) if (!newInput || !newInput.length) { // Missing input return; } if (newInput.length < oldInput.length) { stateReset(newInput, _config) startAnimation() return; } if (p5Utils__inputToFlatString(newInput.slice(0, oldInput.length)) !== p5Utils__inputToFlatString(oldInput)) { stateReset(newInput, _config) startAnimation() return; } DBG && console.log("DBG:NeuronStore:inputUpdated", { msg: "TODO: add input => add Receptors" }) _input = newInput { // add new receptors if needed var addInput = newInput.slice(oldInput.length) var newLetters = addInput.reduce(function (ret, line) { return ret.concat(line) }, []).filter(p5Utils__ArrayDistinctFilter) var oldLetters = oldInput.reduce(function (ret, line) { return ret.concat(line) }, []).filter(p5Utils__ArrayDistinctFilter) var addLetters = p5Utils__ArrayDiff(newLetters.sort(), oldLetters) DBG && console.log("DBG:NeuronStore:inputUpdated", { msg: "TODO: add input => add Receptors", addLetters }) if (!addLetters.length) return; var totalReceptors = oldLetters.length + addLetters.length var oldFirstCx = Math.ceil(_config.ui_output_width / (oldLetters.length + 1)); var newFirstCx = Math.ceil(_config.ui_output_width / (totalReceptors + 1)); var moveXFactor = Number((newFirstCx / oldFirstCx).toFixed(2)) DBG && console.log("DBG:NeuronStore:inputUpdated", { msg: "TODO: add input => add Receptors", moveXFactor, oldFirstCx, newFirstCx }) var allLetters = [].concat(oldLetters, addLetters) _state.receptor = _state.receptor.map(function (receptor) { receptor.ui.cx = receptor.ui.cx * moveXFactor return receptor; }) _state.neuron = _state.neuron.map(function (neuron) { neuron.ui.cx = neuron.ui.cx * moveXFactor return neuron; }) addLetters.forEach(function (letter, idx) { _state.receptor.push(makeReceptor(letter, idx + oldLetters.length, allLetters)) }) _state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; }); } } function inputAddLine(inputLine) { // inputLine: [ [ "A", "B", ... ], ... ] DBG && console.log("DBG:NeuronStore:inputAddLine", { msg: "TODO: add input => add Receptors", keys: inputLine, _input: [].concat(_input) }) var oldInput = [].concat(_input) var newLetters = inputLine.reduce(function (ret, line) { return ret.concat(line) }, []).filter(p5Utils__ArrayDistinctFilter) _input.push(newLetters) { // add new receptors if needed var oldLetters = oldInput.reduce(function (ret, line) { return ret.concat(line) }, []).filter(p5Utils__ArrayDistinctFilter) var addLetters = p5Utils__ArrayDiff(newLetters.sort(), oldLetters) DBG && console.log("DBG:NeuronStore:inputAddLine", { msg: "TODO: add input => add Receptors.2", newLetters, oldLetters, addLetters }) if (!addLetters.length) return; var totalReceptors = oldLetters.length + addLetters.length var oldFirstCx = Math.ceil(_config.ui_output_width / (oldLetters.length + 1)); var newFirstCx = Math.ceil(_config.ui_output_width / (totalReceptors + 1)); var moveXFactor = Number((newFirstCx / oldFirstCx).toFixed(2)) DBG && console.log("DBG:NeuronStore:inputAddLine", { msg: "TODO: add input => add Receptors.3", moveXFactor, oldFirstCx, newFirstCx }) var allLetters = [].concat(oldLetters, addLetters) _state.receptor = _state.receptor.map(function (receptor) { receptor.ui.cx = receptor.ui.cx * moveXFactor return receptor; }) _state.neuron = _state.neuron.map(function (neuron) { neuron.ui.cx = neuron.ui.cx * moveXFactor return neuron; }) addLetters.forEach(function (letter, idx) { _state.receptor.push(makeReceptor(letter, idx + oldLetters.length, allLetters)) }) _state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; }); } } function readLineFromInput() { if (_state.inputReadPos >= _input.length) return null; var line = _input[_state.inputReadPos] _state.inputReadPos += 1 return line; } function forwardAnim() { DBG && console.log("DBG:NeuronStore:forwardAnim:doAnim = '" + (_state.doAnim ? 1 : 0) + "'"); if (!_state.doAnim) return; // 1. read from input if its time // 2. check overcharged nodes // 2.1 check overcharged Receptor // 2.2 check overcharged Neuron // 3. move all charges (Receptor -> Connection, Connection -> Neuron, Neuron -> Connection) // 4. discharge all // ad.2. TODO: what to do with Charge when create new Neuron? // - idea 1: use Charge to create new Neuron: rm Charge, add Neuron, add Connection // 1. read from input if its time if (0 === _state.animPos % _config.config_read_speed_multiplier) { // read from input = add charge to receptor with given letter var line = readLineFromInput() if (null === line) { DBG && console.log("DBG:NeuronStore:forwardAnim:1(readInput): STOP - end of input / discharge all", { inputLength: _input.length, animPos: _state.animPos }) } else { line.forEach(function (label) { var foundReceptorIdx = getReceptorIdx(label) DBG && console.log("DBG:NeuronStore:forwardAnim:1(readInput): label('" + label + "', [" + label.charCodeAt(0) + "])", { animPos: _state.animPos, label, len: _input.length, doAnim: _state.doAnim, foundReceptorIdx }) if (-1 === foundReceptorIdx) { DBG && console.warn("BUG: receptor '" + label + "' not found (NeuronStore:forwardAnim:readInput)") _state.doAnim = false _notifySubscribers() return; } this_charge_add(_config.config_charge_receptor_at_input, { type: 'receptor', idx: foundReceptorIdx }) }) _state.animPos += 1 _notifySubscribers() return; } } // 2. check overcharged nodes // 2.1 check overcharged Neuron // 2.1.1 is overcharged Neuron // 2.1.1.1 is another Neuron with Charge // 2.1.1.2 ! another Neuron with Charge // 2.1.2 ! overcharged Neuron // 2.2 check overcharged Receptor { // check overcharged nodes var overchargedNeuronIdx = _state.neuron.reduce(function (ret, neuron, idx) { var charge = getNeuronCharge(idx) if (charge < neuron.maxCharge) return ret; ret.push({ idx: idx, charge: charge }) return ret; }, []) var overchargedReceptorIdx = _state.receptor.reduce(function (ret, receptor, idx) { var charge = getReceptorCharge(idx) if (charge < _config.config_max_receptor_charge) return ret; ret.push({ idx: idx, charge: charge }) return ret; }, []) if (overchargedNeuronIdx.length > 0) { var overcharged = overchargedNeuronIdx[0] var firstNeuron = _state.neuron[overcharged.idx] var secondNeuronWithCharge = _state.neuron.reduce(function (ret, neuron, idx) { // find neuron with max charge if (idx === overcharged.idx) return ret; // skip firstNeuron var charge = getNeuronCharge(idx) if (!charge) return ret; // skip Neuron without charge if (null === ret) return { idx: idx, charge: charge } return (charge > ret.charge) ? { idx: idx, charge: charge } : ret; }, null) DBG && console.log("DBG:NeuronStore:forwardAnim:2.1(check overcharged)/neuron.1", { overcharged, firstNeuron, secondNeuronWithCharge }) if (null !== secondNeuronWithCharge) { // is already exists - check doubleConn var existingConn = _state.doubleConn.reduce(function (ret, dblConn) { if (ret) return ret; if (dblConn.from[0] === overcharged.idx && dblConn.from[1] === secondNeuronWithCharge.idx) return dblConn; // if (dblConn.from[1] === overcharged.idx && dblConn.from[0] === secondNeuronWithCharge.idx) return dblConn; return ret; }, null) var toNode = _state.neuron[secondNeuronWithCharge.idx] DBG && console.log("DBG:NeuronStore:forwardAnim:2.1(check overcharged)/neuron.2(secondNeuronWithCharge)", { from: firstNeuron.value, to: toNode.value, existingConn }) if (existingConn) { // charge Neuron existingConn.to var idxNewNeuron = existingConn.to this_charge_remove({ type: 'neuron', idx: overcharged.idx }) this_charge_remove({ type: 'neuron', idx: secondNeuronWithCharge.idx }) switch (_config.config_strategy_overcharge) { case "REMOVE_CHARGE": break; case "LEAVE_HALF_CHARGE": { this_charge_add(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx }) this_charge_add(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx }) } break; case "LEAVE_ALMOST_MAX": { this_charge_add(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx }) this_charge_add(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx }) } break; } this_charge_add(overcharged.charge + secondNeuronWithCharge.charge, { type: 'neuron', idx: existingConn.to }) // _state.animPos += 1 // dischargeAll() _notifySubscribers() return; } else { // create new var newNeuron = makeNeuronFromTwoNeurons(overcharged.idx, overcharged.charge, secondNeuronWithCharge.idx, secondNeuronWithCharge.charge) if (!newNeuron) { DBG && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)/neuron.2.1(!newNeuron)", { overcharged }) // _state.animPos += 1 dischargeAll() _notifySubscribers() return; } else { var idxNewNeuron = this_neuron_add(newNeuron) updateOuputHeight(newNeuron) var conn1Idx = this_connection_add({ fromType: 'neuron', from: overcharged.idx, to: idxNewNeuron, timesCreated: 1 }) var conn2Idx = this_connection_add({ fromType: 'neuron', from: secondNeuronWithCharge.idx, to: idxNewNeuron, timesCreated: 1 }) var dblConnIdx = this_doubleConn_add({ from: [overcharged.idx, secondNeuronWithCharge.idx], to: idxNewNeuron, fromConn: [conn1Idx, conn2Idx] }) this_charge_remove({ type: 'neuron', idx: overcharged.idx }) this_charge_remove({ type: 'neuron', idx: secondNeuronWithCharge.idx }) var firstCharge = getNeuronAlmostMaxCharge(overcharged.idx) var secondCharge = getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx) this_charge_add((firstCharge + secondCharge) / 2, { type: 'doubleConn', idx: dblConnIdx, connCharge: [firstCharge, secondCharge] }) switch (_config.config_strategy_overcharge) { case "REMOVE_CHARGE": break; case "LEAVE_HALF_CHARGE": { this_charge_add(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx }) this_charge_add(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx }) } break; case "LEAVE_ALMOST_MAX": { // this_charge_add(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx }) // this_charge_add(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx }) } break; } // _state.animPos += 1 // dischargeAll() _notifySubscribers() return; } } // // _state.animPos += 1 // // dischargeAll() // _notifySubscribers() // return; } else { // !secondNeuronWithCharge // TODO: check if already exists! if yes then charge else create new var idxFoundConnection = _state.connection.reduce(function (ret, conn, idx) { if (ret > -1) return ret; if (conn.fromType === 'neuron' && conn.from === overcharged.idx) return idx; return ret; }, -1) DBG && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)/neuron.2(!secondNeuronWithCharge)", { idxFoundConnection }) if (-1 === idxFoundConnection) { var newNeuron = makeNeuronFromOneNeuron(overcharged.idx, overcharged.charge) if (!newNeuron) { DBG && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)/neuron.2.1(!newNeuron)", { overcharged }) // _state.animPos += 1 dischargeAll() _notifySubscribers() return; } else { _state.neuron.push(newNeuron) updateOuputHeight(newNeuron) var idxNewNeuron = _state.neuron.length - 1 this_connection_add({ fromType: 'neuron', from: overcharged.idx, to: idxNewNeuron, timesCreated: 1 }) this_charge_remove({ type: 'neuron', idx: overcharged.idx }) switch (_config.config_strategy_overcharge) { case "REMOVE_CHARGE": break; case "LEAVE_HALF_CHARGE": { this_charge_add(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx }) } break; case "LEAVE_ALMOST_MAX": { this_charge_add(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx }) } break; } _notifySubscribers() return; } } else { _state.connection[idxFoundConnection].timesCreated += 1 // chargeMove(overcharged.charge, { type: 'receptor', idx: overcharged.idx }, { type: 'connection', idx: idxFoundConnection }) this_charge_remove({ type: 'neuron', idx: overcharged.idx }) switch (_config.config_strategy_overcharge) { case "REMOVE_CHARGE": break; case "LEAVE_HALF_CHARGE": { this_charge_add(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx }) } break; case "LEAVE_ALMOST_MAX": { this_charge_add(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx }) } break; } this_charge_add(overcharged.charge, { type: 'connection', idx: idxFoundConnection }) } _notifySubscribers() return; } // _notifySubscribers() // return; } if (overchargedReceptorIdx.length > 0) { while (overchargedReceptorIdx.length) { var overcharged = overchargedReceptorIdx.shift() // Restriction: only one connection between Receptor and Neuron { var idxFoundConnection = _state.connection.reduce(function (ret, conn, idx) { if (ret > -1) return ret; if (conn.fromType === 'receptor' && conn.from === overcharged.idx) return idx; return ret; }, -1) DBG && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)/receptor", { idxFoundConnection, from: { type: 'receptor', idx: overcharged.idx }, conn: [].concat(_state.connection) }) if (-1 === idxFoundConnection) { var newNeuron = makeNeuronFromOneReceptor(overcharged.idx, overcharged.charge) _state.neuron.push(newNeuron) var idxNewNeuron = _state.neuron.length - 1 idxFoundConnection = this_connection_add({ fromType: 'receptor', from: overcharged.idx, to: idxNewNeuron, timesCreated: 1 }) this_charge_remove({ type: 'receptor', idx: overcharged.idx }) // switch (_config.config_strategy_overcharge) { // case "REMOVE_CHARGE": break; // case "LEAVE_HALF_CHARGE": { // this_charge_add(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx }) // } break; // case "LEAVE_ALMOST_MAX": { // this_charge_add(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx }) // } break; // } this_charge_add(overcharged.charge, { type: 'connection', idx: idxFoundConnection }) } else { _state.connection[idxFoundConnection].timesCreated += 1 // chargeMove(overcharged.charge, { type: 'receptor', idx: overcharged.idx }, { type: 'connection', idx: idxFoundConnection }) this_charge_remove({ type: 'receptor', idx: overcharged.idx }) // switch (_config.config_strategy_overcharge) { // case "REMOVE_CHARGE": break; // case "LEAVE_HALF_CHARGE": { // this_charge_add(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx }) // } break; // case "LEAVE_ALMOST_MAX": { // this_charge_add(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx }) // } break; // } this_charge_add(overcharged.charge, { type: 'connection', idx: idxFoundConnection }) } } } _notifySubscribers() return; } // DBG && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)", { overchargedNeuronIdx, overchargedReceptorIdx }) } { // 3. move all charges (Receptor -> Connection, Connection -> Neuron, Neuron -> Connection) DBG && console.log("TODO:NeuronStore:forwardAnim:3(move all charges)", { charge: [].concat(_state.charge) }) var isChargeTypeConnection = function (charge) { if ("connection" === charge.nodeType) return true; if ("doubleConn" === charge.nodeType) return true; return false; } var toAddChargeNeuronIdxList = _state.charge.filter(isChargeTypeConnection).map(function (charge) { DBG && console.log("TODO:NeuronStore:forwardAnim:3(move all charges).LOOP", { charge }) switch (charge.nodeType) { case "connection": return { charge: charge.value, to: _state.connection[charge.node].to }; case "doubleConn": return { charge: charge.value, to: _state.doubleConn[charge.node].to }; } return null; }) _state.charge = _state.charge.filter(function (charge) { return !isChargeTypeConnection(charge); }) toAddChargeNeuronIdxList.forEach(function (toAddCharge) { this_charge_add(toAddCharge.charge, { type: 'neuron', idx: toAddCharge.to }) }) } { // 4. discharge all dischargeAll() } _state.animPos += 1 _notifySubscribers(); } function dischargeAll() { _state.charge = _state.charge.map(function (charge) { return Object.assign({}, charge, { value: Math.max(0, charge.value - _config.config_discharge_per_tick), }) }).filter(function (charge) { return charge.value > 0; }) } function makeNeuronFromTwoNeurons(fromNeuronIdx, fromNeuronCharge, toNeuronIdx, toNeuronCharge) { var fromNode = _state.neuron[fromNeuronIdx] var toNode = _state.neuron[toNeuronIdx] var maxCharge = getNewNeuronMaxCharge(fromNode) DBG && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { fromNode, toNode, maxCharge }) if (maxCharge < _config.config_discharge_per_tick) return null; var uiNewNodePos = { cx: 0, cy: 0 } { // closer to node with more charge (first) var xDiff = Math.abs(fromNode.ui.cx - toNode.ui.cx) var isFirstOnRight = (fromNode.ui.cx > toNode.ui.cx) if (0 === xDiff) { uiNewNodePos.cx = fromNode.ui.cx + (isFirstOnRight ? -1 : 1) * 20 } else { // fromNeuronCharge + toNeuronCharge -- xDiff // toNeuronCharge -- x ==> x = toNeuronCharge * xDiff / (fromNeuronCharge + toNeuronCharge) var xToFirst = (toNeuronCharge * xDiff) / (fromNeuronCharge + toNeuronCharge) xToFirst = Math.max(40, xToFirst) uiNewNodePos.cx = fromNode.ui.cx + (isFirstOnRight ? -1 : 1) * xToFirst } } uiNewNodePos.cy = (fromNode.ui.cy != toNode.ui.cy) ? Math.max(fromNode.ui.cy, toNode.ui.cy) + 10 + _config.ui_space_y / 2 : fromNode.ui.cy + 10 + _config.ui_space_y ; uiNewNodePos.cy = _state.neuron.reduce(function (ret, neuron) { if (uiNewNodePos.cx < neuron.ui.cx - 10) return ret; if (uiNewNodePos.cx > neuron.ui.cx + 10) return ret; if (ret < neuron.ui.cy - 10) return ret; if (ret > neuron.ui.cy + 10) return ret; return ret + 10 + _config.ui_space_y / 2; }, uiNewNodePos.cy) DBG && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { uiNewNodePos, fromNode, toNode, newNodeMaxCharge: getNewNeuronMaxCharge(fromNode) }) // var value = '' + (_state.neuron.length + 1); var value = [toNode.value, fromNode.value].join('') return Object.assign({}, DEFAULT_NEURON, { value: value, maxCharge: getNewNeuronMaxCharge(fromNode), uiShape: "ellipse", ui: Object.assign({}, uiNewNodePos, { rx: 10, ry: 10, }), }); } function makeNeuronFromOneReceptor(fromReceptorIdx, charge) { var sourceNode = _state.receptor[fromReceptorIdx] // var value = '' + (_state.neuron.length + 1); var value = [sourceNode.value, ""].join('') // TODO: same name what Receptor DBG && console.log("DBG:NeuronStore:makeNeuronFromOneReceptor", { sourceNode, newNodeMaxCharge: getNewNeuronMaxCharge(sourceNode) }) return Object.assign({}, DEFAULT_NEURON, { value: value, maxCharge: getNewNeuronMaxCharge(sourceNode), uiShape: "ellipse", ui: { cx: sourceNode.ui.cx, cy: sourceNode.ui.cy + 10 + _config.ui_space_y, rx: 10, ry: 10, }, }); } function makeNeuronFromOneNeuron(fromNeuronIdx, charge) { var sourceNode = _state.neuron[fromNeuronIdx] // var value = '' + (_state.neuron.length + 1); var value = [sourceNode.value, "^"].join('') var maxCharge = getNewNeuronMaxCharge(sourceNode) if (maxCharge < _config.config_discharge_per_tick) return null; DBG && console.log("DBG:NeuronStore:makeNeuronFromOneNeuron", { sourceNode, newNodeMaxCharge: maxCharge }) return Object.assign({}, DEFAULT_NEURON, { value: value, maxCharge: maxCharge, uiShape: "ellipse", ui: { cx: sourceNode.ui.cx, cy: sourceNode.ui.cy + 10 + _config.ui_space_y, rx: 10, ry: 10, }, }); } function getNewNeuronMaxCharge(sourceNode) { // return sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one; return sourceNode.maxCharge - _config.config_discharge_per_tick; } function this_neuron_add(neuron) { _state.neuron.push(neuron) return _state.neuron.length - 1; } function this_doubleConn_add(doubleConn) { _state.doubleConn.push(doubleConn) return _state.doubleConn.length - 1; } function this_connection_add(conn) { _state.connection.push(conn) return _state.connection.length - 1; } function this_charge_add(charge, to) { var foundChargeIdx = _state.charge.reduce(function (ret, charge, idx) { if (charge.nodeType === to.type && charge.node === to.idx) return idx; return ret; }, -1) DBG && console.log("DBG:NeuronStore:this_charge_add.1", { charge, to, foundChargeIdx }) var chargeIdx = foundChargeIdx if (-1 === foundChargeIdx) { _state.charge.push({ value: charge, nodeType: to.type, node: to.idx }) chargeIdx = _state.charge.length - 1 } else { _state.charge[chargeIdx].value += charge } if ('doubleConn' === to.type) { // connCharge: [ firstCharge, secondCharge ] if (-1 === foundChargeIdx) { _state.charge[chargeIdx].connCharge = to.connCharge } else { _state.charge[chargeIdx].connCharge[0] += to.connCharge[0] _state.charge[chargeIdx].connCharge[1] += to.connCharge[1] } } DBG && console.log("DBG:NeuronStore:this_charge_add.2", { state_charge: [].concat(_state.charge) }) return chargeIdx; } function this_charge_remove(from) { _state.charge = _state.charge.filter(function (charge) { return !(from.type === charge.nodeType && from.idx === charge.node); }) } function updateOuputHeight(newNeuron) { if (newNeuron.ui.cy + 30 > _state.uiOutputHeight) { _state.uiOutputHeight = newNeuron.ui.cy + 30 } } function dispatch(payload) { DBG && console.log("DBG:NeuronStore:dispatch('" + payload.type + "')", payload); switch (payload.type) { case 'INIT': stateReset(payload.input, payload.config); startAnimation(); break; case 'INPUT_UPDATED': inputUpdated(payload.input); break; case 'INPUT_ADD_LINE': inputAddLine(payload.input); break; case 'PAUSE': _state.doAnim = false; _notifySubscribers(); break; case 'PLAY': _state.doAnim = true; break; default: { DBG && console.warn("Not implemented dispatch action type '" + payload.type + "'") } } } function subscribe(callback) { _callback = callback; // TODO: array return unsubscribe; } function unsubscribe() { _callback = null } function _notifySubscribers() { if (_callback) _callback() } function makeReceptor(value, idx, arr) { // usage: [].map(makeReceptor) var totalReceptors = arr.length; var cx = Math.ceil(_config.ui_output_width / (totalReceptors + 1)); var r = Math.min(Math.ceil(_config.ui_output_width / (totalReceptors + 10) / 2), _config.ui_max_receptor_r); var xCenter = cx + cx * idx - r; return Object.assign({}, DEFAULT_NEURON, { value: value, maxCharge: _config.config_max_receptor_charge, uiShape: "circle", ui: { cx: xCenter, cy: 40, r: r, }, next: [], }) } function getReceptorIdx(char) { return _state.mapReceptorChar.indexOf(char) } function startAnimation() { _state.doAnim = true if (_animIntervalID) clearInterval(_animIntervalID) _animIntervalID = setInterval(forwardAnim, _config.config_anim_speed) } function getReceptorCharge(idx) { return _state.charge.filter(function (charge) { return ('receptor' === charge.nodeType && idx === charge.node); }).reduce(function (ret, charge) { return ret + charge.value; }, 0) } function getNeuronCharge(idx) { return _state.charge.filter(function (charge) { return ('neuron' === charge.nodeType && idx === charge.node); }).reduce(function (ret, charge) { return ret + charge.value; }, 0) } function getReceptorAlmostMaxCharge(idx) { return _state.receptor[idx].maxCharge - _config.config_discharge_per_tick; } function getNeuronAlmostMaxCharge(idx) { DBG && console.log("DBG:getNeuronAlmostMaxCharge(" + idx + ")", { charge: _state.neuron[idx].maxCharge - _config.config_discharge_per_tick }) return _state.neuron[idx].maxCharge - _config.config_discharge_per_tick; } function getNodesChargeState() { var receptor = [].concat(_state.receptor); var neuron = [].concat(_state.neuron); _state.charge.forEach(function (charge) { // _state.charge: [ { value: number, nodeType: (receptor|neuron), node: int }, ... ] groupd by node (type/idx) switch (charge.nodeType) { case 'receptor': receptor[charge.node].charge = charge.value; break; case 'neuron': neuron[charge.node].charge = charge.value; break; } }) function getNodeInfoFunction(type) { return function _getNodeInfo(node) { return { type: type, value: node.value, charge: node.charge, } } } return receptor.map(getNodeInfoFunction('receptor')) .concat( neuron.map(getNodeInfoFunction('neuron')) ) ; } return { subscribe: subscribe, dispatch: dispatch, getState: function () { return _state; }, getNodesChargeState: getNodesChargeState, getListReceptor: function () { return _state.receptor; }, getListNeuron: function () { return _state.neuron; }, getReceptor: function (idx) { return _state.receptor[idx]; }, getNeuron: function (idx) { return _state.neuron[idx]; }, getConnection: function (idx) { return _state.connection[idx]; }, getDoubleConnection: function (idx) { return _state.doubleConn[idx]; }, getCharge: function (idx) { return _state.charge[idx]; }, getListConnection: function () { return _state.connection; }, getListCharge: function () { return _state.charge; }, getReceptorCharge: getReceptorCharge, getNeuronCharge: getNeuronCharge, getInput: function () { return [].concat(_input); }, } } // export default makeNeuronStore global[MAKE_STORE_FUNCTION_NAME] = makeNeuronStore