Дополнил описание галадки Шеннона
This commit is contained in:
		
							
								
								
									
										6
									
								
								npm-shrinkwrap.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								npm-shrinkwrap.json
									
									
									
										generated
									
									
									
								
							@@ -4,6 +4,12 @@
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@anwinged/predictor": {
 | 
			
		||||
      "version": "0.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@anwinged/predictor/-/predictor-0.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-817M9xiPesxLtvUH/qZNs3EBNw5HBOR/W8T3HYLqyNvMRCEJ/h86uJJB5BW+FzZc+mQsVQcRX+NI8pSOBr3jwg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/code-frame": {
 | 
			
		||||
      "version": "7.8.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.0.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "Homepage",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@anwinged/predictor": "^0.2.1",
 | 
			
		||||
    "@babel/core": "^7.8.0",
 | 
			
		||||
    "@babel/plugin-proposal-class-properties": "^7.8.0",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "^7.8.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,130 @@ scripts:
 | 
			
		||||
    - /static/predictor.js
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Правила игры простые. Робот пытается предсказать, что выберет человек: чет или нечет.
 | 
			
		||||
Если предсказание удалось, робот получает очко, а счет уменьшается на единицу.
 | 
			
		||||
Если же предугадать выбор не удалось, то очко достается человеку, а счет увеличивается.
 | 
			
		||||
Игрок победит, если доберется до 50 очков. Но когда счет опустится до -50, победа будет
 | 
			
		||||
за железякой.
 | 
			
		||||
В студенческое время я наткнулся на интересную статью об [игре "Чет-нечет"](game)
 | 
			
		||||
на домашней страничке пользователя [ltwood](ltwood).
 | 
			
		||||
 | 
			
		||||
Чтобы выбирать вариант с клавиатуры, кликните внутри серой рамки,
 | 
			
		||||
а потом пользуйтесь клавишами "1" - нечет или "2" - чет.
 | 
			
		||||
Правила очень простые. Игрок загадывает один вариант из двух: "чет" или "нечет",
 | 
			
		||||
а оппонент пытается угадать выбор игрока. Если угадать не удалось, то очко получает
 | 
			
		||||
загадавший, а если угадать получилось - то угадывающий. Кто первым наберет 20 очков,
 | 
			
		||||
тот и молодец!
 | 
			
		||||
 | 
			
		||||
-   [Код гадалки](https://github.com/anwinged/homepage/blob/master/source/_assets/predictor/demo.vue)
 | 
			
		||||
Кажется, что в этой игре все случайно. Случайно загадывается число, потом случайно
 | 
			
		||||
второй игрок пытается угадать что же было загадано. Я очень сильно удивился, когда
 | 
			
		||||
попробовал поиграть в эту игру с программой и за десять попыток так ни разу и не выиграл.
 | 
			
		||||
 | 
			
		||||
Парадокс в том, что мы _думаем_ что загадываем числа случайно. На самом деле все не так,
 | 
			
		||||
и последовательность загаданных чисел не случайна.
 | 
			
		||||
 | 
			
		||||
Исходного кода оригинальной гадалки в открытом доступе нет, есть только [описание алгоритма](algo),
 | 
			
		||||
по которому я сделал свою реализацию на TypeScrypt.
 | 
			
		||||
 | 
			
		||||
## Демоверсия
 | 
			
		||||
 | 
			
		||||
Попробуйте набрать 50 очков и выиграть. Чтобы выбирать вариант с клавиатуры,
 | 
			
		||||
кликните внутри серой рамки, а потом пользуйтесь клавишами "1" - нечет или "2" - чет.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<div id="app"></div>
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Как Это работает
 | 
			
		||||
 | 
			
		||||
Математически алгоритм на [странице](algo) сайта ltwood.
 | 
			
		||||
Я рассмотрю простой пример, чтобы показать принцип.
 | 
			
		||||
 | 
			
		||||
В основе алгоритма находится популяция "демонов" - автоматов, которые на основании ходов
 | 
			
		||||
игрока и предсказанных значениях выдают новое предсказание. Демонами управляет
 | 
			
		||||
супервайзер. Задача супервайзера в том, чтобы опросить всех демонов, выбрать ответ
 | 
			
		||||
от одного их них, а после получения ответа игрока пометить тех, кто выдал правильный ответ.
 | 
			
		||||
 | 
			
		||||
Алгоритм состоит из двух шагов:
 | 
			
		||||
 | 
			
		||||
-   предсказать следующих ход игрока;
 | 
			
		||||
-   учесть реальный ход игрока, добавив веса тому демону, который предугадал ход.
 | 
			
		||||
 | 
			
		||||
Рассмотрим работу на примере одного демона.
 | 
			
		||||
 | 
			
		||||
Пусть у нас есть демон, который смотрит на последний хода игрока
 | 
			
		||||
и на свое последнее предсказание.
 | 
			
		||||
 | 
			
		||||
Строим два вектора:
 | 
			
		||||
 | 
			
		||||
-   `[<1 ход демона>, <1 ход игрока>, 0]`
 | 
			
		||||
-   `[<1 ход демона>, <1 ход игрока>, 1]`
 | 
			
		||||
 | 
			
		||||
В самом начале, когда у демона нет никакой информации о ходах игрока, эти векторы
 | 
			
		||||
будут выглядеть как `[0]` и `[1]`. Но с накоплением данных, они всегда будут каждый
 | 
			
		||||
по 5 элементов.
 | 
			
		||||
 | 
			
		||||
После чего смотрим, который из таких наборов в прошлом приносил победу чаще,
 | 
			
		||||
и соответственно выбираем или вариант с 0, или с 1.
 | 
			
		||||
 | 
			
		||||
После получения действительного хода игрока, мы увеличиваем вес того набора,
 | 
			
		||||
который оказался верным. И далее снова предсказываем ход.
 | 
			
		||||
 | 
			
		||||
Теперь с числами.
 | 
			
		||||
 | 
			
		||||
#### Ход 1
 | 
			
		||||
 | 
			
		||||
У демона нет информации, наборы `[0]` и `[1]` равнозначны, выбираем `[0]`,
 | 
			
		||||
а значит предсказываем ход игрока 0.
 | 
			
		||||
 | 
			
		||||
Игрок загадывал 1. Обновляем веса:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
[0] = 0
 | 
			
		||||
[1] = 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Ход 2
 | 
			
		||||
 | 
			
		||||
Строим векторы на основе последних ходов:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
0: [0, 1, 0]
 | 
			
		||||
1: [0, 1, 1]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Для этих векторов тоже еще нет весов, так что снова выбираем первый, предсказываем 0.
 | 
			
		||||
 | 
			
		||||
Игрок снова выбрал 1. Обновляем веса (помним, что еще были прошлые вектора из одного элемента):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
[0] = 0
 | 
			
		||||
[1] = 1
 | 
			
		||||
[0, 1, 0] = 0
 | 
			
		||||
[0, 1, 1] = 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Ход 3
 | 
			
		||||
 | 
			
		||||
Картина такая же, как на втором ходу, но отличие в том, что у нас есть веса с прошлого хода:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
0: [0, 1, 0] - 0
 | 
			
		||||
1: [0, 1, 1] - 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Выбираем вариант 1, игрок снова выбирает 1. Предсказание удалось!
 | 
			
		||||
 | 
			
		||||
## Расширение алгоритма
 | 
			
		||||
 | 
			
		||||
Это был самый элементарный вариант. Понятно, что на таком далеко не уедешь,
 | 
			
		||||
и никого не обыграешь. Чтобы хорошо предугадывать ходы игроков, используется
 | 
			
		||||
несколько демонов с разной величиной просматриваемой истории. Следит за ними
 | 
			
		||||
"супервайзер", который ведет для каждого демона рейтинг. На основе этого рейтинга
 | 
			
		||||
выбираются ответы тех демонов, которые были наиболее успешны в своих предсказаниях.
 | 
			
		||||
 | 
			
		||||
## Ссылки
 | 
			
		||||
 | 
			
		||||
-   [Код гадалки](repo)
 | 
			
		||||
-   [Описание алгоритма](algo)
 | 
			
		||||
-   [Описание игры у ltwood](game)
 | 
			
		||||
 | 
			
		||||
[ltwood]: https://sites.google.com/site/ltwood/
 | 
			
		||||
[game]: https://sites.google.com/site/ltwood/projects/heshby
 | 
			
		||||
[algo]: https://sites.google.com/site/ltwood/projects/heshby/algorithm
 | 
			
		||||
[repo]: https://github.com/anwinged/predictor
 | 
			
		||||
 
 | 
			
		||||
@@ -1,145 +0,0 @@
 | 
			
		||||
const DEFAULT_EPSILON = 0.01;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {Number[]} steps
 | 
			
		||||
 *
 | 
			
		||||
 * @returns {String}
 | 
			
		||||
 */
 | 
			
		||||
function create_key(steps) {
 | 
			
		||||
    return steps.join(':');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Daemon {
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Number}
 | 
			
		||||
     */
 | 
			
		||||
    base;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Number}
 | 
			
		||||
     */
 | 
			
		||||
    humanCount;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Number}
 | 
			
		||||
     */
 | 
			
		||||
    robotCount;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Number}
 | 
			
		||||
     */
 | 
			
		||||
    epsilon;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @type {Object}
 | 
			
		||||
     */
 | 
			
		||||
    weights = {};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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 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;
 | 
			
		||||
