Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
94f6fd3ae9 | |||
49bb3da4bb | |||
9e06a1c630 | |||
eb66b6904d | |||
4a18dc5808 | |||
54a5b0683d | |||
e3ea017315 |
@ -8,7 +8,9 @@ jobs:
|
||||
- checkout
|
||||
- run: npm ci
|
||||
- run: npm run format-check
|
||||
- run: npm run test
|
||||
- run: npm run test:junit-report
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
|
||||
build_and_publish:
|
||||
docker:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,5 +3,6 @@
|
||||
built/
|
||||
coverage/
|
||||
node_modules/
|
||||
test-results/
|
||||
var/
|
||||
.npmrc
|
||||
|
12
README.md
12
README.md
@ -1,16 +1,18 @@
|
||||
# Электронная гадалка
|
||||
|
||||
[](https://circleci.com/gh/anwinged/predictor/tree/master)
|
||||
|
||||
[Демоверсия][demo]
|
||||
|
||||
Алгоритм, который противостоит человеку, и на основе ходов пытается предсказать
|
||||
следующих ход человека.
|
||||
|
||||
Игрок загаывает один из двух вариантов, а робот пытается его угадать.
|
||||
Игрок загадывает один из двух вариантов, а робот пытается его угадать.
|
||||
Если программе удалось угадать, то игрок теряет очко.
|
||||
Если же программа не смогла предсказать выбор человека, то игрок зарабатывает очко.
|
||||
Если программа не смогла предсказать выбор человека, то игрок зарабатывает очко.
|
||||
|
||||
Алгоритм реализован на основе [описания][algorithm]. В процессе реализации алгоритм слегка изменился.
|
||||
В отличие от описания, здесь можно допольнительно указать количество вариантов.
|
||||
В отличие от описания, здесь можно дополнительно указать количество вариантов.
|
||||
С двумя вариантами будет игра "Чет - нечет", а с тремя - "Камень, ножницы, бумага".
|
||||
|
||||
Интересно то, что программу сложно обыграть. Игрок пытается обставить робота, но все равно
|
||||
@ -34,7 +36,7 @@ const prediction = predictor.pass(1);
|
||||
// Получение текущего счета
|
||||
const score = predictor.score;
|
||||
|
||||
// Получение количества сделаннх ходов
|
||||
// Получение количества сделанных ходов
|
||||
const sc = predictor.stepCount();
|
||||
```
|
||||
|
||||
@ -53,7 +55,7 @@ const sc = predictor.stepCount();
|
||||
tools/build-docker
|
||||
tools/npm run build
|
||||
|
||||
## Тестиирование
|
||||
## Тестирование
|
||||
|
||||
tools/npm run test
|
||||
|
||||
|
53
package-lock.json
generated
53
package-lock.json
generated
@ -1119,6 +1119,12 @@
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
@ -1953,6 +1959,12 @@
|
||||
"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": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
|
||||
@ -3576,6 +3588,17 @@
|
||||
"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": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@ -3917,6 +3940,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
|
||||
@ -6195,6 +6242,12 @@
|
||||
"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": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
@ -4,13 +4,14 @@
|
||||
"description": "",
|
||||
"author": "Anton Vakhrushev",
|
||||
"license": "MIT",
|
||||
"main": "built/predictor.js",
|
||||
"main": "dist/predictor.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/anwinged/predictor.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"test:junit-report": "mocha --reporter mocha-junit-reporter --reporter-options mochaFile=./test-results/results.xml",
|
||||
"coverage": "nyc mocha",
|
||||
"build:dev": "webpack",
|
||||
"build": "webpack --env.production",
|
||||
@ -25,6 +26,7 @@
|
||||
"@types/node": "^13.9.2",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^7.1.1",
|
||||
"mocha-junit-reporter": "^1.23.3",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^1.19.1",
|
||||
"ts-loader": "^6.2.1",
|
||||
|
@ -1,49 +1,42 @@
|
||||
import Journal from './Journal';
|
||||
|
||||
const DEFAULT_EPSILON = 0.01;
|
||||
|
||||
function create_key(steps: number[]): string {
|
||||
return steps.join(':');
|
||||
}
|
||||
|
||||
class Daemon {
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
base;
|
||||
static DEFAULT_EPSILON = 0.01;
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
humanCount;
|
||||
private readonly thisId: string;
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
robotCount;
|
||||
private readonly base: number;
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
epsilon;
|
||||
private readonly humanCount: number;
|
||||
|
||||
/**
|
||||
* @type {Object}
|
||||
*/
|
||||
weights = {};
|
||||
private readonly robotCount: number;
|
||||
|
||||
private readonly epsilon: number;
|
||||
|
||||
private weights: { [key: string]: number } = {};
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
base: number,
|
||||
humanCount: number,
|
||||
robotCount: number,
|
||||
epsilon: number = DEFAULT_EPSILON
|
||||
epsilon: number = Daemon.DEFAULT_EPSILON
|
||||
) {
|
||||
this.thisId = id;
|
||||
this.base = base;
|
||||
this.humanCount = humanCount;
|
||||
this.robotCount = robotCount;
|
||||
this.epsilon = epsilon;
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this.thisId;
|
||||
}
|
||||
|
||||
get power(): number {
|
||||
return this.humanCount + this.robotCount;
|
||||
}
|
||||
@ -62,69 +55,35 @@ class Daemon {
|
||||
return proposals.indexOf(maxWeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Journal} journal
|
||||
* @param {Number} humanValue
|
||||
*/
|
||||
adjust(journal, humanValue) {
|
||||
adjust(journal: Journal, humanValue: number): void {
|
||||
const steps = this._getStepSlice(journal);
|
||||
const adjustmentWeight = this._getAdjustmentWeight(journal.length);
|
||||
this._adjustWeight([...steps, humanValue], adjustmentWeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Journal} journal
|
||||
*
|
||||
* @returns {Number[]}
|
||||
*/
|
||||
private _getStepSlice(journal) {
|
||||
getWeights() {
|
||||
return { ...this.weights };
|
||||
}
|
||||
|
||||
private _getStepSlice(journal: Journal): number[] {
|
||||
return journal.getLastMovements(this.humanCount, this.robotCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} stepNumber
|
||||
*
|
||||
* @returns {Number}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _getAdjustmentWeight(stepNumber) {
|
||||
private _getAdjustmentWeight(stepNumber: number): number {
|
||||
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;
|
||||
return key in this.weights ? this.weights[key] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number[]} steps
|
||||
* @param {Number} value
|
||||
*
|
||||
* @returns {Number}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _setWeight(steps, value) {
|
||||
private _setWeight(steps: number[], value: number): void {
|
||||
const key = create_key(steps);
|
||||
this.weights[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number[]} steps
|
||||
* @param {Number} weight
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _adjustWeight(steps, weight) {
|
||||
private _adjustWeight(steps: number[], weight: number): void {
|
||||
const currentWeight = this._getWeight(steps);
|
||||
const newWeight = currentWeight + weight;
|
||||
this._setWeight(steps, newWeight);
|
||||
|
17
src/Move.ts
17
src/Move.ts
@ -2,13 +2,20 @@
|
||||
* Represents one game move.
|
||||
*/
|
||||
class Move {
|
||||
public human: number;
|
||||
|
||||
public robot: number;
|
||||
private readonly itsHuman: number;
|
||||
private readonly itsRobot: number;
|
||||
|
||||
constructor(human: number, robot: number) {
|
||||
this.human = human;
|
||||
this.robot = robot;
|
||||
this.itsHuman = human;
|
||||
this.itsRobot = robot;
|
||||
}
|
||||
|
||||
get human() {
|
||||
return this.itsHuman;
|
||||
}
|
||||
|
||||
get robot() {
|
||||
return this.itsRobot;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,35 @@ import Daemon from './Daemon';
|
||||
import Journal from './Journal';
|
||||
import Supervisor from './Supervisor';
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
interface DaemonConfig {
|
||||
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,
|
||||
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 },
|
||||
{ human: 2, robot: 2 },
|
||||
{ human: 3, robot: 3 },
|
||||
{ human: 4, robot: 4 },
|
||||
{ human: 5, robot: 5 },
|
||||
{ human: 6, robot: 6 },
|
||||
],
|
||||
};
|
||||
|
||||
@ -18,73 +38,70 @@ export default class Predictor {
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
base;
|
||||
readonly base: number;
|
||||
|
||||
/**
|
||||
* @type {Number}
|
||||
*/
|
||||
score;
|
||||
score: number;
|
||||
|
||||
/**
|
||||
* @type {Journal}
|
||||
*/
|
||||
journal;
|
||||
journal: Journal;
|
||||
|
||||
/**
|
||||
* @type {Supervisor}
|
||||
*/
|
||||
supervisor;
|
||||
supervisor: Supervisor;
|
||||
|
||||
/**
|
||||
* @param {Object} config
|
||||
*/
|
||||
constructor(config = DEFAULT_CONFIG) {
|
||||
history: HistoryRecord[];
|
||||
|
||||
constructor(config: PredictorConfig = 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);
|
||||
this.history = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number|String} humanValue
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
pass(humanValue) {
|
||||
const value = parseInt(humanValue, 10);
|
||||
if (value < 0 || value >= this.base) {
|
||||
pass(humanValue: number): number {
|
||||
if (humanValue < 0 || humanValue >= 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);
|
||||
this.score += prediction === humanValue ? -1 : 1;
|
||||
this.supervisor.adjust(this.journal, humanValue);
|
||||
this.journal.makeMove(humanValue, prediction);
|
||||
this.history.push({
|
||||
score: this.score,
|
||||
move: [humanValue, prediction],
|
||||
rates: this.supervisor.rates(),
|
||||
weights: this.supervisor.weights(),
|
||||
});
|
||||
return prediction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} daemonConfigs
|
||||
*
|
||||
* @returns {Daemon[]}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_createDaemons(daemonConfigs) {
|
||||
showHistory(deep: number): HistoryRecord[] {
|
||||
return this.history.slice(-deep);
|
||||
}
|
||||
|
||||
private _createDaemons(daemonConfigs: DaemonConfig[]): Daemon[] {
|
||||
return daemonConfigs.map(config => {
|
||||
const epsilon = config.epsilon || Daemon.DEFAULT_EPSILON;
|
||||
return new Daemon(
|
||||
config.id ||
|
||||
`daemon-${config.human}-${config.robot}-${epsilon}`,
|
||||
this.base,
|
||||
config.human,
|
||||
config.robot,
|
||||
config.epsilon || 0.01
|
||||
epsilon
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Number}
|
||||
*/
|
||||
stepCount() {
|
||||
stepCount(): number {
|
||||
return this.journal.length;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,34 @@
|
||||
import Journal from './Journal';
|
||||
import Daemon from './Daemon';
|
||||
|
||||
const DEFAULT_EPSILON = 0.01;
|
||||
interface DaemonRate {
|
||||
daemon: Daemon;
|
||||
rate: number;
|
||||
}
|
||||
|
||||
interface Prediction {
|
||||
daemonRate: DaemonRate;
|
||||
rate: number;
|
||||
power: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
class Supervisor {
|
||||
daemons: { daemon: Daemon; rate: number }[] = [];
|
||||
static DEFAULT_EPSILON = 0.01;
|
||||
|
||||
daemonRates: DaemonRate[] = [];
|
||||
|
||||
readonly epsilon: number;
|
||||
|
||||
constructor(daemons: Daemon[], epsilon: number = DEFAULT_EPSILON) {
|
||||
constructor(
|
||||
daemons: Daemon[],
|
||||
epsilon: number = Supervisor.DEFAULT_EPSILON
|
||||
) {
|
||||
if (!daemons || daemons.length === 0) {
|
||||
throw Error('Empty daemon list');
|
||||
}
|
||||
|
||||
this.daemons = daemons.map(daemon => ({
|
||||
this.daemonRates = daemons.map(daemon => ({
|
||||
daemon: daemon,
|
||||
rate: 0,
|
||||
}));
|
||||
@ -21,11 +36,6 @@ class Supervisor {
|
||||
this.epsilon = epsilon;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Journal} journal
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
predict(journal: Journal): number {
|
||||
const predictions = this._createPredictions(journal);
|
||||
const ordered = this._sortPredictions(predictions);
|
||||
@ -33,47 +43,45 @@ class Supervisor {
|
||||
return ordered[0].value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Journal} journal
|
||||
* @param {Number} humanValue
|
||||
*/
|
||||
adjust(journal: Journal, humanValue) {
|
||||
adjust(journal: Journal, humanValue: number) {
|
||||
const predictions = this._createPredictions(journal);
|
||||
for (const prediction of predictions) {
|
||||
if (prediction.value === humanValue) {
|
||||
prediction.daemon.rate += this._getAdjustmentWeight(
|
||||
prediction.daemonRate.rate += this._getAdjustmentWeight(
|
||||
journal.length
|
||||
);
|
||||
}
|
||||
prediction.daemon.daemon.adjust(journal, humanValue);
|
||||
prediction.daemonRate.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),
|
||||
rates() {
|
||||
const result = {};
|
||||
this.daemonRates.forEach(r => {
|
||||
result[r.daemon.id] = r.rate;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
weights() {
|
||||
const result = {};
|
||||
this.daemonRates.forEach(r => {
|
||||
result[r.daemon.id] = r.daemon.getWeights();
|
||||
});
|
||||
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),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} predictions
|
||||
*
|
||||
* @returns {Array}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _sortPredictions(predictions) {
|
||||
return predictions.sort((result1, result2) => {
|
||||
private _sortPredictions(predictions: Prediction[]) {
|
||||
return predictions.sort((result1: Prediction, result2: Prediction) => {
|
||||
const rateDiff = result2.rate - result1.rate;
|
||||
if (Math.abs(rateDiff) > 0.000001) {
|
||||
return rateDiff;
|
||||
|
@ -3,50 +3,61 @@ import { expect } from 'chai';
|
||||
|
||||
import Daemon from '../src/Daemon';
|
||||
import Journal from '../src/Journal';
|
||||
import Move from '../src/Move';
|
||||
|
||||
// it('Get prediction for beginning', function() {
|
||||
// const m = new Journal();
|
||||
// const d = new Daemon(2, 1, 1);
|
||||
// const predicted = d.predict(m);
|
||||
// expect(predicted).to.equals(0);
|
||||
// });
|
||||
describe('Daemon', function() {
|
||||
it('Get prediction for beginning', function() {
|
||||
const daemon = new Daemon('d1', 2, 1, 1);
|
||||
expect('d1').to.equals(daemon.id);
|
||||
|
||||
it('Can get power', function() {
|
||||
const d = new Daemon(2, 5, 8);
|
||||
expect(d.power).to.eqls(13);
|
||||
const predicted = daemon.predict(new Journal());
|
||||
expect(0).to.equals(predicted);
|
||||
});
|
||||
|
||||
// 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);
|
||||
// });
|
||||
// });
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,20 +4,25 @@ import { expect } from 'chai';
|
||||
import Journal from '../src/Journal';
|
||||
import Move from '../src/Move';
|
||||
|
||||
describe('Journal', function() {
|
||||
it('Create with empty constructor', function() {
|
||||
const m = new Journal();
|
||||
expect(m.getLastMovements(5, 5)).to.eqls([]);
|
||||
const journal = new Journal();
|
||||
expect(journal.length).to.equals(0);
|
||||
expect(journal.getLastMovements(5, 5)).to.eqls([]);
|
||||
});
|
||||
|
||||
it('Constructor with human steps', function() {
|
||||
const m = new Journal([new Move(1, 1)]);
|
||||
expect(m.getLastMovements(5, 5)).to.eqls([1, 1]);
|
||||
const journal = new Journal([new Move(1, 1)]);
|
||||
expect(journal.length).equals(1);
|
||||
expect(journal.getLastMovements(5, 5)).to.eqls([1, 1]);
|
||||
});
|
||||
|
||||
it('Make steps', function() {
|
||||
const m = new Journal();
|
||||
m.makeMove(1, 0);
|
||||
expect(m.getLastMovements(5, 5)).to.eqls([0, 1]);
|
||||
const journal = new Journal();
|
||||
journal.makeMove(1, 0);
|
||||
journal.makeMove(1, 1);
|
||||
expect(journal.length).to.equals(2);
|
||||
expect(journal.getLastMovements(2, 2)).to.eqls([0, 1, 1, 1]);
|
||||
});
|
||||
|
||||
it('Get slice', function() {
|
||||
@ -27,6 +32,6 @@ it('Get slice', function() {
|
||||
new Move(0, 1),
|
||||
new Move(1, 0),
|
||||
]);
|
||||
|
||||
expect(m.getLastMovements(2, 2)).to.eqls([1, 0, 0, 1]);
|
||||
});
|
||||
});
|
||||
|
17
tests/PredictorTest.ts
Normal file
17
tests/PredictorTest.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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);
|
||||
});
|
||||
});
|
35
tests/SupervisorTest.ts
Normal file
35
tests/SupervisorTest.ts
Normal file
@ -0,0 +1,35 @@
|
||||
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());
|
||||
});
|
||||
});
|
11
tools/node
11
tools/node
@ -4,13 +4,18 @@ set -eu
|
||||
|
||||
source .env
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
--tty \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$PWD:/srv/app" \
|
||||
--workdir /srv/app \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
node "$@"
|
||||
|
11
tools/npm
11
tools/npm
@ -9,14 +9,19 @@ CONTAINER_CACHE_DIR=/tmp/.npm
|
||||
|
||||
mkdir -p ${HOST_CACHE_DIR}
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
--tty \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$UID:$(id -g)" \
|
||||
--volume "$PWD:/srv/app" \
|
||||
--volume "$PWD:/app" \
|
||||
--env npm_config_cache="${CONTAINER_CACHE_DIR}" \
|
||||
--workdir /srv/app \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
npm "$@"
|
||||
|
Loading…
Reference in New Issue
Block a user