Compare commits

..

No commits in common. "master" and "0.1.2" have entirely different histories.

16 changed files with 235 additions and 368 deletions

View File

@ -8,9 +8,7 @@ jobs:
- checkout - checkout
- run: npm ci - run: npm ci
- run: npm run format-check - run: npm run format-check
- run: npm run test:junit-report - run: npm run test
- store_test_results:
path: test-results
build_and_publish: build_and_publish:
docker: docker:
@ -21,17 +19,13 @@ jobs:
- run: npm run build - run: npm run build
- run: npm run build:dev - run: npm run build:dev
- run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
- run: npm version --no-git-tag-version --force "${CIRCLE_TAG}" - run: npm publish --tag "${CIRCLE_TAG}" --access public
- run: npm publish --access public
workflows: workflows:
version: 2 version: 2
test_and_publish: test_and_publish:
jobs: jobs:
- test: - test
filters:
tags:
only: /.*/
- build_and_publish: - build_and_publish:
requires: requires:
- test - test

1
.gitignore vendored
View File

@ -3,6 +3,5 @@
built/ built/
coverage/ coverage/
node_modules/ node_modules/
test-results/
var/ var/
.npmrc .npmrc

View File

@ -1,18 +1,16 @@
# Электронная гадалка # Электронная гадалка
[![CircleCI](https://circleci.com/gh/anwinged/predictor/tree/master.svg?style=svg)](https://circleci.com/gh/anwinged/predictor/tree/master)
[Демоверсия][demo] [Демоверсия][demo]
Алгоритм, который противостоит человеку, и на основе ходов пытается предсказать Алгоритм, который противостоит человеку, и на основе ходов пытается предсказать
следующих ход человека. следующих ход человека.
Игрок загадывает один из двух вариантов, а робот пытается его угадать. Игрок загаывает один из двух вариантов, а робот пытается его угадать.
Если программе удалось угадать, то игрок теряет очко. Если программе удалось угадать, то игрок теряет очко.
Если программа не смогла предсказать выбор человека, то игрок зарабатывает очко. Если же программа не смогла предсказать выбор человека, то игрок зарабатывает очко.
Алгоритм реализован на основе [описания][algorithm]. В процессе реализации алгоритм слегка изменился. Алгоритм реализован на основе [описания][algorithm]. В процессе реализации алгоритм слегка изменился.
В отличие от описания, здесь можно дополнительно указать количество вариантов. В отличие от описания, здесь можно допольнительно указать количество вариантов.
С двумя вариантами будет игра "Чет - нечет", а с тремя - "Камень, ножницы, бумага". С двумя вариантами будет игра "Чет - нечет", а с тремя - "Камень, ножницы, бумага".
Интересно то, что программу сложно обыграть. Игрок пытается обставить робота, но все равно Интересно то, что программу сложно обыграть. Игрок пытается обставить робота, но все равно
@ -36,7 +34,7 @@ const prediction = predictor.pass(1);
// Получение текущего счета // Получение текущего счета
const score = predictor.score; const score = predictor.score;
// Получение количества сделанных ходов // Получение количества сделаннх ходов
const sc = predictor.stepCount(); const sc = predictor.stepCount();
``` ```
@ -55,7 +53,7 @@ const sc = predictor.stepCount();
tools/build-docker tools/build-docker
tools/npm run build tools/npm run build
## Тестирование ## Тестиирование
tools/npm run test tools/npm run test

53
package-lock.json generated
View File

@ -1119,12 +1119,6 @@
"supports-color": "^5.3.0" "supports-color": "^5.3.0"
} }
}, },
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
"dev": true
},
"check-error": { "check-error": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@ -1959,12 +1953,6 @@
"which": "^1.2.9" "which": "^1.2.9"
} }
}, },
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=",
"dev": true
},
"crypto-browserify": { "crypto-browserify": {
"version": "3.12.0", "version": "3.12.0",
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
@ -3588,17 +3576,6 @@
"object-visit": "^1.0.0" "object-visit": "^1.0.0"
} }
}, },
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
"dev": true,
"requires": {
"charenc": "~0.0.1",
"crypt": "~0.0.1",
"is-buffer": "~1.1.1"
}
},
"md5.js": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@ -3940,30 +3917,6 @@
} }
} }
}, },
"mocha-junit-reporter": {
"version": "1.23.3",
"resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz",
"integrity": "sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA==",
"dev": true,
"requires": {
"debug": "^2.2.0",
"md5": "^2.1.0",
"mkdirp": "~0.5.1",
"strip-ansi": "^4.0.0",
"xml": "^1.0.0"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -6242,12 +6195,6 @@
"typedarray-to-buffer": "^3.1.5" "typedarray-to-buffer": "^3.1.5"
} }
}, },
"xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
"dev": true
},
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@ -4,14 +4,13 @@
"description": "", "description": "",
"author": "Anton Vakhrushev", "author": "Anton Vakhrushev",
"license": "MIT", "license": "MIT",
"main": "dist/predictor.js", "main": "built/predictor.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/anwinged/predictor.git" "url": "https://github.com/anwinged/predictor.git"
}, },
"scripts": { "scripts": {
"test": "mocha", "test": "mocha",
"test:junit-report": "mocha --reporter mocha-junit-reporter --reporter-options mochaFile=./test-results/results.xml",
"coverage": "nyc mocha", "coverage": "nyc mocha",
"build:dev": "webpack", "build:dev": "webpack",
"build": "webpack --env.production", "build": "webpack --env.production",
@ -26,7 +25,6 @@
"@types/node": "^13.9.2", "@types/node": "^13.9.2",
"chai": "^4.2.0", "chai": "^4.2.0",
"mocha": "^7.1.1", "mocha": "^7.1.1",
"mocha-junit-reporter": "^1.23.3",
"nyc": "^15.0.0", "nyc": "^15.0.0",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-loader": "^6.2.1", "ts-loader": "^6.2.1",

View File

@ -1,42 +1,49 @@
import Journal from './Journal'; import Journal from './Journal';
const DEFAULT_EPSILON = 0.01;
function create_key(steps: number[]): string { function create_key(steps: number[]): string {
return steps.join(':'); return steps.join(':');
} }
class Daemon { class Daemon {
static DEFAULT_EPSILON = 0.01; /**
* @type {Number}
*/
base;
private readonly thisId: string; /**
* @type {Number}
*/
humanCount;
private readonly base: number; /**
* @type {Number}
*/
robotCount;
private readonly humanCount: number; /**
* @type {Number}
*/
epsilon;
private readonly robotCount: number; /**
* @type {Object}
private readonly epsilon: number; */
weights = {};
private weights: { [key: string]: number } = {};
constructor( constructor(
id: string,
base: number, base: number,
humanCount: number, humanCount: number,
robotCount: number, robotCount: number,
epsilon: number = Daemon.DEFAULT_EPSILON epsilon: number = DEFAULT_EPSILON
) { ) {
this.thisId = id;
this.base = base; this.base = base;
this.humanCount = humanCount; this.humanCount = humanCount;
this.robotCount = robotCount; this.robotCount = robotCount;
this.epsilon = epsilon; this.epsilon = epsilon;
} }
get id(): string {
return this.thisId;
}
get power(): number { get power(): number {
return this.humanCount + this.robotCount; return this.humanCount + this.robotCount;
} }
@ -55,35 +62,69 @@ class Daemon {
return proposals.indexOf(maxWeight); return proposals.indexOf(maxWeight);
} }
adjust(journal: Journal, humanValue: number): void { /**
* @param {Journal} journal
* @param {Number} humanValue
*/
adjust(journal, humanValue) {
const steps = this._getStepSlice(journal); const steps = this._getStepSlice(journal);
const adjustmentWeight = this._getAdjustmentWeight(journal.length); const adjustmentWeight = this._getAdjustmentWeight(journal.length);
this._adjustWeight([...steps, humanValue], adjustmentWeight); this._adjustWeight([...steps, humanValue], adjustmentWeight);
} }
getWeights() { /**
return { ...this.weights }; * @param {Journal} journal
} *
* @returns {Number[]}
private _getStepSlice(journal: Journal): number[] { */
private _getStepSlice(journal) {
return journal.getLastMovements(this.humanCount, this.robotCount); return journal.getLastMovements(this.humanCount, this.robotCount);
} }
private _getAdjustmentWeight(stepNumber: number): number { /**
* @param {Number} stepNumber
*
* @returns {Number}
*
* @private
*/
private _getAdjustmentWeight(stepNumber) {
return Math.pow(1 + this.epsilon, stepNumber); return Math.pow(1 + this.epsilon, stepNumber);
} }
/**
* @param {Number[]} steps
*
* @returns {Number}
*
* @private
*/
private _getWeight(steps: number[]): number { private _getWeight(steps: number[]): number {
const key = create_key(steps); const key = create_key(steps);
return key in this.weights ? this.weights[key] : 0; const weight = this.weights[key];
return weight as number;
} }
private _setWeight(steps: number[], value: number): void { /**
* @param {Number[]} steps
* @param {Number} value
*
* @returns {Number}
*
* @private
*/
private _setWeight(steps, value) {
const key = create_key(steps); const key = create_key(steps);
this.weights[key] = value; this.weights[key] = value;
} }
private _adjustWeight(steps: number[], weight: number): void { /**
* @param {Number[]} steps
* @param {Number} weight
*
* @private
*/
private _adjustWeight(steps, weight) {
const currentWeight = this._getWeight(steps); const currentWeight = this._getWeight(steps);
const newWeight = currentWeight + weight; const newWeight = currentWeight + weight;
this._setWeight(steps, newWeight); this._setWeight(steps, newWeight);

View File

@ -2,20 +2,13 @@
* Represents one game move. * Represents one game move.
*/ */
class Move { class Move {
private readonly itsHuman: number; public human: number;
private readonly itsRobot: number;
public robot: number;
constructor(human: number, robot: number) { constructor(human: number, robot: number) {
this.itsHuman = human; this.human = human;
this.itsRobot = robot; this.robot = robot;
}
get human() {
return this.itsHuman;
}
get robot() {
return this.itsRobot;
} }
} }

View File

@ -2,35 +2,15 @@ import Daemon from './Daemon';
import Journal from './Journal'; import Journal from './Journal';
import Supervisor from './Supervisor'; import Supervisor from './Supervisor';
interface DaemonConfig { const DEFAULT_CONFIG = {
id?: string;
human: number;
robot: number;
epsilon?: number;
}
interface PredictorConfig {
base: number;
supervisor_epsilon: number;
daemons: DaemonConfig[];
}
interface HistoryRecord {
score: number;
move: [number, number];
rates: { [id: string]: number };
weights: { [id: string]: any };
}
const DEFAULT_CONFIG: PredictorConfig = {
base: 2, base: 2,
supervisor_epsilon: 0.01, supervisor_epsilon: 0.01,
daemons: [ daemons: [
{ human: 2, robot: 2 }, { human: 2, robot: 2, epsilon: 0.01 },
{ human: 3, robot: 3 }, { human: 3, robot: 3, epsilon: 0.01 },
{ human: 4, robot: 4 }, { human: 4, robot: 4, epsilon: 0.01 },
{ human: 5, robot: 5 }, { human: 5, robot: 5, epsilon: 0.01 },
{ human: 6, robot: 6 }, { human: 6, robot: 6, epsilon: 0.01 },
], ],
}; };
@ -38,70 +18,73 @@ export default class Predictor {
/** /**
* @type {Number} * @type {Number}
*/ */
readonly base: number; base;
/** /**
* @type {Number} * @type {Number}
*/ */
score: number; score;
/** /**
* @type {Journal} * @type {Journal}
*/ */
journal: Journal; journal;
/** /**
* @type {Supervisor} * @type {Supervisor}
*/ */
supervisor: Supervisor; supervisor;
history: HistoryRecord[]; /**
* @param {Object} config
constructor(config: PredictorConfig = DEFAULT_CONFIG) { */
constructor(config = DEFAULT_CONFIG) {
this.base = config.base; this.base = config.base;
this.score = 0; this.score = 0;
this.journal = new Journal(); this.journal = new Journal();
const daemons = this._createDaemons(config.daemons); const daemons = this._createDaemons(config.daemons);
this.supervisor = new Supervisor(daemons, config.supervisor_epsilon); this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
this.history = [];
} }
pass(humanValue: number): number { /**
if (humanValue < 0 || humanValue >= this.base) { * @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})`); throw new Error(`Passed value must be in [0, ${this.base})`);
} }
const prediction = this.supervisor.predict(this.journal); const prediction = this.supervisor.predict(this.journal);
this.score += prediction === humanValue ? -1 : 1; this.score += prediction === value ? -1 : 1;
this.supervisor.adjust(this.journal, humanValue); this.supervisor.adjust(this.journal, value);
this.journal.makeMove(humanValue, prediction); this.journal.makeMove(value, prediction);
this.history.push({
score: this.score,
move: [humanValue, prediction],
rates: this.supervisor.rates(),
weights: this.supervisor.weights(),
});
return prediction; return prediction;
} }
showHistory(deep: number): HistoryRecord[] { /**
return this.history.slice(-deep); * @param {Object} daemonConfigs
} *
* @returns {Daemon[]}
private _createDaemons(daemonConfigs: DaemonConfig[]): Daemon[] { *
* @private
*/
_createDaemons(daemonConfigs) {
return daemonConfigs.map(config => { return daemonConfigs.map(config => {
const epsilon = config.epsilon || Daemon.DEFAULT_EPSILON;
return new Daemon( return new Daemon(
config.id ||
`daemon-${config.human}-${config.robot}-${epsilon}`,
this.base, this.base,
config.human, config.human,
config.robot, config.robot,
epsilon config.epsilon || 0.01
); );
}); });
} }
stepCount(): number { /**
* @returns {Number}
*/
stepCount() {
return this.journal.length; return this.journal.length;
} }
} }

View File

@ -1,34 +1,19 @@
import Journal from './Journal'; import Journal from './Journal';
import Daemon from './Daemon'; import Daemon from './Daemon';
interface DaemonRate { const DEFAULT_EPSILON = 0.01;
daemon: Daemon;
rate: number;
}
interface Prediction {
daemonRate: DaemonRate;
rate: number;
power: number;
value: number;
}
class Supervisor { class Supervisor {
static DEFAULT_EPSILON = 0.01; daemons: { daemon: Daemon; rate: number }[] = [];
daemonRates: DaemonRate[] = [];
readonly epsilon: number; readonly epsilon: number;
constructor( constructor(daemons: Daemon[], epsilon: number = DEFAULT_EPSILON) {
daemons: Daemon[],
epsilon: number = Supervisor.DEFAULT_EPSILON
) {
if (!daemons || daemons.length === 0) { if (!daemons || daemons.length === 0) {
throw Error('Empty daemon list'); throw Error('Empty daemon list');
} }
this.daemonRates = daemons.map(daemon => ({ this.daemons = daemons.map(daemon => ({
daemon: daemon, daemon: daemon,
rate: 0, rate: 0,
})); }));
@ -36,6 +21,11 @@ class Supervisor {
this.epsilon = epsilon; this.epsilon = epsilon;
} }
/**
* @param {Journal} journal
*
* @returns {Number}
*/
predict(journal: Journal): number { predict(journal: Journal): number {
const predictions = this._createPredictions(journal); const predictions = this._createPredictions(journal);
const ordered = this._sortPredictions(predictions); const ordered = this._sortPredictions(predictions);
@ -43,45 +33,47 @@ class Supervisor {
return ordered[0].value; return ordered[0].value;
} }
adjust(journal: Journal, humanValue: number) { /**
* @param {Journal} journal
* @param {Number} humanValue
*/
adjust(journal: Journal, humanValue) {
const predictions = this._createPredictions(journal); const predictions = this._createPredictions(journal);
for (const prediction of predictions) { for (const prediction of predictions) {
if (prediction.value === humanValue) { if (prediction.value === humanValue) {
prediction.daemonRate.rate += this._getAdjustmentWeight( prediction.daemon.rate += this._getAdjustmentWeight(
journal.length journal.length
); );
} }
prediction.daemonRate.daemon.adjust(journal, humanValue); prediction.daemon.daemon.adjust(journal, humanValue);
} }
} }
rates() { /**
const result = {}; * @param {Journal} journal
this.daemonRates.forEach(r => { *
result[r.daemon.id] = r.rate; * @returns {Array}
}); *
return result; * @private
} */
private _createPredictions(journal: Journal) {
weights() { return this.daemons.map(daemon => ({
const result = {}; daemon: daemon,
this.daemonRates.forEach(r => { power: daemon.daemon.power,
result[r.daemon.id] = r.daemon.getWeights(); rate: daemon.rate,
}); value: daemon.daemon.predict(journal),
return result;
}
private _createPredictions(journal: Journal): Prediction[] {
return this.daemonRates.map(daemonRate => ({
daemonRate: daemonRate,
power: daemonRate.daemon.power,
rate: daemonRate.rate,
value: daemonRate.daemon.predict(journal),
})); }));
} }
private _sortPredictions(predictions: Prediction[]) { /**
return predictions.sort((result1: Prediction, result2: Prediction) => { * @param {Array} predictions
*
* @returns {Array}
*
* @private
*/
private _sortPredictions(predictions) {
return predictions.sort((result1, result2) => {
const rateDiff = result2.rate - result1.rate; const rateDiff = result2.rate - result1.rate;
if (Math.abs(rateDiff) > 0.000001) { if (Math.abs(rateDiff) > 0.000001) {
return rateDiff; return rateDiff;

View File

@ -3,61 +3,50 @@ import { expect } from 'chai';
import Daemon from '../src/Daemon'; import Daemon from '../src/Daemon';
import Journal from '../src/Journal'; import Journal from '../src/Journal';
import Move from '../src/Move';
describe('Daemon', function() { // it('Get prediction for beginning', function() {
it('Get prediction for beginning', function() { // const m = new Journal();
const daemon = new Daemon('d1', 2, 1, 1); // const d = new Daemon(2, 1, 1);
expect('d1').to.equals(daemon.id); // const predicted = d.predict(m);
// expect(predicted).to.equals(0);
// });
const predicted = daemon.predict(new Journal()); it('Can get power', function() {
expect(0).to.equals(predicted); const d = new Daemon(2, 5, 8);
}); expect(d.power).to.eqls(13);
it('Can get power', function() {
const d = new Daemon('d1', 2, 5, 8);
expect(13).to.eqls(d.power);
});
it('Can adjust', function() {
const journal = new Journal([new Move(0, 0), new Move(0, 0)]);
const daemon = new Daemon('d1', 2, 1, 1, 0.1);
daemon.adjust(journal, 1);
expect({ '0:0:1': 1.1 ** 2 }).to.eqls(daemon.getWeights());
});
it('Daemon 1-1', function() {
const journal = new Journal();
const daemon = new Daemon('d1', 2, 1, 1, 0.1);
const steps = [
{
prediction: 0,
human: 1,
},
{
prediction: 0,
human: 1,
},
{
prediction: 1,
human: 1,
},
{
prediction: 0,
human: 1,
},
{
prediction: 1,
human: 1,
},
];
steps.forEach(step => {
const prediction = daemon.predict(journal);
expect(prediction).to.eqls(step.prediction);
daemon.adjust(journal, step.human);
journal.makeMove(step.human, step.prediction);
});
});
}); });
// it('Daemon 1-1', function() {
// const m = new Journal();
// const d = new Daemon(2, 1, 1);
//
// const steps = [
// {
// prediction: 0,
// human: 1,
// },
// {
// prediction: 0,
// human: 1,
// },
// {
// prediction: 1,
// human: 1,
// },
// {
// prediction: 0,
// human: 1,
// },
// {
// prediction: 1,
// human: 1,
// },
// ];
//
// steps.forEach(step => {
// const prediction = d.predict(m);
// expect(prediction).to.eqls(step.prediction);
// d.adjust(m, step.human);
// m.makeMove(step.human, step.prediction);
// });
// });

View File

@ -4,34 +4,29 @@ import { expect } from 'chai';
import Journal from '../src/Journal'; import Journal from '../src/Journal';
import Move from '../src/Move'; import Move from '../src/Move';
describe('Journal', function() { it('Create with empty constructor', function() {
it('Create with empty constructor', function() { const m = new Journal();
const journal = new Journal(); expect(m.getLastMovements(5, 5)).to.eqls([]);
expect(journal.length).to.equals(0); });
expect(journal.getLastMovements(5, 5)).to.eqls([]);
});
it('Constructor with human steps', function() { it('Constructor with human steps', function() {
const journal = new Journal([new Move(1, 1)]); const m = new Journal([new Move(1, 1)]);
expect(journal.length).equals(1); expect(m.getLastMovements(5, 5)).to.eqls([1, 1]);
expect(journal.getLastMovements(5, 5)).to.eqls([1, 1]); });
});
it('Make steps', function() { it('Make steps', function() {
const journal = new Journal(); const m = new Journal();
journal.makeMove(1, 0); m.makeMove(1, 0);
journal.makeMove(1, 1); expect(m.getLastMovements(5, 5)).to.eqls([0, 1]);
expect(journal.length).to.equals(2); });
expect(journal.getLastMovements(2, 2)).to.eqls([0, 1, 1, 1]);
});
it('Get slice', function() { it('Get slice', function() {
const m = new Journal([ const m = new Journal([
new Move(1, 1), new Move(1, 1),
new Move(0, 1), new Move(0, 1),
new Move(0, 1), new Move(0, 1),
new Move(1, 0), new Move(1, 0),
]); ]);
expect(m.getLastMovements(2, 2)).to.eqls([1, 0, 0, 1]); expect(m.getLastMovements(2, 2)).to.eqls([1, 0, 0, 1]);
});
}); });

View File

@ -1,17 +0,0 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import Predictor from '../src/Predictor';
describe('Predictor', function() {
it('Get prediction for one daemon state', function() {
const predictor = new Predictor({
base: 2,
supervisor_epsilon: 0.01,
daemons: [{ robot: 1, human: 1, epsilon: 0.01 }],
});
const predicted = predictor.pass(1);
expect(predicted).to.equals(0);
expect(predictor.stepCount()).to.equals(1);
});
});

View File

@ -1,35 +0,0 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import Supervisor from '../src/Supervisor';
import Daemon from '../src/Daemon';
import Journal from '../src/Journal';
describe('Supervisor', function() {
it('Get prediction for one daemon state', function() {
const supervisor = new Supervisor(
[new Daemon('d1', 2, 1, 1, 0.1)],
0.1
);
const journal = new Journal();
const human1 = 1;
const predicted1 = supervisor.predict(journal);
expect(0).to.equals(predicted1, 'First prediction for empty journal');
journal.makeMove(human1, predicted1);
supervisor.adjust(journal, human1);
const human2 = 1;
const predicted2 = supervisor.predict(journal);
expect(1).to.equals(
predicted2,
`Second prediction for (${human1}, ${predicted1})`
);
journal.makeMove(human2, predicted1);
supervisor.adjust(journal, human2);
expect({ d1: 1.1 ** 2 }).to.eqls(supervisor.rates());
});
});

View File

@ -4,18 +4,13 @@ set -eu
source .env source .env
TTY=
if [ -t 1 ] ; then
TTY=--tty
fi
docker run \ docker run \
--rm \ --rm \
--interactive \ --interactive \
${TTY} \ --tty \
--init \ --init \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
--volume "$PWD:/app" \ --volume "$PWD:/srv/app" \
--workdir /app \ --workdir /srv/app \
${NODE_IMAGE} \ ${NODE_IMAGE} \
node "$@" node "$@"

View File

@ -9,19 +9,14 @@ CONTAINER_CACHE_DIR=/tmp/.npm
mkdir -p ${HOST_CACHE_DIR} mkdir -p ${HOST_CACHE_DIR}
TTY=
if [ -t 1 ] ; then
TTY=--tty
fi
docker run \ docker run \
--rm \ --rm \
--interactive \ --interactive \
${TTY} \ --tty \
--init \ --init \
--user "$UID:$(id -g)" \ --user "$UID:$(id -g)" \
--volume "$PWD:/app" \ --volume "$PWD:/srv/app" \
--env npm_config_cache="${CONTAINER_CACHE_DIR}" \ --env npm_config_cache="${CONTAINER_CACHE_DIR}" \
--workdir /app \ --workdir /srv/app \
${NODE_IMAGE} \ ${NODE_IMAGE} \
npm "$@" npm "$@"

View File

@ -10,7 +10,7 @@ docker run \
--tty \ --tty \
--init \ --init \
--user "$(id -u):$(id -g)" \ --user "$(id -u):$(id -g)" \
--volume "$PWD:/app" \ --volume "$PWD:/srv/app" \
--workdir /app \ --workdir /srv/app \
${NODE_IMAGE} \ ${NODE_IMAGE} \
./node_modules/.bin/tsc "$@" ./node_modules/.bin/tsc "$@"