Add base and format code

This commit is contained in:
Anton Vakhrushev 2018-06-30 17:21:27 +03:00
parent 065e325b26
commit cb96183a7d
10 changed files with 240 additions and 43 deletions

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,9 @@
"jest": { "jest": {
"collectCoverage": true, "collectCoverage": true,
"collectCoverageFrom": [ "collectCoverageFrom": [
"**/source/*.js" "**/source/**/*.js",
"!**/source/build.js",
"!**/source/index.js"
], ],
"testMatch": [ "testMatch": [
"**/tests/**/*.js" "**/tests/**/*.js"

View File

@ -1,61 +1,145 @@
const DEFAULT_EPSILON = 0.01; const DEFAULT_EPSILON = 0.01;
/**
* @param {Number[]} steps
*
* @returns {String}
*/
function create_key(steps) { function create_key(steps) {
return steps.join(''); return steps.join(':');
} }
export default class Daemon { class Daemon {
/**
* @type {Number}
*/
base;
/**
* @type {Number}
*/
humanCount; humanCount;
/**
* @type {Number}
*/
robotCount; robotCount;
/**
* @type {Number}
*/
epsilon; epsilon;
/**
* @type {Object}
*/
weights = {}; weights = {};
constructor(humanCount, robotCount, epsilon = DEFAULT_EPSILON) { /**
* @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.humanCount = humanCount;
this.robotCount = robotCount; this.robotCount = robotCount;
this.epsilon = epsilon; this.epsilon = epsilon;
} }
/**
* @returns {Number}
*/
get power() { get power() {
return this.humanCount + this.robotCount; return this.humanCount + this.robotCount;
} }
/**
* @param {Journal} journal
*
* @returns {Number}
*/
predict(journal) { predict(journal) {
const steps = this._getStepSlice(journal); const steps = this._getStepSlice(journal);
const w0 = this._getWeight([...steps, 0]);
const w1 = this._getWeight([...steps, 1]);
return w1 > w0 ? 1 : 0; 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) { 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);
} }
/**
* @param {Journal} journal
*
* @returns {Number[]}
*/
_getStepSlice(journal) { _getStepSlice(journal) {
return journal.getLastMovements(this.humanCount, this.robotCount); return journal.getLastMovements(this.humanCount, this.robotCount);
} }
/**
* @param {Number} stepNumber
*
* @returns {Number}
*
* @private
*/
_getAdjustmentWeight(stepNumber) { _getAdjustmentWeight(stepNumber) {
return Math.pow(1 + this.epsilon, stepNumber); return Math.pow(1 + this.epsilon, stepNumber);
} }
/**
* @param {Number[]} steps
*
* @returns {Number}
*
* @private
*/
_getWeight(steps) { _getWeight(steps) {
const key = create_key(steps); const key = create_key(steps);
const weight = this.weights[key]; const weight = this.weights[key];
return weight === undefined ? 0 : weight; return weight === undefined ? 0 : weight;
} }
/**
* @param {Number[]} steps
* @param {Number} value
*
* @returns {Number}
*
* @private
*/
_setWeight(steps, value) { _setWeight(steps, value) {
const key = create_key(steps); const key = create_key(steps);
this.weights[key] = value; this.weights[key] = value;
} }
/**
* @param {Number[]} steps
* @param {Number} weight
*
* @private
*/
_adjustWeight(steps, weight) { _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);
} }
} }
export default Daemon;

View File

@ -1,16 +1,32 @@
import Move from './Move'; import Move from './Move';
export default class Journal { class Journal {
/**
* @type {Move[]}
*/
moves = []; moves = [];
/**
* @param {Move[]} moves
*/
constructor(moves = []) { constructor(moves = []) {
this.moves = moves; this.moves = moves;
} }
/**
* @param {Number} human
* @param {Number} robot
*/
makeMove(human, robot) { makeMove(human, robot) {
this.moves.push(new Move(human, robot)); this.moves.push(new Move(human, robot));
} }
/**
* @param {Number} humanCount
* @param {Number} robotCount
*
* @returns {Number[]}
*/
getLastMovements(humanCount, robotCount) { getLastMovements(humanCount, robotCount) {
const humanMoves = this.moves.map(m => m.human); const humanMoves = this.moves.map(m => m.human);
const robotMoves = this.moves.map(m => m.robot); const robotMoves = this.moves.map(m => m.robot);
@ -20,7 +36,12 @@ export default class Journal {
); );
} }
/**
* @returns {Number}
*/
get length() { get length() {
return this.moves.length; return this.moves.length;
} }
} }
export default Journal;

View File

@ -1,14 +1,15 @@
export default class Move { /**
* Represents one game move.
*/
class Move {
/**
* @param {Number} human
* @param {Number} robot
*/
constructor(human, robot) { constructor(human, robot) {
this._human = human ? 1 : 0; this.human = human;
this._robot = robot ? 1 : 0; this.robot = robot;
}
get human() {
return this._human;
}
get robot() {
return this._robot;
} }
} }
export default Move;

View File

@ -1,8 +1,9 @@
import Daemon from './Daemon';
import Journal from './Journal'; import Journal from './Journal';
import Supervisor from './Supervisor'; import Supervisor from './Supervisor';
import Daemon from './Daemon';
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
base: 2,
supervisor_epsilon: 0.01, supervisor_epsilon: 0.01,
daemons: [ daemons: [
{ human: 2, robot: 2, epsilon: 0.01 }, { human: 2, robot: 2, epsilon: 0.01 },
@ -14,24 +15,47 @@ const DEFAULT_CONFIG = {
}; };
export default class Predictor { export default class Predictor {
/**
* @type {Number}
*/
base;
/**
* @type {Number}
*/
score; score;
/**
* @type {Journal}
*/
journal; journal;
/**
* @type {Supervisor}
*/
supervisor; supervisor;
/**
* @param {Object} config
*/
constructor(config = DEFAULT_CONFIG) { constructor(config = DEFAULT_CONFIG) {
this.base = config.base;
this.score = 0; this.score = 0;
this.journal = new Journal(); this.journal = new Journal();
const daemons = config.daemons.map(daemonConfig => { const daemons = this._createDaemons(config.daemons);
return new Daemon(
daemonConfig.human,
daemonConfig.robot,
daemonConfig.epsilon || 0.01
);
});
this.supervisor = new Supervisor(daemons, config.supervisor_epsilon); this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
} }
pass(value) { /**
* @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); const prediction = this.supervisor.predict(this.journal);
this.score += prediction === value ? -1 : 1; this.score += prediction === value ? -1 : 1;
this.supervisor.adjust(this.journal, value); this.supervisor.adjust(this.journal, value);
@ -39,6 +63,27 @@ export default class Predictor {
return 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() { stepCount() {
return this.journal.length; return this.journal.length;
} }

View File

@ -1,11 +1,22 @@
const DEFAULT_EPSILON = 0.01; const DEFAULT_EPSILON = 0.01;
export default class Supervisor { class Supervisor {
/**
* @type {{daemon: Daemon, rate: Number}[]}
*/
daemons = []; daemons = [];
/**
* @type {Number}
*/
epsilon; epsilon;
/**
* @param {Daemon[]} daemons
* @param {Number} epsilon
*/
constructor(daemons, epsilon = DEFAULT_EPSILON) { constructor(daemons, epsilon = DEFAULT_EPSILON) {
if (!daemons) { if (!daemons || daemons.length === 0) {
throw Error('Empty daemon list'); throw Error('Empty daemon list');
} }
this.daemons = daemons.map(daemon => ({ this.daemons = daemons.map(daemon => ({
@ -15,6 +26,11 @@ export default class Supervisor {
this.epsilon = epsilon; this.epsilon = epsilon;
} }
/**
* @param {Journal} journal
*
* @returns {Number}
*/
predict(journal) { predict(journal) {
const predictions = this._createPredictions(journal); const predictions = this._createPredictions(journal);
const ordered = this._sortPredictions(predictions); const ordered = this._sortPredictions(predictions);
@ -22,6 +38,10 @@ export default class Supervisor {
return ordered[0].value; return ordered[0].value;
} }
/**
* @param {Journal} journal
* @param {Number} humanValue
*/
adjust(journal, humanValue) { adjust(journal, humanValue) {
const predictions = this._createPredictions(journal); const predictions = this._createPredictions(journal);
for (const prediction of predictions) { for (const prediction of predictions) {
@ -34,6 +54,13 @@ export default class Supervisor {
} }
} }
/**
* @param {Journal} journal
*
* @returns {Array}
*
* @private
*/
_createPredictions(journal) { _createPredictions(journal) {
return this.daemons.map(daemon => ({ return this.daemons.map(daemon => ({
daemon: daemon, daemon: daemon,
@ -43,6 +70,13 @@ export default class Supervisor {
})); }));
} }
/**
* @param {Array} predictions
*
* @returns {Array}
*
* @private
*/
_sortPredictions(predictions) { _sortPredictions(predictions) {
return predictions.sort((result1, result2) => { return predictions.sort((result1, result2) => {
const rateDiff = result2.rate - result1.rate; const rateDiff = result2.rate - result1.rate;
@ -53,7 +87,16 @@ export default class Supervisor {
}); });
} }
/**
* @param {Number} stepNumber
*
* @returns {Number}
*
* @private
*/
_getAdjustmentWeight(stepNumber) { _getAdjustmentWeight(stepNumber) {
return Math.pow(1 + this.epsilon, stepNumber); return Math.pow(1 + this.epsilon, stepNumber);
} }
} }
export default Supervisor;

View File

@ -6,6 +6,7 @@ new Vue({
el: '#app', el: '#app',
data: { data: {
predictor: new Predictor({ predictor: new Predictor({
base: 3,
daemons: [ daemons: [
{ human: 3, robot: 3 }, { human: 3, robot: 3 },
{ human: 4, robot: 4 }, { human: 4, robot: 4 },
@ -15,11 +16,11 @@ new Vue({
}, },
methods: { methods: {
click(v) { click(v) {
const value = v ? 1 : 0; const value = parseInt(v, 10);
this.pass(value); this.pass(value);
}, },
press(evt) { press(evt) {
const value = evt.key === '1' ? 0 : 1; const value = parseInt(evt.key, 10) - 1;
this.pass(value); this.pass(value);
}, },
pass(value) { pass(value) {

View File

@ -1,21 +1,21 @@
import Daemon from '../source/Daemon';
import expect from 'expect'; import expect from 'expect';
import History from '../source/Journal'; import Daemon from '../source/Daemon';
import Journal from '../source/Journal';
test('Get prediction for beginning', function() { test('Get prediction for beginning', function() {
const m = new History(); const m = new Journal();
const d = new Daemon(1, 1); const d = new Daemon(2, 1, 1);
expect(d.predict(m)).toEqual(0); expect(d.predict(m)).toEqual(0);
}); });
test('Can get power', function() { test('Can get power', function() {
const d = new Daemon(5, 8); const d = new Daemon(2, 5, 8);
expect(d.power).toEqual(13); expect(d.power).toEqual(13);
}); });
test('Daemon 1-1', function() { test('Daemon 1-1', function() {
const m = new History(); const m = new Journal();
const d = new Daemon(1, 1); const d = new Daemon(2, 1, 1);
const steps = [ const steps = [
{ {
@ -40,11 +40,10 @@ test('Daemon 1-1', function() {
}, },
]; ];
steps.forEach((step, index) => { steps.forEach(step => {
const prediction = d.predict(m); const prediction = d.predict(m);
expect(prediction).toEqual(step.prediction); expect(prediction).toEqual(step.prediction);
d.adjust(m, step.human, index + 1); d.adjust(m, step.human);
m.makeMove(step.human, step.prediction); m.makeMove(step.human, step.prediction);
console.log('Step', index + 1, d);
}); });
}); });

View File

@ -11,6 +11,7 @@
<div class="buttons"> <div class="buttons">
<button value="0" v-on:click="click(0)">0</button> <button value="0" v-on:click="click(0)">0</button>
<button value="1" v-on:click="click(1)">1</button> <button value="1" v-on:click="click(1)">1</button>
<button value="2" v-on:click="click(2)">2</button>
</div> </div>
</main> </main>
<script src="dist/app.js"></script> <script src="dist/app.js"></script>