Add task scheduler
This commit is contained in:
commit
5c69dd6a90
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.idea/
|
||||
.nyc_output/
|
||||
dist/
|
||||
coverage/
|
||||
node_modules/
|
||||
test-results/
|
||||
var/
|
||||
.npmrc
|
22
Makefile
Normal file
22
Makefile
Normal file
@ -0,0 +1,22 @@
|
||||
all: format build
|
||||
|
||||
restart-server:
|
||||
docker-compose restart
|
||||
|
||||
build:
|
||||
tools/npm run-script build:dev
|
||||
|
||||
build-min:
|
||||
tools/npm run-script build
|
||||
|
||||
format:
|
||||
tools/npm run-script format
|
||||
|
||||
test:
|
||||
tools/npm run test
|
||||
|
||||
coverage:
|
||||
tools/npm run coverage
|
||||
|
||||
publish:
|
||||
tools/npm publish --access public
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Электронная гадалка
|
||||
|
||||
[](https://circleci.com/gh/anwinged/predictor/tree/master)
|
||||
|
||||
[Демоверсия][demo]
|
||||
|
||||
Алгоритм, который противостоит человеку, и на основе ходов пытается предсказать
|
||||
следующих ход человека.
|
||||
|
||||
Игрок загадывает один из двух вариантов, а робот пытается его угадать.
|
||||
Если программе удалось угадать, то игрок теряет очко.
|
||||
Если программа не смогла предсказать выбор человека, то игрок зарабатывает очко.
|
||||
|
||||
Алгоритм реализован на основе [описания][algorithm]. В процессе реализации алгоритм слегка изменился.
|
||||
В отличие от описания, здесь можно дополнительно указать количество вариантов.
|
||||
С двумя вариантами будет игра "Чет - нечет", а с тремя - "Камень, ножницы, бумага".
|
||||
|
||||
Интересно то, что программу сложно обыграть. Игрок пытается обставить робота, но все равно
|
||||
делает свои ходы не случайно. Именно эта "неслучайность" позволяет быстро приспосабливаться и эффективно
|
||||
противостоять игроку.
|
||||
|
||||
Более подробно о гадалке и алгоритме можно прочитать на сайте [ltwood][about].
|
||||
|
||||
## Использование
|
||||
|
||||
```javascript
|
||||
import Predictor from "predictor";
|
||||
|
||||
// Создание гадалки
|
||||
const predictor = new Predictor(config);
|
||||
|
||||
// Передача значения, которое выбрал пользователь,
|
||||
// и получение предсказание для этого значения
|
||||
const prediction = predictor.pass(1);
|
||||
|
||||
// Получение текущего счета
|
||||
const score = predictor.score;
|
||||
|
||||
// Получение количества сделанных ходов
|
||||
const sc = predictor.stepCount();
|
||||
```
|
||||
|
||||
### Конфигурация
|
||||
|
||||
- `config.base` - количество вариантов хода. Число большее 2.
|
||||
Чем больше вариантов, тем больше ходов алгоритм тратит на приспособление к ходам человека.
|
||||
- `config.daemons` - настройка списка демонов.
|
||||
- `config.daemons.human` - количество анализируемых ходов человека,
|
||||
- `config.daemons.robot` - количество анализируемых ходов робота,
|
||||
- `config.daemons.epsilon` - вес вознаграждения правильного ответа.
|
||||
- `config.supervisor_epsilon` - вес вознаграждения демона за правильный ответ.
|
||||
|
||||
## Сборка
|
||||
|
||||
tools/build-docker
|
||||
tools/npm run build
|
||||
|
||||
## Тестирование
|
||||
|
||||
tools/npm run test
|
||||
|
||||
[algorithm]: https://sites.google.com/site/ltwood/projects/heshby/algorithm
|
||||
[demo]: https://vakhrushev.me/articles/2019-05-01-predictor/
|
||||
[about]: https://sites.google.com/site/ltwood/projects/heshby
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
||||
version: '3.6'
|
||||
|
||||
services:
|
||||
server:
|
||||
image: abiosoft/caddy:1.0.3-no-stats
|
||||
volumes:
|
||||
- ./server/Caddyfile:/etc/Caddyfile:ro
|
||||
- ./dist:/srv:ro
|
||||
ports:
|
||||
- 49876:49876
|
||||
restart: unless-stopped
|
6454
package-lock.json
generated
Normal file
6454
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@anwinged/predictor",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"author": "Anton Vakhrushev",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"format": "prettier --tab-width=4 --single-quote --trailing-comma es5 --write '{src,tests}/**/*.{ts,js}'",
|
||||
"format-check": "prettier --tab-width=4 --single-quote --trailing-comma es5 --check '{src,tests}/**/*.{ts,js}'",
|
||||
"format-wp": "prettier --tab-width=4 --single-quote --trailing-comma es5 --write 'webpack.config.js'",
|
||||
"format-md": "prettier --write './*.md'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/node": "^13.9.4",
|
||||
"@types/url-parse": "^1.4.3",
|
||||
"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.2",
|
||||
"ts-node": "^8.8.1",
|
||||
"typescript": "^3.8.3",
|
||||
"url-parse": "^1.4.7",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
}
|
||||
}
|
4
server/Caddyfile
Normal file
4
server/Caddyfile
Normal file
@ -0,0 +1,4 @@
|
||||
0.0.0.0:49876 {
|
||||
root /srv
|
||||
tls off
|
||||
}
|
3
src/Action/Action.ts
Normal file
3
src/Action/Action.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default abstract class Action {
|
||||
abstract async run();
|
||||
}
|
7
src/Action/GoToMainAction.ts
Normal file
7
src/Action/GoToMainAction.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import Action from './Action';
|
||||
|
||||
export default class GoToMainAction extends Action {
|
||||
async run(): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
30
src/ModeDetector.ts
Normal file
30
src/ModeDetector.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import * as URLParse from 'url-parse';
|
||||
|
||||
const SESSION_KEY = 'travian_automation_mode';
|
||||
const SESSION_VALUE = 'command_execution';
|
||||
|
||||
export default class ModeDetector {
|
||||
isAuto(): boolean {
|
||||
return this.isAutoByLocation() || this.isAutoBySession();
|
||||
}
|
||||
|
||||
setAuto(): void {
|
||||
sessionStorage.setItem(SESSION_KEY, SESSION_VALUE);
|
||||
}
|
||||
|
||||
private isAutoByLocation(): boolean {
|
||||
const p = new URLParse(window.location.href, true);
|
||||
console.log('PARSED LOCATION', p);
|
||||
if (p.query['auto-management'] !== undefined) {
|
||||
console.log('AUTO MANAGEMENT ON');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private isAutoBySession(): boolean {
|
||||
const k = sessionStorage.getItem(SESSION_KEY);
|
||||
return k === SESSION_VALUE;
|
||||
}
|
||||
}
|
47
src/Queue.ts
Normal file
47
src/Queue.ts
Normal file
@ -0,0 +1,47 @@
|
||||
export class QueueItem {
|
||||
readonly name: string;
|
||||
|
||||
readonly args;
|
||||
|
||||
constructor(name: string, args: { [name: string]: any }) {
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
export class Queue {
|
||||
private readonly name;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
pop() {
|
||||
const serialized = localStorage.getItem(this.name);
|
||||
if (serialized === null) {
|
||||
return null;
|
||||
}
|
||||
const items = JSON.parse(serialized) as Array<QueueItem>;
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const first = items.shift();
|
||||
|
||||
localStorage.setItem(this.name, JSON.stringify(items));
|
||||
|
||||
if (first === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new QueueItem(first.name || '', first.args || {});
|
||||
}
|
||||
|
||||
push(item: QueueItem): void {
|
||||
const serialized = localStorage.getItem(this.name);
|
||||
const items = serialized
|
||||
? (JSON.parse(serialized) as Array<QueueItem>)
|
||||
: [];
|
||||
const first = items.push(item);
|
||||
localStorage.setItem(this.name, JSON.stringify(items));
|
||||
}
|
||||
}
|
54
src/Scheduler.ts
Normal file
54
src/Scheduler.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { sleep } from './utils';
|
||||
import { Queue } from './Queue';
|
||||
import GoToMainAction from './Action/GoToMainAction';
|
||||
|
||||
const ACTION_QUEUE = 'action_queue';
|
||||
const TASK_QUEUE = 'task_queue';
|
||||
|
||||
export default class Scheduler {
|
||||
taskQueue: Queue;
|
||||
actionQueue: Queue;
|
||||
|
||||
constructor() {
|
||||
this.taskQueue = new Queue(TASK_QUEUE);
|
||||
this.actionQueue = new Queue(ACTION_QUEUE);
|
||||
}
|
||||
|
||||
async run() {
|
||||
while (true) {
|
||||
const action = this.popAction();
|
||||
console.log('POP ACTION', action);
|
||||
if (action !== null) {
|
||||
await action.run();
|
||||
} else {
|
||||
const task = this.popTask();
|
||||
console.log('POP TASK', task);
|
||||
if (task !== null) {
|
||||
// do task
|
||||
}
|
||||
}
|
||||
const waitTime = Math.random() * 5000;
|
||||
console.log('WAIT', waitTime);
|
||||
await sleep(waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
private popTask() {
|
||||
const item = this.taskQueue.pop();
|
||||
if (item === null) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private popAction() {
|
||||
const item = this.actionQueue.pop();
|
||||
if (item === null) {
|
||||
return null;
|
||||
}
|
||||
if (item.name === 'go_to_main') {
|
||||
return new GoToMainAction();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
12
src/index.ts
Normal file
12
src/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import ModeDetector from './ModeDetector';
|
||||
import Scheduler from './Scheduler';
|
||||
|
||||
const md = new ModeDetector();
|
||||
if (md.isAuto()) {
|
||||
md.setAuto();
|
||||
console.log('AUTO MANAGEMENT ON');
|
||||
const scheduler = new Scheduler();
|
||||
scheduler.run();
|
||||
} else {
|
||||
console.log('NORMAL MODE');
|
||||
}
|
5
src/utils.ts
Normal file
5
src/utils.ts
Normal file
@ -0,0 +1,5 @@
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export { sleep };
|
21
tools/node
Executable file
21
tools/node
Executable file
@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
node "$@"
|
27
tools/npm
Executable file
27
tools/npm
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
HOST_CACHE_DIR=$PWD/var/docker-cache/.npm
|
||||
CONTAINER_CACHE_DIR=/tmp/.npm
|
||||
|
||||
mkdir -p ${HOST_CACHE_DIR}
|
||||
|
||||
TTY=
|
||||
if [ -t 1 ] ; then
|
||||
TTY=--tty
|
||||
fi
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
${TTY} \
|
||||
--init \
|
||||
--user "$UID:$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--env npm_config_cache="${CONTAINER_CACHE_DIR}" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
npm "$@"
|
16
tools/tsc
Executable file
16
tools/tsc
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
source .env
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
--interactive \
|
||||
--tty \
|
||||
--init \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--volume "$PWD:/app" \
|
||||
--workdir /app \
|
||||
${NODE_IMAGE} \
|
||||
./node_modules/.bin/tsc "$@"
|
15
travian.user.js
Normal file
15
travian.user.js
Normal file
@ -0,0 +1,15 @@
|
||||
// ==UserScript==
|
||||
// @name New Userscript
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 0.1
|
||||
// @description try to take over the world!
|
||||
// @author You
|
||||
// @match http://*/*
|
||||
// @grant none
|
||||
// ==/UserScript==
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Your code here...
|
||||
})();
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"lib": ["es6", "dom"],
|
||||
"allowJs": true,
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"strictNullChecks": true,
|
||||
"types": ["node", "url-parse", "mocha", "chai"]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
26
webpack.config.js
Normal file
26
webpack.config.js
Normal file
@ -0,0 +1,26 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = (env = {}) => ({
|
||||
mode: env.production ? 'production' : 'development',
|
||||
entry: path.resolve(__dirname, 'src/index.ts'),
|
||||
output: {
|
||||
filename: env.production ? 'travian.min.js' : 'travian.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
library: 'travian',
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user