Add base and format code
This commit is contained in:
parent
065e325b26
commit
cb96183a7d
File diff suppressed because one or more lines are too long
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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() {
|
export default Move;
|
||||||
return this._human;
|
|
||||||
}
|
|
||||||
|
|
||||||
get robot() {
|
|
||||||
return this._robot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user