From cb96183a7dba3e1e8664f158eaa980442143aa9b Mon Sep 17 00:00:00 2001
From: Anton Vakhrushev <anwinged@ya.ru>
Date: Sat, 30 Jun 2018 17:21:27 +0300
Subject: [PATCH] Add base and format code

---
 lib/predictor.js     |  2 +-
 package.json         |  4 +-
 source/Daemon.js     | 96 +++++++++++++++++++++++++++++++++++++++++---
 source/Journal.js    | 23 ++++++++++-
 source/Move.js       | 23 ++++++-----
 source/Predictor.js  | 63 ++++++++++++++++++++++++-----
 source/Supervisor.js | 47 +++++++++++++++++++++-
 source/index.js      |  5 ++-
 tests/DaemonTest.js  | 19 +++++----
 web/index.html       |  1 +
 10 files changed, 240 insertions(+), 43 deletions(-)

diff --git a/lib/predictor.js b/lib/predictor.js
index 6f7339d..94226a0 100644
--- a/lib/predictor.js
+++ b/lib/predictor.js
@@ -1 +1 @@
-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("predictor",[],t):"object"==typeof exports?exports.predictor=t():e.predictor=t()}(window,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=5)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}var u=.01;function i(e){return e.join("")}var a=function(){function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:u;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.weights={},this.humanCount=t,this.robotCount=n,this.epsilon=r}return r(e,[{key:"predict",value:function(e){var t=this._getStepSlice(e),n=this._getWeight([].concat(o(t),[0]));return this._getWeight([].concat(o(t),[1]))>n?1:0}},{key:"adjust",value:function(e,t){var n=this._getStepSlice(e),r=this._getAdjustmentWeight(e.length);this._adjustWeight([].concat(o(n),[t]),r)}},{key:"_getStepSlice",value:function(e){return e.getLastMovements(this.humanCount,this.robotCount)}},{key:"_getAdjustmentWeight",value:function(e){return Math.pow(1+this.epsilon,e)}},{key:"_getWeight",value:function(e){var t=i(e),n=this.weights[t];return void 0===n?0:n}},{key:"_setWeight",value:function(e,t){var n=i(e);this.weights[n]=t}},{key:"_adjustWeight",value:function(e,t){var n=this._getWeight(e)+t;this._setWeight(e,n)}},{key:"power",get:function(){return this.humanCount+this.robotCount}}]),e}();t.default=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();var o=.01,u=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o;if(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.daemons=[],!t)throw Error("Empty daemon list");this.daemons=t.map(function(e){return{daemon:e,rate:0}}),this.epsilon=n}return r(e,[{key:"predict",value:function(e){var t=this._createPredictions(e);return this._sortPredictions(t)[0].value}},{key:"adjust",value:function(e,t){var n=this._createPredictions(e),r=!0,o=!1,u=void 0;try{for(var i,a=n[Symbol.iterator]();!(r=(i=a.next()).done);r=!0){var s=i.value;s.value===t&&(s.daemon.rate+=this._getAdjustmentWeight(e.length)),s.daemon.daemon.adjust(e,t)}}catch(e){o=!0,u=e}finally{try{!r&&a.return&&a.return()}finally{if(o)throw u}}}},{key:"_createPredictions",value:function(e){return this.daemons.map(function(t){return{daemon:t,power:t.daemon.power,rate:t.rate,value:t.daemon.predict(e)}})}},{key:"_sortPredictions",value:function(e){return e.sort(function(e,t){var n=t.rate-e.rate;return Math.abs(n)>1e-6?n:e.power-t.power})}},{key:"_getAdjustmentWeight",value:function(e){return Math.pow(1+this.epsilon,e)}}]),e}();t.default=u},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();var o=function(){function e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this._human=t?1:0,this._robot=n?1:0}return r(e,[{key:"human",get:function(){return this._human}},{key:"robot",get:function(){return this._robot}}]),e}();t.default=o},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),u=n(2),i=(r=u)&&r.__esModule?r:{default:r};var a=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.moves=[],this.moves=t}return o(e,[{key:"makeMove",value:function(e,t){this.moves.push(new i.default(e,t))}},{key:"getLastMovements",value:function(e,t){var n=this.moves.map(function(e){return e.human}),r=this.moves.map(function(e){return e.robot});return[].concat(r.slice(-t),n.slice(-e))}},{key:"length",get:function(){return this.moves.length}}]),e}();t.default=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),o=a(n(3)),u=a(n(1)),i=a(n(0));function a(e){return e&&e.__esModule?e:{default:e}}var s={supervisor_epsilon:.01,daemons:[{human:2,robot:2,epsilon:.01},{human:3,robot:3,epsilon:.01},{human:4,robot:4,epsilon:.01},{human:5,robot:5,epsilon:.01},{human:6,robot:6,epsilon:.01}]},c=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.score=0,this.journal=new o.default;var n=t.daemons.map(function(e){return new i.default(e.human,e.robot,e.epsilon||.01)});this.supervisor=new u.default(n,t.supervisor_epsilon)}return r(e,[{key:"pass",value:function(e){var t=this.supervisor.predict(this.journal);return this.score+=t===e?-1:1,this.supervisor.adjust(this.journal,e),this.journal.makeMove(e,t),t}},{key:"stepCount",value:function(){return this.journal.length}}]),e}();t.default=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o=n(4),u=(r=o)&&r.__esModule?r:{default:r};t.default=u.default}])});
\ No newline at end of file
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("predictor",[],t):"object"==typeof exports?exports.predictor=t():e.predictor=t()}(window,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=5)}([function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();var o=.01,i=function(){function e(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o;if(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.daemons=[],!t||0===t.length)throw Error("Empty daemon list");this.daemons=t.map(function(e){return{daemon:e,rate:0}}),this.epsilon=n}return r(e,[{key:"predict",value:function(e){var t=this._createPredictions(e);return this._sortPredictions(t)[0].value}},{key:"adjust",value:function(e,t){var n=this._createPredictions(e),r=!0,o=!1,i=void 0;try{for(var u,a=n[Symbol.iterator]();!(r=(u=a.next()).done);r=!0){var s=u.value;s.value===t&&(s.daemon.rate+=this._getAdjustmentWeight(e.length)),s.daemon.daemon.adjust(e,t)}}catch(e){o=!0,i=e}finally{try{!r&&a.return&&a.return()}finally{if(o)throw i}}}},{key:"_createPredictions",value:function(e){return this.daemons.map(function(t){return{daemon:t,power:t.daemon.power,rate:t.rate,value:t.daemon.predict(e)}})}},{key:"_sortPredictions",value:function(e){return e.sort(function(e,t){var n=t.rate-e.rate;return Math.abs(n)>1e-6?n:e.power-t.power})}},{key:"_getAdjustmentWeight",value:function(e){return Math.pow(1+this.epsilon,e)}}]),e}();t.default=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});t.default=function e(t,n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.human=t,this.robot=n}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=n(1),u=(r=i)&&r.__esModule?r:{default:r};var a=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.moves=[],this.moves=t}return o(e,[{key:"makeMove",value:function(e,t){this.moves.push(new u.default(e,t))}},{key:"getLastMovements",value:function(e,t){var n=this.moves.map(function(e){return e.human}),r=this.moves.map(function(e){return e.robot});return[].concat(r.slice(-t),n.slice(-e))}},{key:"length",get:function(){return this.moves.length}}]),e}();t.default=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}();function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}var i=.01;function u(e){return e.join(":")}var a=function(){function e(t,n,r){var o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:i;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.weights={},this.base=t,this.humanCount=n,this.robotCount=r,this.epsilon=o}return r(e,[{key:"predict",value:function(e){for(var t=this._getStepSlice(e),n=[],r=0;r<this.base;++r)n[r]=this._getWeight([].concat(o(t),[r]));var i=Math.max.apply(Math,n);return n.indexOf(i)}},{key:"adjust",value:function(e,t){var n=this._getStepSlice(e),r=this._getAdjustmentWeight(e.length);this._adjustWeight([].concat(o(n),[t]),r)}},{key:"_getStepSlice",value:function(e){return e.getLastMovements(this.humanCount,this.robotCount)}},{key:"_getAdjustmentWeight",value:function(e){return Math.pow(1+this.epsilon,e)}},{key:"_getWeight",value:function(e){var t=u(e),n=this.weights[t];return void 0===n?0:n}},{key:"_setWeight",value:function(e,t){var n=u(e);this.weights[n]=t}},{key:"_adjustWeight",value:function(e,t){var n=this._getWeight(e)+t;this._setWeight(e,n)}},{key:"power",get:function(){return this.humanCount+this.robotCount}}]),e}();t.default=a},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),o=a(n(3)),i=a(n(2)),u=a(n(0));function a(e){return e&&e.__esModule?e:{default:e}}var s={base:2,supervisor_epsilon:.01,daemons:[{human:2,robot:2,epsilon:.01},{human:3,robot:3,epsilon:.01},{human:4,robot:4,epsilon:.01},{human:5,robot:5,epsilon:.01},{human:6,robot:6,epsilon:.01}]},c=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:s;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.base=t.base,this.score=0,this.journal=new i.default;var n=this._createDaemons(t.daemons);this.supervisor=new u.default(n,t.supervisor_epsilon)}return r(e,[{key:"pass",value:function(e){var t=parseInt(e,10);if(t<0||t>=this.base)throw new Error("Passed value must be in [0, "+this.base+")");var n=this.supervisor.predict(this.journal);return this.score+=n===t?-1:1,this.supervisor.adjust(this.journal,t),this.journal.makeMove(t,n),n}},{key:"_createDaemons",value:function(e){var t=this;return e.map(function(e){return new o.default(t.base,e.human,e.robot,e.epsilon||.01)})}},{key:"stepCount",value:function(){return this.journal.length}}]),e}();t.default=c},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,o=n(4),i=(r=o)&&r.__esModule?r:{default:r};t.default=i.default}])});
\ No newline at end of file
diff --git a/package.json b/package.json
index 3cdfa7c..1ea642e 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,9 @@
   "jest": {
     "collectCoverage": true,
     "collectCoverageFrom": [
-      "**/source/*.js"
+      "**/source/**/*.js",
+      "!**/source/build.js",
+      "!**/source/index.js"
     ],
     "testMatch": [
       "**/tests/**/*.js"
diff --git a/source/Daemon.js b/source/Daemon.js
index 52659bb..f81deb2 100644
--- a/source/Daemon.js
+++ b/source/Daemon.js
@@ -1,61 +1,145 @@
 const DEFAULT_EPSILON = 0.01;
 
+/**
+ * @param {Number[]} steps
+ *
+ * @returns {String}
+ */
 function create_key(steps) {
-    return steps.join('');
+    return steps.join(':');
 }
 
-export default class Daemon {
+class Daemon {
+    /**
+     * @type {Number}
+     */
+    base;
+
+    /**
+     * @type {Number}
+     */
     humanCount;
+
+    /**
+     * @type {Number}
+     */
     robotCount;
+
+    /**
+     * @type {Number}
+     */
     epsilon;
+
+    /**
+     * @type {Object}
+     */
     weights = {};
 
-    constructor(humanCount, robotCount, epsilon = DEFAULT_EPSILON) {
+    /**
+     * @param {Number} base
+     * @param {Number} humanCount
+     * @param {Number} robotCount
+     * @param {Number} epsilon
+     */
+    constructor(base, humanCount, robotCount, epsilon = DEFAULT_EPSILON) {
+        this.base = base;
         this.humanCount = humanCount;
         this.robotCount = robotCount;
         this.epsilon = epsilon;
     }
 
+    /**
+     * @returns {Number}
+     */
     get power() {
         return this.humanCount + this.robotCount;
     }
 
+    /**
+     * @param {Journal} journal
+     *
+     * @returns {Number}
+     */
     predict(journal) {
         const steps = this._getStepSlice(journal);
-        const w0 = this._getWeight([...steps, 0]);
-        const w1 = this._getWeight([...steps, 1]);
 
-        return w1 > w0 ? 1 : 0;
+        const proposals = [];
+        for (let i = 0; i < this.base; ++i) {
+            proposals[i] = this._getWeight([...steps, i]);
+        }
+
+        const maxWeight = Math.max(...proposals);
+
+        return proposals.indexOf(maxWeight);
     }
 
+    /**
+     * @param {Journal} journal
+     * @param {Number} humanValue
+     */
     adjust(journal, humanValue) {
         const steps = this._getStepSlice(journal);
         const adjustmentWeight = this._getAdjustmentWeight(journal.length);
         this._adjustWeight([...steps, humanValue], adjustmentWeight);
     }
 
+    /**
+     * @param {Journal} journal
+     *
+     * @returns {Number[]}
+     */
     _getStepSlice(journal) {
         return journal.getLastMovements(this.humanCount, this.robotCount);
     }
 
+    /**
+     * @param {Number} stepNumber
+     *
+     * @returns {Number}
+     *
+     * @private
+     */
     _getAdjustmentWeight(stepNumber) {
         return Math.pow(1 + this.epsilon, stepNumber);
     }
 
+    /**
+     * @param {Number[]} steps
+     *
+     * @returns {Number}
+     *
+     * @private
+     */
     _getWeight(steps) {
         const key = create_key(steps);
         const weight = this.weights[key];
         return weight === undefined ? 0 : weight;
     }
 
+    /**
+     * @param {Number[]} steps
+     * @param {Number} value
+     *
+     * @returns {Number}
+     *
+     * @private
+     */
     _setWeight(steps, value) {
         const key = create_key(steps);
         this.weights[key] = value;
     }
 
+    /**
+     * @param {Number[]} steps
+     * @param {Number} weight
+     *
+     * @private
+     */
     _adjustWeight(steps, weight) {
         const currentWeight = this._getWeight(steps);
         const newWeight = currentWeight + weight;
         this._setWeight(steps, newWeight);
     }
 }
+
+export default Daemon;
diff --git a/source/Journal.js b/source/Journal.js
index 350fe08..86584bd 100644
--- a/source/Journal.js
+++ b/source/Journal.js
@@ -1,16 +1,32 @@
 import Move from './Move';
 
-export default class Journal {
+class Journal {
+    /**
+     * @type {Move[]}
+     */
     moves = [];
 
+    /**
+     * @param {Move[]} moves
+     */
     constructor(moves = []) {
         this.moves = moves;
     }
 
+    /**
+     * @param {Number} human
+     * @param {Number} robot
+     */
     makeMove(human, robot) {
         this.moves.push(new Move(human, robot));
     }
 
+    /**
+     * @param {Number} humanCount
+     * @param {Number} robotCount
+     *
+     * @returns {Number[]}
+     */
     getLastMovements(humanCount, robotCount) {
         const humanMoves = this.moves.map(m => m.human);
         const robotMoves = this.moves.map(m => m.robot);
@@ -20,7 +36,12 @@ export default class Journal {
         );
     }
 
+    /**
+     * @returns {Number}
+     */
     get length() {
         return this.moves.length;
     }
 }
+
+export default Journal;
diff --git a/source/Move.js b/source/Move.js
index c4c032d..292735c 100644
--- a/source/Move.js
+++ b/source/Move.js
@@ -1,14 +1,15 @@
-export default class Move {
+/**
+ * Represents one game move.
+ */
+class Move {
+    /**
+     * @param {Number} human
+     * @param {Number} robot
+     */
     constructor(human, robot) {
-        this._human = human ? 1 : 0;
-        this._robot = robot ? 1 : 0;
-    }
-
-    get human() {
-        return this._human;
-    }
-
-    get robot() {
-        return this._robot;
+        this.human = human;
+        this.robot = robot;
     }
 }
+
+export default Move;
diff --git a/source/Predictor.js b/source/Predictor.js
index a689371..fe3b397 100644
--- a/source/Predictor.js
+++ b/source/Predictor.js
@@ -1,8 +1,9 @@
+import Daemon from './Daemon';
 import Journal from './Journal';
 import Supervisor from './Supervisor';
-import Daemon from './Daemon';
 
 const DEFAULT_CONFIG = {
+    base: 2,
     supervisor_epsilon: 0.01,
     daemons: [
         { human: 2, robot: 2, epsilon: 0.01 },
@@ -14,24 +15,47 @@ const DEFAULT_CONFIG = {
 };
 
 export default class Predictor {
+    /**
+     * @type {Number}
+     */
+    base;
+
+    /**
+     * @type {Number}
+     */
     score;
+
+    /**
+     * @type {Journal}
+     */
     journal;
+
+    /**
+     * @type {Supervisor}
+     */
     supervisor;
 
+    /**
+     * @param {Object} config
+     */
     constructor(config = DEFAULT_CONFIG) {
+        this.base = config.base;
         this.score = 0;
         this.journal = new Journal();
-        const daemons = config.daemons.map(daemonConfig => {
-            return new Daemon(
-                daemonConfig.human,
-                daemonConfig.robot,
-                daemonConfig.epsilon || 0.01
-            );
-        });
+        const daemons = this._createDaemons(config.daemons);
         this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
     }
 
-    pass(value) {
+    /**
+     * @param {Number|String} humanValue
+     *
+     * @returns {Number}
+     */
+    pass(humanValue) {
+        const value = parseInt(humanValue, 10);
+        if (value < 0 || value >= this.base) {
+            throw new Error(`Passed value must be in [0, ${this.base})`);
+        }
         const prediction = this.supervisor.predict(this.journal);
         this.score += prediction === value ? -1 : 1;
         this.supervisor.adjust(this.journal, value);
@@ -39,6 +63,27 @@ export default class Predictor {
         return prediction;
     }
 
+    /**
+     * @param {Object} daemonConfigs
+     *
+     * @returns {Daemon[]}
+     *
+     * @private
+     */
+    _createDaemons(daemonConfigs) {
+        return daemonConfigs.map(config => {
+            return new Daemon(
+                this.base,
+                config.human,
+                config.robot,
+                config.epsilon || 0.01
+            );
+        });
+    }
+
+    /**
+     * @returns {Number}
+     */
     stepCount() {
         return this.journal.length;
     }
diff --git a/source/Supervisor.js b/source/Supervisor.js
index 50ca98a..faaa0cd 100644
--- a/source/Supervisor.js
+++ b/source/Supervisor.js
@@ -1,11 +1,22 @@
 const DEFAULT_EPSILON = 0.01;
 
-export default class Supervisor {
+class Supervisor {
+    /**
+     * @type {{daemon: Daemon, rate: Number}[]}
+     */
     daemons = [];
+
+    /**
+     * @type {Number}
+     */
     epsilon;
 
+    /**
+     * @param {Daemon[]} daemons
+     * @param {Number} epsilon
+     */
     constructor(daemons, epsilon = DEFAULT_EPSILON) {
-        if (!daemons) {
+        if (!daemons || daemons.length === 0) {
             throw Error('Empty daemon list');
         }
         this.daemons = daemons.map(daemon => ({
@@ -15,6 +26,11 @@ export default class Supervisor {
         this.epsilon = epsilon;
     }
 
+    /**
+     * @param {Journal} journal
+     *
+     * @returns {Number}
+     */
     predict(journal) {
         const predictions = this._createPredictions(journal);
         const ordered = this._sortPredictions(predictions);
@@ -22,6 +38,10 @@ export default class Supervisor {
         return ordered[0].value;
     }
 
+    /**
+     * @param {Journal} journal
+     * @param {Number} humanValue
+     */
     adjust(journal, humanValue) {
         const predictions = this._createPredictions(journal);
         for (const prediction of predictions) {
@@ -34,6 +54,13 @@ export default class Supervisor {
         }
     }
 
+    /**
+     * @param {Journal} journal
+     *
+     * @returns {Array}
+     *
+     * @private
+     */
     _createPredictions(journal) {
         return this.daemons.map(daemon => ({
             daemon: daemon,
@@ -43,6 +70,13 @@ export default class Supervisor {
         }));
     }
 
+    /**
+     * @param {Array} predictions
+     *
+     * @returns {Array}
+     *
+     * @private
+     */
     _sortPredictions(predictions) {
         return predictions.sort((result1, result2) => {
             const rateDiff = result2.rate - result1.rate;
@@ -53,7 +87,16 @@ export default class Supervisor {
         });
     }
 
+    /**
+     * @param {Number} stepNumber
+     *
+     * @returns {Number}
+     *
+     * @private
+     */
     _getAdjustmentWeight(stepNumber) {
         return Math.pow(1 + this.epsilon, stepNumber);
     }
 }
+
+export default Supervisor;
diff --git a/source/index.js b/source/index.js
index 0f69324..88dbb1d 100644
--- a/source/index.js
+++ b/source/index.js
@@ -6,6 +6,7 @@ new Vue({
     el: '#app',
     data: {
         predictor: new Predictor({
+            base: 3,
             daemons: [
                 { human: 3, robot: 3 },
                 { human: 4, robot: 4 },
@@ -15,11 +16,11 @@ new Vue({
     },
     methods: {
         click(v) {
-            const value = v ? 1 : 0;
+            const value = parseInt(v, 10);
             this.pass(value);
         },
         press(evt) {
-            const value = evt.key === '1' ? 0 : 1;
+            const value = parseInt(evt.key, 10) - 1;
             this.pass(value);
         },
         pass(value) {
diff --git a/tests/DaemonTest.js b/tests/DaemonTest.js
index 0dc8ba7..80279a3 100644
--- a/tests/DaemonTest.js
+++ b/tests/DaemonTest.js
@@ -1,21 +1,21 @@
-import Daemon from '../source/Daemon';
 import expect from 'expect';
-import History from '../source/Journal';
+import Daemon from '../source/Daemon';
+import Journal from '../source/Journal';
 
 test('Get prediction for beginning', function() {
-    const m = new History();
-    const d = new Daemon(1, 1);
+    const m = new Journal();
+    const d = new Daemon(2, 1, 1);
     expect(d.predict(m)).toEqual(0);
 });
 
 test('Can get power', function() {
-    const d = new Daemon(5, 8);
+    const d = new Daemon(2, 5, 8);
     expect(d.power).toEqual(13);
 });
 
 test('Daemon 1-1', function() {
-    const m = new History();
-    const d = new Daemon(1, 1);
+    const m = new Journal();
+    const d = new Daemon(2, 1, 1);
 
     const steps = [
         {
@@ -40,11 +40,10 @@ test('Daemon 1-1', function() {
         },
     ];
 
-    steps.forEach((step, index) => {
+    steps.forEach(step => {
         const prediction = d.predict(m);
         expect(prediction).toEqual(step.prediction);
-        d.adjust(m, step.human, index + 1);
+        d.adjust(m, step.human);
         m.makeMove(step.human, step.prediction);
-        console.log('Step', index + 1, d);
     });
 });
diff --git a/web/index.html b/web/index.html
index 8b1bdf8..e074b0b 100644
--- a/web/index.html
+++ b/web/index.html
@@ -11,6 +11,7 @@
             <div class="buttons">
                 <button value="0" v-on:click="click(0)">0</button>
                 <button value="1" v-on:click="click(1)">1</button>
+                <button value="2" v-on:click="click(2)">2</button>
             </div>
         </main>
         <script src="dist/app.js"></script>