Rewrite lib with typescript

This commit is contained in:
2020-03-21 17:59:41 +03:00
parent 474dae9748
commit 4bdd38ec15
28 changed files with 2374 additions and 7718 deletions

134
src/Daemon.ts Normal file
View File

@ -0,0 +1,134 @@
import Journal from './Journal';
const DEFAULT_EPSILON = 0.01;
function create_key(steps: number[]): string {
return steps.join(':');
}
class Daemon {
/**
* @type {Number}
*/
base;
/**
* @type {Number}
*/
humanCount;
/**
* @type {Number}
*/
robotCount;
/**
* @type {Number}
*/
epsilon;
/**
* @type {Object}
*/
weights = {};
constructor(
base: number,
humanCount: number,
robotCount: number,
epsilon: number = DEFAULT_EPSILON
) {
this.base = base;
this.humanCount = humanCount;
this.robotCount = robotCount;
this.epsilon = epsilon;
}
get power(): number {
return this.humanCount + this.robotCount;
}
predict(journal: Journal): number {
const steps = this._getStepSlice(journal);
const proposals: number[] = [];
for (let i = 0; i < this.base; ++i) {
const weight = this._getWeight([...steps, i]);
proposals.push(weight);
}
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[]}
*/
private _getStepSlice(journal) {
return journal.getLastMovements(this.humanCount, this.robotCount);
}
/**
* @param {Number} stepNumber
*
* @returns {Number}
*
* @private
*/
private _getAdjustmentWeight(stepNumber) {
return Math.pow(1 + this.epsilon, stepNumber);
}
/**
* @param {Number[]} steps
*
* @returns {Number}
*
* @private
*/
private _getWeight(steps: number[]): number {
const key = create_key(steps);
const weight = this.weights[key];
return weight as number;
}
/**
* @param {Number[]} steps
* @param {Number} value
*
* @returns {Number}
*
* @private
*/
private _setWeight(steps, value) {
const key = create_key(steps);
this.weights[key] = value;
}
/**
* @param {Number[]} steps
* @param {Number} weight
*
* @private
*/
private _adjustWeight(steps, weight) {
const currentWeight = this._getWeight(steps);
const newWeight = currentWeight + weight;
this._setWeight(steps, newWeight);
}
}
export default Daemon;

28
src/Journal.ts Normal file
View File

@ -0,0 +1,28 @@
import Move from './Move';
class Journal {
moves: Move[] = [];
constructor(moves: Move[] = []) {
this.moves = moves;
}
makeMove(human: number, robot: number): void {
this.moves.push(new Move(human, robot));
}
getLastMovements(humanCount: number, robotCount: number): number[] {
const humanMoves = this.moves.map(m => m.human);
const robotMoves = this.moves.map(m => m.robot);
return [
...robotMoves.slice(-robotCount),
...humanMoves.slice(-humanCount),
];
}
get length(): number {
return this.moves.length;
}
}
export default Journal;

15
src/Move.ts Normal file
View File

@ -0,0 +1,15 @@
/**
* Represents one game move.
*/
class Move {
public human: number;
public robot: number;
constructor(human: number, robot: number) {
this.human = human;
this.robot = robot;
}
}
export default Move;

90
src/Predictor.ts Normal file
View File

@ -0,0 +1,90 @@
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;
}
}

90
src/Supervisor.ts Normal file
View File

@ -0,0 +1,90 @@
import Journal from './Journal';
import Daemon from './Daemon';
const DEFAULT_EPSILON = 0.01;
class Supervisor {
daemons: { daemon: Daemon; rate: number }[] = [];
readonly epsilon: number;
constructor(daemons: Daemon[], epsilon: number = 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: Journal): number {
const predictions = this._createPredictions(journal);
const ordered = this._sortPredictions(predictions);
return ordered[0].value;
}
/**
* @param {Journal} journal
* @param {Number} humanValue
*/
adjust(journal: 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
*/
private _createPredictions(journal: 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
*/
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;
});
}
private _getAdjustmentWeight(stepNumber: number): number {
return Math.pow(1 + this.epsilon, stepNumber);
}
}
export default Supervisor;

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
import Predictor from './Predictor';
export default Predictor;