Neuron.php.makeNeuronStore.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. var MAKE_STORE_FUNCTION_NAME = MAKE_STORE_FUNCTION_NAME || 'makeNeuronStore'
  2. var DBG = DBG || false;
  3. var DBG1 = true;
  4. // var STRATEGY_OVERCHARGE = [
  5. // "REMOVE_CHARGE",
  6. // "LEAVE_HALF_CHARGE",
  7. // "LEAVE_ALMOST_MAX",
  8. // ]
  9. var DEFAULT_CONFIG = {
  10. ui_output_width: 800,
  11. ui_max_receptor_r: 12,
  12. ui_space_y: 50,
  13. config_anim_speed: 300,
  14. config_read_speed_multiplier: 4,
  15. config_charge_receptor_at_input: 1,
  16. config_max_receptor_charge: 1,
  17. config_max_neuron_charge: 1.5,
  18. config_discharge_per_tick: 0.1,
  19. config_discharge_max_in_new_neuron_from_one: 0.7,
  20. }
  21. var DEFAULT_NEURON = {
  22. value: '',
  23. charge: 0,
  24. maxCharge: DEFAULT_CONFIG.config_max_neuron_charge,
  25. uiShape: "circle",
  26. ui: { cx: 0, cy: 0 },
  27. source: [],
  28. next: [],
  29. }
  30. function makeDefaultNeuronsStoreState() {
  31. return {
  32. receptor: [],
  33. mapReceptorChar: {},
  34. neuron: [],
  35. connection: [],
  36. doubleConn: [], // connections between two nodes
  37. charge: [], // TODO: list of [ { charge: number, nodeType: (receptor|neuron), nodeIdx: int }, ... ] groupd by node (type/idx)
  38. inputReadPos: 0,
  39. animPos: 0,
  40. doAnim: true,
  41. uiOutputHeight: 300,
  42. }
  43. }
  44. function makeNeuronStore() {
  45. DBG && console.log("DBG:NeuronStore:makeNeuronStore");
  46. var _state = makeDefaultNeuronsStoreState()
  47. var _config = {}
  48. var _inputText = ''
  49. var _animIntervalID = null
  50. var _callback = null
  51. function readCharFromInput() {
  52. if (_state.inputReadPos >= _inputText.length) return null;
  53. var char = _inputText.charAt(_state.inputReadPos)
  54. _state.inputReadPos += 1
  55. return char;
  56. }
  57. function forwardAnim() {
  58. DBG1 && console.log("DBG:NeuronStore:forwardAnim:doAnim = '" + (_state.doAnim ? 1 : 0) + "'");
  59. if (!_state.doAnim) return;
  60. // 1. read from input if its time
  61. // 2. check overcharged nodes
  62. // 2.1 check overcharged Receptor
  63. // 2.2 check overcharged Neuron
  64. // 3. move all charges (Receptor -> Connection, Connection -> Neuron, Neuron -> Connection)
  65. // 4. discharge all
  66. // ad.2. TODO: what to do with Charge when create new Neuron?
  67. // - idea 1: use Charge to create new Neuron: rm Charge, add Neuron, add Connection
  68. // 1. read from input if its time
  69. if (0 === _state.animPos % _config.config_read_speed_multiplier) {
  70. // read from input = add charge to receptor with given letter
  71. var char = readCharFromInput()
  72. if (null === char) {
  73. DBG1 && console.log("DBG:NeuronStore:forwardAnim:1(readInput): STOP - end of input", { inputLength: _inputText.length, animPos: _state.animPos })
  74. _state.doAnim = false
  75. _notifySubscribers()
  76. return;
  77. }
  78. var foundReceptorIdx = getReceptorIdx(char)
  79. DBG1 && console.log("DBG:NeuronStore:forwardAnim:1(readInput): char('" + char + "', [" + char.charCodeAt(0) + "])", { animPos: _state.animPos, char, len: _inputText.length, doAnim: _state.doAnim, foundReceptorIdx })
  80. if (-1 === foundReceptorIdx) {
  81. DBG1 && console.warn("BUG: receptor '" + char + "' not found (NeuronStore:forwardAnim:readInput)")
  82. _state.doAnim = false
  83. _notifySubscribers()
  84. return;
  85. }
  86. chargeAdd(_config.config_charge_receptor_at_input, { type: 'receptor', idx: foundReceptorIdx })
  87. _state.animPos += 1
  88. _notifySubscribers()
  89. return;
  90. }
  91. // 2. check overcharged nodes
  92. // 2.1 check overcharged Receptor
  93. // 2.2 check overcharged Neuron
  94. { // check overcharged nodes
  95. var overchargedNeuronIdx = _state.neuron.reduce(function (ret, neuron, idx) {
  96. var charge = getNeuronCharge(idx)
  97. if (charge < neuron.maxCharge) return ret;
  98. ret.push({ idx: idx, charge: charge })
  99. return ret;
  100. }, [])
  101. var overchargedReceptorIdx = _state.receptor.reduce(function (ret, receptor, idx) {
  102. var charge = getReceptorCharge(idx)
  103. if (charge < _config.config_max_receptor_charge) return ret;
  104. ret.push({ idx: idx, charge: charge })
  105. return ret;
  106. }, [])
  107. if (overchargedNeuronIdx.length > 0) {
  108. var overcharged = overchargedNeuronIdx[0]
  109. var firstNeuron = _state.neuron[overcharged.idx]
  110. var secondNeuronWithCharge = _state.neuron.reduce(function (ret, neuron, idx) { // find neuron with max charge
  111. if (idx === overcharged.idx) return ret; // skip firstNeuron
  112. var charge = getNeuronCharge(idx)
  113. if (!charge) return ret; // skip Neuron without charge
  114. if (null === ret) return { idx: idx, charge: charge }
  115. return (charge > ret.charge) ? { idx: idx, charge: charge } : ret;
  116. }, null)
  117. DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.1", { overcharged, firstNeuron, secondNeuronWithCharge })
  118. if (null !== secondNeuronWithCharge) {
  119. // is already exists - check doubleConn: { from: [ neuronIdx, neuronIdx ], to: neuronIdx }
  120. var existingConn = _state.doubleConn.reduce(function (ret, dblConn) {
  121. if (ret) return ret;
  122. if (dblConn.from[0] === overcharged.idx && dblConn.from[1] === secondNeuronWithCharge.idx) return dblConn;
  123. // if (dblConn.from[1] === overcharged.idx && dblConn.from[0] === secondNeuronWithCharge.idx) return dblConn;
  124. return ret;
  125. }, null)
  126. var TODO__value = [firstNeuron.value, _state.neuron[secondNeuronWithCharge.idx].value].join('')
  127. var TODO__toNodeIdx = _state.neuron.reduce(function (ret, node, idx) {
  128. if (node.value === TODO__value) return idx;
  129. return ret;
  130. }, null)
  131. var TODO__toNode = (null !== TODO__toNodeIdx) ? _state.neuron[TODO__toNodeIdx] : null
  132. if (null !== TODO__toNode && null !== existingConn && TODO__toNodeIdx !== existingConn.toNode) {
  133. // existingConn = null // Froce create new connection if not the same order
  134. }
  135. var toNode = _state.neuron[secondNeuronWithCharge.idx]
  136. DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2(secondNeuronWithCharge)", { from: firstNeuron.value, to: toNode.value, existingConn, TODO__value, TODO__toNode })
  137. if (existingConn) { // charge Neuron existingConn.to
  138. var idxNewNeuron = existingConn.to
  139. chargeRemove({ type: 'neuron', idx: overcharged.idx })
  140. chargeRemove({ type: 'neuron', idx: secondNeuronWithCharge.idx })
  141. switch (_config.config_strategy_overcharge) {
  142. case "REMOVE_CHARGE": break;
  143. case "LEAVE_HALF_CHARGE": {
  144. chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
  145. chargeAdd(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx })
  146. } break;
  147. case "LEAVE_ALMOST_MAX": {
  148. chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
  149. chargeAdd(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx })
  150. } break;
  151. }
  152. chargeAdd(overcharged.charge + secondNeuronWithCharge.charge, { type: 'neuron', idx: existingConn.to })
  153. } else { // create new
  154. var newNeuron = makeNeuronFromTwoNeurons(overcharged.idx, overcharged.charge, secondNeuronWithCharge.idx, secondNeuronWithCharge.charge)
  155. _state.neuron.push(newNeuron)
  156. updateOuputHeight(newNeuron)
  157. var idxNewNeuron = _state.neuron.length - 1
  158. _state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
  159. _state.connection.push({ fromType: 'neuron', fromIdx: secondNeuronWithCharge.idx, toIdx: idxNewNeuron, timesCreated: 1 })
  160. _state.doubleConn.push({ from: [overcharged.idx, secondNeuronWithCharge.idx], to: idxNewNeuron })
  161. chargeRemove({ type: 'neuron', idx: overcharged.idx })
  162. chargeRemove({ type: 'neuron', idx: secondNeuronWithCharge.idx })
  163. switch (_config.config_strategy_overcharge) {
  164. case "REMOVE_CHARGE": break;
  165. case "LEAVE_HALF_CHARGE": {
  166. chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
  167. chargeAdd(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx })
  168. } break;
  169. case "LEAVE_ALMOST_MAX": {
  170. chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
  171. chargeAdd(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx })
  172. } break;
  173. }
  174. }
  175. // _state.animPos += 1
  176. _notifySubscribers()
  177. return;
  178. } else { // !secondNeuronWithCharge
  179. // TODO: check if already exists! if yes then charge else create new
  180. var idxFoundConnection = _state.connection.reduce(function (ret, conn, idx) {
  181. if (ret > -1) return ret;
  182. if (conn.fromType === 'neuron' && conn.fromIdx === overcharged.idx) return idx;
  183. return ret;
  184. }, -1)
  185. DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2(!secondNeuronWithCharge)", { idxFoundConnection })
  186. if (-1 === idxFoundConnection) {
  187. var newNeuron = makeNeuronFromOneNeuron(overcharged.idx, overcharged.charge)
  188. _state.neuron.push(newNeuron)
  189. updateOuputHeight(newNeuron)
  190. var idxNewNeuron = _state.neuron.length - 1
  191. _state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
  192. chargeRemove({ type: 'neuron', idx: overcharged.idx })
  193. switch (_config.config_strategy_overcharge) {
  194. case "REMOVE_CHARGE": break;
  195. case "LEAVE_HALF_CHARGE": {
  196. chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
  197. } break;
  198. case "LEAVE_ALMOST_MAX": {
  199. chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
  200. } break;
  201. }
  202. } else {
  203. _state.connection[idxFoundConnection].timesCreated += 1
  204. // chargeMove(overcharged.charge, { type: 'receptor', idx: overcharged.idx }, { type: 'connection', idx: idxFoundConnection })
  205. chargeRemove({ type: 'neuron', idx: overcharged.idx })
  206. switch (_config.config_strategy_overcharge) {
  207. case "REMOVE_CHARGE": break;
  208. case "LEAVE_HALF_CHARGE": {
  209. chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
  210. } break;
  211. case "LEAVE_ALMOST_MAX": {
  212. chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
  213. } break;
  214. }
  215. chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
  216. }
  217. }
  218. }
  219. if (overchargedReceptorIdx.length > 0) { // TODO: while ?
  220. if (1 === overchargedReceptorIdx.length) {
  221. var overcharged = overchargedReceptorIdx.shift()
  222. // Restriction: only one connection between nodes
  223. {
  224. var idxFoundConnection = _state.connection.reduce(function (ret, conn, idx) {
  225. if (ret > -1) return ret;
  226. if (conn.fromType === 'receptor' && conn.fromIdx === overcharged.idx) return idx;
  227. return ret;
  228. }, -1)
  229. DBG1 && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)/receptor", { idxFoundConnection, from: { type: 'receptor', idx: overcharged.idx }, conn: [].concat(_state.connection) })
  230. if (-1 === idxFoundConnection) {
  231. var newNeuron = makeNeuronFromOneReceptor(overcharged.idx, overcharged.charge)
  232. _state.neuron.push(newNeuron)
  233. var idxNewNeuron = _state.neuron.length - 1
  234. _state.connection.push({ fromType: 'receptor', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
  235. idxFoundConnection = _state.connection.length - 1
  236. chargeRemove({ type: 'receptor', idx: overcharged.idx })
  237. switch (_config.config_strategy_overcharge) {
  238. case "REMOVE_CHARGE": break;
  239. case "LEAVE_HALF_CHARGE": {
  240. chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
  241. } break;
  242. case "LEAVE_ALMOST_MAX": {
  243. chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
  244. } break;
  245. }
  246. chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
  247. } else {
  248. _state.connection[idxFoundConnection].timesCreated += 1
  249. // chargeMove(overcharged.charge, { type: 'receptor', idx: overcharged.idx }, { type: 'connection', idx: idxFoundConnection })
  250. chargeRemove({ type: 'receptor', idx: overcharged.idx })
  251. switch (_config.config_strategy_overcharge) {
  252. case "REMOVE_CHARGE": break;
  253. case "LEAVE_HALF_CHARGE": {
  254. chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
  255. } break;
  256. case "LEAVE_ALMOST_MAX": {
  257. chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
  258. } break;
  259. }
  260. chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
  261. }
  262. }
  263. // _state.animPos += 1
  264. _notifySubscribers()
  265. return;
  266. } else {
  267. DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/receptor", { TODO: "more then 1 receptor overcharged!" })
  268. }
  269. }
  270. // DBG1 && console.log("DBG:NeuronStore:forwardAnim:2(check overcharged)", { overchargedNeuronIdx, overchargedReceptorIdx })
  271. }
  272. { // 3. move all charges (Receptor -> Connection, Connection -> Neuron, Neuron -> Connection)
  273. DBG1 && console.log("TODO:NeuronStore:forwardAnim:3(move all charges)", { charge: [].concat(_state.charge) })
  274. var toAddChargeNeuronIdxList = _state.charge.filter(function (charge) { return ("connection" === charge.nodeType); }).map(function (charge) {
  275. return { charge: charge.charge, connIdx: charge.nodeIdx };
  276. })
  277. _state.charge = _state.charge.filter(function (charge) { return !("connection" === charge.nodeType); })
  278. toAddChargeNeuronIdxList.forEach(function (toAddCharge) {
  279. var conn = _state.connection[toAddCharge.connIdx]
  280. chargeAdd(toAddCharge.charge, { type: 'neuron', idx: conn.toIdx })
  281. })
  282. }
  283. { // 4. discharge all
  284. _state.charge = _state.charge.map(function (charge) {
  285. return Object.assign({}, charge, {
  286. charge: Math.max(0, charge.charge - _config.config_discharge_per_tick),
  287. })
  288. }).filter(function (charge) {
  289. return charge.charge > 0;
  290. })
  291. }
  292. _state.animPos += 1
  293. _notifySubscribers();
  294. }
  295. function makeNeuronFromTwoNeurons(fromNeuronIdx, fromNeuronCharge, toNeuronIdx, toNeuronCharge) {
  296. var fromNode = _state.neuron[fromNeuronIdx]
  297. var toNode = _state.neuron[toNeuronIdx]
  298. DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { fromNode, toNode })
  299. var uiNewNodePos = { cx: 0, cy: 0 }
  300. { // closer to node with more charge (first)
  301. var xDiff = Math.abs(fromNode.ui.cx - toNode.ui.cx)
  302. var isFirstOnRight = (fromNode.ui.cx > toNode.ui.cx)
  303. // fromNeuronCharge + toNeuronCharge -- xDiff
  304. // toNeuronCharge -- x ==> x = toNeuronCharge * xDiff / (fromNeuronCharge + toNeuronCharge)
  305. var xToFirst = (toNeuronCharge * xDiff) / (fromNeuronCharge + toNeuronCharge)
  306. uiNewNodePos.cx = fromNode.ui.cx + (isFirstOnRight ? -1 : 1) * xToFirst
  307. }
  308. uiNewNodePos.cy = _state.neuron.reduce(function (ret, neuron) {
  309. if (uiNewNodePos.cx < neuron.ui.cx - 10) return ret;
  310. if (uiNewNodePos.cx > neuron.ui.cx + 10) return ret;
  311. if (ret < neuron.ui.cy - 10) return ret;
  312. if (ret > neuron.ui.cy + 10) return ret;
  313. return ret + 10 + _config.ui_space_y;
  314. }, fromNode.ui.cy + 10 + _config.ui_space_y)
  315. DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { uiNewNodePos, fromNode, toNode })
  316. // var value = '' + (_state.neuron.length + 1);
  317. var value = [fromNode.value, toNode.value].join('')
  318. return Object.assign({}, DEFAULT_NEURON, {
  319. value: value,
  320. charge: 0,
  321. maxCharge: fromNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
  322. uiShape: "ellipse",
  323. ui: Object.assign({}, uiNewNodePos, {
  324. rx: 10,
  325. ry: 10,
  326. }),
  327. });
  328. }
  329. function makeNeuronFromOneReceptor(fromReceptorIdx, charge) {
  330. var sourceNode = _state.receptor[fromReceptorIdx]
  331. // var value = '' + (_state.neuron.length + 1);
  332. var value = [sourceNode.value, ""].join('') // TODO: same name what Receptor
  333. DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneReceptor", { sourceNode, confMaxCharge: _config.config_discharge_max_in_new_neuron_from_one })
  334. return Object.assign({}, DEFAULT_NEURON, {
  335. value: value,
  336. charge: 0,
  337. maxCharge: sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
  338. uiShape: "ellipse",
  339. ui: {
  340. cx: sourceNode.ui.cx,
  341. cy: sourceNode.ui.cy + 10 + _config.ui_space_y,
  342. rx: 10,
  343. ry: 10,
  344. },
  345. });
  346. }
  347. function makeNeuronFromOneNeuron(fromNeuronIdx, charge) {
  348. var sourceNode = _state.neuron[fromNeuronIdx]
  349. // var value = '' + (_state.neuron.length + 1);
  350. var value = [sourceNode.value, "^"].join('')
  351. DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneNeuron", { sourceNode, confMaxCharge: _config.config_discharge_max_in_new_neuron_from_one })
  352. return Object.assign({}, DEFAULT_NEURON, {
  353. value: value,
  354. charge: 0,
  355. maxCharge: sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
  356. uiShape: "ellipse",
  357. ui: {
  358. cx: sourceNode.ui.cx,
  359. cy: sourceNode.ui.cy + 10 + _config.ui_space_y,
  360. rx: 10,
  361. ry: 10,
  362. },
  363. });
  364. }
  365. function chargeAdd(charge, to) {
  366. var foundChargeIdx = _state.charge.reduce(function (ret, charge, idx) {
  367. if (charge.nodeType === to.type && charge.nodeIdx === to.idx) return idx;
  368. return ret;
  369. }, -1)
  370. DBG1 && console.log("DBG:NeuronStore:chargeAdd", { charge, to, foundChargeIdx })
  371. if (-1 === foundChargeIdx) {
  372. _state.charge.push({ charge: charge, nodeType: to.type, nodeIdx: to.idx })
  373. foundChargeIdx = _state.charge.length - 1
  374. } else {
  375. _state.charge[foundChargeIdx].charge += charge
  376. }
  377. DBG1 && console.log("DBG:NeuronStore:chargeAdd.2", { state_charge: [].concat(_state.charge) })
  378. return foundChargeIdx;
  379. }
  380. function chargeRemove(from) {
  381. _state.charge = _state.charge.filter(function (item) {
  382. return !(from.type === item.nodeType && from.idx === item.nodeIdx);
  383. })
  384. }
  385. function updateOuputHeight(newNeuron) {
  386. if (newNeuron.ui.cy + 30 > _state.uiOutputHeight) {
  387. _state.uiOutputHeight = newNeuron.ui.cy + 30
  388. }
  389. }
  390. function dispatch(payload) {
  391. DBG1 && console.log("DBG:NeuronStore:dispatch('" + payload.type + "')", payload);
  392. switch (payload.type) {
  393. case 'INIT': stateReset(payload.inputText, payload.config); startAnimation(); break;
  394. case 'PAUSE': _state.doAnim = false; _notifySubscribers(); break;
  395. case 'PLAY': _state.doAnim = true; break;
  396. default: {
  397. DBG1 && console.warn("Not implemented dispatch action type '" + payload.type + "'")
  398. }
  399. }
  400. }
  401. function subscribe(callback) {
  402. _callback = callback; // TODO: array
  403. return unsubscribe;
  404. }
  405. function unsubscribe() {
  406. _callback = null
  407. }
  408. function _notifySubscribers() {
  409. if (_callback) _callback()
  410. }
  411. function makeReceptor(value, idx, arr) { // usage: [].map(makeReceptor)
  412. var totalReceptors = arr.length;
  413. var cx = Math.ceil(_config.ui_output_width / (totalReceptors + 1));
  414. var r = Math.min(Math.ceil(_config.ui_output_width / (totalReceptors + 10) / 2), _config.ui_max_receptor_r);
  415. var xCenter = cx + cx * idx - r;
  416. return Object.assign({}, DEFAULT_NEURON, {
  417. value: value,
  418. maxCharge: _config.config_max_receptor_charge,
  419. uiShape: "circle",
  420. ui: {
  421. cx: xCenter,
  422. cy: 40,
  423. r: r,
  424. },
  425. next: [],
  426. })
  427. }
  428. function stateReset(inputText, config) {
  429. _inputText = inputText
  430. _config = config
  431. _state = makeDefaultNeuronsStoreState()
  432. var distinct = function (value, idx, self) {
  433. return (idx === self.indexOf(value));
  434. };
  435. var foundLetters = inputText.split("").filter(distinct);
  436. _state.receptor = foundLetters.map(makeReceptor);
  437. _state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; });
  438. if (_animIntervalID) clearInterval(_animIntervalID)
  439. _notifySubscribers();
  440. }
  441. function getReceptorIdx(char) {
  442. return _state.mapReceptorChar.indexOf(char)
  443. }
  444. function startAnimation() {
  445. _state.doAnim = true
  446. if (_animIntervalID) clearInterval(_animIntervalID)
  447. _animIntervalID = setInterval(forwardAnim, _config.config_anim_speed)
  448. }
  449. function getReceptorCharge(idx) {
  450. return _state.charge.filter(function (charge) {
  451. return ('receptor' === charge.nodeType && idx === charge.nodeIdx);
  452. }).reduce(function (ret, charge) {
  453. return ret + charge.charge;
  454. }, 0)
  455. }
  456. function getNeuronCharge(idx) {
  457. return _state.charge.filter(function (charge) {
  458. return ('neuron' === charge.nodeType && idx === charge.nodeIdx);
  459. }).reduce(function (ret, charge) {
  460. return ret + charge.charge;
  461. }, 0)
  462. }
  463. function getReceptorAlmostMaxCharge(idx) {
  464. return _state.receptor[idx].maxCharge - _config.config_discharge_per_tick;
  465. }
  466. function getNeuronAlmostMaxCharge(idx) {
  467. DBG1 && console.log("DBG:getNeuronAlmostMaxCharge(" + idx + ")", { charge: _state.neuron[idx].maxCharge - _config.config_discharge_per_tick })
  468. return _state.neuron[idx].maxCharge - _config.config_discharge_per_tick;
  469. }
  470. function getNodesChargeState() {
  471. var receptor = [].concat(_state.receptor);
  472. var neuron = [].concat(_state.neuron);
  473. _state.charge.forEach(function (charge) {
  474. // _state.charge: [ { charge: number, nodeType: (receptor|neuron), nodeIdx: int }, ... ] groupd by node (type/idx)
  475. switch (charge.nodeType) {
  476. case 'receptor': receptor[charge.nodeIdx].charge = charge.charge; break;
  477. case 'neuron': neuron[charge.nodeIdx].charge = charge.charge; break;
  478. }
  479. })
  480. function getNodeInfoFunction(type) {
  481. return function _getNodeInfo(node) {
  482. return {
  483. type: type,
  484. value: node.value,
  485. charge: node.charge,
  486. }
  487. }
  488. }
  489. return receptor.map(getNodeInfoFunction('receptor'))
  490. .concat(
  491. neuron.map(getNodeInfoFunction('neuron'))
  492. )
  493. ;
  494. }
  495. return {
  496. subscribe: subscribe,
  497. dispatch: dispatch,
  498. getState: function () { return _state; },
  499. getNodesChargeState: getNodesChargeState,
  500. getListReceptor: function () { return _state.receptor; },
  501. getListNeuron: function () { return _state.neuron; },
  502. getReceptor: function (idx) { return _state.receptor[idx]; },
  503. getNeuron: function (idx) { return _state.neuron[idx]; },
  504. getConnection: function (idx) { return _state.connection[idx]; },
  505. getListConnection: function () { return _state.connection; },
  506. getListCharge: function () { return _state.charge; },
  507. getReceptorCharge: getReceptorCharge,
  508. getNeuronCharge: getNeuronCharge,
  509. }
  510. }
  511. global[MAKE_STORE_FUNCTION_NAME] = makeNeuronStore
  512. // export default makeNeuronStore