@@ -1,47 +0,0 @@
 | 
			
		||||
import Move from './Move';
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
        return [].concat(
 | 
			
		||||
            robotMoves.slice(-robotCount),
 | 
			
		||||
            humanMoves.slice(-humanCount)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @returns {Number}
 | 
			
		||||
     */
 | 
			
		||||
    get length() {
 | 
			
		||||
        return this.moves.length;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Journal;
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Represents one game move.
 | 
			
		||||
 */
 | 
			
		||||
class Move {
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Number} human
 | 
			
		||||
     * @param {Number} robot
 | 
			
		||||
     */
 | 
			
		||||
    constructor(human, robot) {
 | 
			
		||||
        this.human = human;
 | 
			
		||||
        this.robot = robot;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Move;
 | 
			
		||||
@@ -1,90 +0,0 @@
 | 
			
		||||
import Daemon from './Daemon';
 | 
			
		||||
import Journal from './Journal';
 | 
			
		||||
import Supervisor from './Supervisor';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_CONFIG = {
 | 
			
		||||
    base: 2,
 | 
			
		||||
    supervisor_epsilon: 0.01,
 | 
			
		||||
    daemons: [
 | 
			
		||||
        { human: 2, robot: 2, epsilon: 0.01 },
 | 
			
		||||
        { human: 3, robot: 3, epsilon: 0.01 },
 | 
			
		||||
        { human: 4, robot: 4, epsilon: 0.01 },
 | 
			
		||||
        { human: 5, robot: 5, epsilon: 0.01 },
 | 
			
		||||
        { human: 6, robot: 6, epsilon: 0.01 },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 = this._createDaemons(config.daemons);
 | 
			
		||||
        this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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);
 | 
			
		||||
        this.journal.makeMove(value, prediction);
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
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;
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Predictor from './Predictor';
 | 
			
		||||
import Predictor from '@anwinged/predictor';
 | 
			
		||||
 | 
			
		||||
const MAX_SCORE = 50;
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +89,7 @@ export default {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* const prediction = */ this.predictor.pass(value);
 | 
			
		||||
            /* const prediction = */ this.predictor.pass(+value);
 | 
			
		||||
        },
 | 
			
		||||
        restart() {
 | 
			
		||||
            this.predictor = make_predictor();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user