const DEFAULT_EPSILON = 0.01; class Supervisor { /** * @type {{daemon: Daemon, rate: Number}[]} */ daemons = []; /** * @type {Number} */ epsilon; /** * @param {Daemon[]} daemons * @param {Number} epsilon */ constructor(daemons, epsilon = DEFAULT_EPSILON) { if (!daemons || daemons.length === 0) { throw Error('Empty daemon list'); } this.daemons = daemons.map(daemon => ({ daemon: daemon, rate: 0, })); this.epsilon = epsilon; } /** * @param {Journal} journal * * @returns {Number} */ predict(journal) { const predictions = this._createPredictions(journal); const ordered = this._sortPredictions(predictions); return ordered[0].value; } /** * @param {Journal} journal * @param {Number} humanValue */ adjust(journal, humanValue) { const predictions = this._createPredictions(journal); for (const prediction of predictions) { if (prediction.value === humanValue) { prediction.daemon.rate += this._getAdjustmentWeight( journal.length ); } prediction.daemon.daemon.adjust(journal, humanValue); } } /** * @param {Journal} journal * * @returns {Array} * * @private */ _createPredictions(journal) { return this.daemons.map(daemon => ({ daemon: daemon, power: daemon.daemon.power, rate: daemon.rate, value: daemon.daemon.predict(journal), })); } /** * @param {Array} predictions * * @returns {Array} * * @private */ _sortPredictions(predictions) { return predictions.sort((result1, result2) => { const rateDiff = result2.rate - result1.rate; if (Math.abs(rateDiff) > 0.000001) { return rateDiff; } return result1.power - result2.power; }); } /** * @param {Number} stepNumber * * @returns {Number} * * @private */ _getAdjustmentWeight(stepNumber) { return Math.pow(1 + this.epsilon, stepNumber); } } export default Supervisor;