Automatic action resolving with decorators and magic

This commit is contained in:
Anton Vakhrushev 2020-04-03 11:54:13 +03:00
parent 54fca1f4f4
commit 29485d233d
13 changed files with 92 additions and 111 deletions

View File

@ -1,6 +1,35 @@
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { GameState } from '../Storage/GameState';
import Scheduler from '../Scheduler';
export default abstract class ActionController { const actionMap: { [name: string]: Function | undefined } = {};
abstract async run(args: Args, task: Task);
export function registerAction(constructor: Function) {
console.log('REGISTER ACTION', constructor.name);
actionMap[constructor.name] = constructor;
}
export function createAction(
name: string,
state: GameState,
scheduler: Scheduler
): ActionController | undefined {
const storedFunction = actionMap[name];
if (storedFunction === undefined) {
return undefined;
}
const constructor = (storedFunction as unknown) as typeof ActionController;
return new constructor(state, scheduler);
}
export class ActionController {
protected state: GameState;
protected scheduler: Scheduler;
constructor(state: GameState, scheduler: Scheduler) {
this.state = state;
this.scheduler = scheduler;
}
async run(args: Args, task: Task) {}
} }

View File

@ -1,11 +1,10 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { BuildingQueueFullError } from '../Errors'; import { BuildingQueueFullError } from '../Errors';
export default class CheckBuildingRemainingTimeAction extends ActionController { @registerAction
static NAME = 'check_building_remaining_time'; export class CheckBuildingRemainingTimeAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const timer = jQuery('.buildDuration .timer'); const timer = jQuery('.buildDuration .timer');
if (timer.length === 1) { if (timer.length === 1) {

View File

@ -1,9 +1,9 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
export default class ClickButtonAction extends ActionController { @registerAction
static NAME = 'click_button'; export class ClickButtonAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const el = jQuery(args.selector); const el = jQuery(args.selector);
if (el.length === 1) { if (el.length === 1) {

View File

@ -1,16 +1,9 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import Scheduler from '../Scheduler';
export default class CompleteTaskAction extends ActionController { @registerAction
static NAME = 'complete_task'; export class CompleteTaskAction extends ActionController {
private scheduler: Scheduler;
constructor(scheduler: Scheduler) {
super();
this.scheduler = scheduler;
}
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
this.scheduler.completeTask(task.id); this.scheduler.completeTask(task.id);
} }

View File

@ -1,9 +1,9 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
export default class GoToPageAction extends ActionController { @registerAction
static NAME = 'go_to_page'; export class GoToPageAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
window.location.assign(args.path); window.location.assign(args.path);
} }

View File

@ -1,18 +1,10 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { ActionError } from '../Errors'; import { ActionError } from '../Errors';
import { GameState } from '../Storage/GameState';
export default class GrabHeroAttributesAction extends ActionController {
static NAME = 'grab_hero_attributes';
private state: GameState;
constructor(state: GameState) {
super();
this.state = state;
}
@registerAction
export class GrabHeroAttributesAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const healthElement = jQuery( const healthElement = jQuery(
'#attributes .attribute.health .powervalue .value' '#attributes .attribute.health .powervalue .value'

View File

@ -1,7 +1,6 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { GameState } from '../Storage/GameState';
import { trimPrefix } from '../utils'; import { trimPrefix } from '../utils';
import { AbortTaskError } from '../Errors'; import { AbortTaskError } from '../Errors';
@ -13,15 +12,8 @@ interface Adventure {
level: number; level: number;
} }
export default class SendOnAdventureAction extends ActionController { @registerAction
static NAME = 'send_on_adventure'; export class SendOnAdventureAction extends ActionController {
private state: GameState;
constructor(state: GameState) {
super();
this.state = state;
}
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const adventures = this.findAdventures(); const adventures = this.findAdventures();

View File

@ -1,11 +1,10 @@
import ActionController from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { TryLaterError } from '../Errors'; import { TryLaterError } from '../Errors';
import Scheduler from '../Scheduler';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
export default class UpgradeBuildingAction extends ActionController { @registerAction
static NAME = 'upgrade_building'; export class UpgradeBuildingAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const btn = jQuery( const btn = jQuery(
'.upgradeButtonsContainer .section1 button.green.build' '.upgradeButtonsContainer .section1 button.green.build'

View File

@ -1,25 +1,18 @@
import { markPage, sleepShort, timestamp } from './utils'; import { markPage, sleepShort, timestamp } from './utils';
import UpgradeBuildingTask from './Task/UpgradeBuildingTask'; import UpgradeBuildingTask from './Task/UpgradeBuildingTask';
import UpgradeBuildingAction from './Action/UpgradeBuildingAction';
import { import {
AbortTaskError, AbortTaskError,
BuildingQueueFullError, BuildingQueueFullError,
TryLaterError, TryLaterError,
} from './Errors'; } from './Errors';
import { TaskQueue, TaskList, Task, TaskId } from './Storage/TaskQueue'; import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue';
import ActionQueue from './Storage/ActionQueue'; import ActionQueue from './Storage/ActionQueue';
import { Args, Command } from './Common'; import { Args, Command } from './Common';
import TaskQueueRenderer from './TaskQueueRenderer'; import TaskQueueRenderer from './TaskQueueRenderer';
import ActionController from './Action/ActionController'; import { ActionController, createAction } from './Action/ActionController';
import TaskController from './Task/TaskController'; import TaskController from './Task/TaskController';
import GoToPageAction from './Action/GoToPageAction';
import CheckBuildingRemainingTimeAction from './Action/CheckBuildingRemainingTimeAction';
import SendOnAdventureTask from './Task/SendOnAdventureTask'; import SendOnAdventureTask from './Task/SendOnAdventureTask';
import GrabHeroAttributesAction from './Action/GrabHeroAttributesAction';
import { GameState } from './Storage/GameState'; import { GameState } from './Storage/GameState';
import CompleteTaskAction from './Action/CompleteTaskAction';
import SendOnAdventureAction from './Action/SendOnAdventureAction';
import ClickButtonAction from './Action/ClickButtonAction';
export default class Scheduler { export default class Scheduler {
private readonly version: string; private readonly version: string;
@ -91,7 +84,12 @@ export default class Scheduler {
private async processTaskCommand(task: Task) { private async processTaskCommand(task: Task) {
const taskController = this.createTaskControllerByName(task.cmd.name); const taskController = this.createTaskControllerByName(task.cmd.name);
this.log('PROCESS TASK', task.cmd.name, task, taskController); this.log(
'PROCESS TASK',
taskController?.constructor.name,
task,
taskController
);
if (taskController) { if (taskController) {
taskController.run(task); taskController.run(task);
} }
@ -99,7 +97,11 @@ export default class Scheduler {
private async processActionCommand(cmd: Command, task: Task) { private async processActionCommand(cmd: Command, task: Task) {
const actionController = this.createActionControllerByName(cmd.name); const actionController = this.createActionControllerByName(cmd.name);
this.log('PROCESS ACTION', cmd.name, actionController); this.log(
'PROCESS ACTION',
actionController?.constructor.name,
actionController
);
if (actionController) { if (actionController) {
await this.runAction(actionController, cmd.args, task); await this.runAction(actionController, cmd.args, task);
} }
@ -136,31 +138,14 @@ export default class Scheduler {
} }
private createActionControllerByName( private createActionControllerByName(
actonName: string actionName: string
): ActionController | undefined { ): ActionController | undefined {
if (actonName === GoToPageAction.NAME) { const action = createAction(actionName, this.gameState, this);
return new GoToPageAction(); if (!action) {
this.logError('ACTION NOT FOUND', actionName);
return undefined;
} }
if (actonName === ClickButtonAction.NAME) { return action;
return new ClickButtonAction();
}
if (actonName === CompleteTaskAction.NAME) {
return new CompleteTaskAction(this);
}
if (actonName === UpgradeBuildingAction.NAME) {
return new UpgradeBuildingAction();
}
if (actonName === CheckBuildingRemainingTimeAction.NAME) {
return new CheckBuildingRemainingTimeAction();
}
if (actonName === GrabHeroAttributesAction.NAME) {
return new GrabHeroAttributesAction(this.gameState);
}
if (actonName === SendOnAdventureAction.NAME) {
return new SendOnAdventureAction(this.gameState);
}
this.logError('ACTION NOT FOUND', actonName);
return undefined;
} }
private async runAction(action: ActionController, args: Args, task: Task) { private async runAction(action: ActionController, args: Args, task: Task) {

View File

@ -2,11 +2,11 @@ import Scheduler from '../Scheduler';
import { Args, Command } from '../Common'; import { Args, Command } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import TaskController from './TaskController'; import TaskController from './TaskController';
import GoToPageAction from '../Action/GoToPageAction'; import { GoToPageAction } from '../Action/GoToPageAction';
import GrabHeroAttributesAction from '../Action/GrabHeroAttributesAction'; import { GrabHeroAttributesAction } from '../Action/GrabHeroAttributesAction';
import CompleteTaskAction from '../Action/CompleteTaskAction'; import { CompleteTaskAction } from '../Action/CompleteTaskAction';
import SendOnAdventureAction from '../Action/SendOnAdventureAction'; import { SendOnAdventureAction } from '../Action/SendOnAdventureAction';
import ClickButtonAction from '../Action/ClickButtonAction'; import { ClickButtonAction } from '../Action/ClickButtonAction';
export default class SendOnAdventureTask extends TaskController { export default class SendOnAdventureTask extends TaskController {
static NAME = 'send_on_adventure'; static NAME = 'send_on_adventure';
@ -17,25 +17,21 @@ export default class SendOnAdventureTask extends TaskController {
this.scheduler = scheduler; this.scheduler = scheduler;
} }
name(): string {
return SendOnAdventureTask.NAME;
}
run(task: Task) { run(task: Task) {
const args: Args = { ...task.cmd.args, taskId: task.id }; const args: Args = { ...task.cmd.args, taskId: task.id };
this.scheduler.scheduleActions([ this.scheduler.scheduleActions([
new Command(GoToPageAction.NAME, { ...args, path: 'hero.php' }), new Command(GoToPageAction.name, { ...args, path: 'hero.php' }),
new Command(GrabHeroAttributesAction.NAME, args), new Command(GrabHeroAttributesAction.name, args),
new Command(GoToPageAction.NAME, { new Command(GoToPageAction.name, {
...args, ...args,
path: '/hero.php?t=3', path: '/hero.php?t=3',
}), }),
new Command(SendOnAdventureAction.NAME, args), new Command(SendOnAdventureAction.name, args),
new Command(ClickButtonAction.NAME, { new Command(ClickButtonAction.name, {
...args, ...args,
selector: '.adventureSendButton button', selector: '.adventureSendButton button',
}), }),
new Command(CompleteTaskAction.NAME, args), new Command(CompleteTaskAction.name, args),
]); ]);
} }
} }

View File

@ -1,6 +1,5 @@
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
export default abstract class TaskController { export default abstract class TaskController {
abstract name(): string;
abstract run(task: Task); abstract run(task: Task);
} }

View File

@ -1,11 +1,11 @@
import Scheduler from '../Scheduler'; import Scheduler from '../Scheduler';
import UpgradeBuildingAction from '../Action/UpgradeBuildingAction'; import { UpgradeBuildingAction } from '../Action/UpgradeBuildingAction';
import { Args, Command } from '../Common'; import { Args, Command } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import TaskController from './TaskController'; import TaskController from './TaskController';
import GoToPageAction from '../Action/GoToPageAction'; import { GoToPageAction } from '../Action/GoToPageAction';
import CheckBuildingRemainingTimeAction from '../Action/CheckBuildingRemainingTimeAction'; import { CheckBuildingRemainingTimeAction } from '../Action/CheckBuildingRemainingTimeAction';
import CompleteTaskAction from '../Action/CompleteTaskAction'; import { CompleteTaskAction } from '../Action/CompleteTaskAction';
export default class UpgradeBuildingTask extends TaskController { export default class UpgradeBuildingTask extends TaskController {
static NAME = 'upgrade_building'; static NAME = 'upgrade_building';
@ -16,22 +16,18 @@ export default class UpgradeBuildingTask extends TaskController {
this.scheduler = scheduler; this.scheduler = scheduler;
} }
name(): string {
return UpgradeBuildingTask.NAME;
}
run(task: Task) { run(task: Task) {
console.log('RUN', UpgradeBuildingTask.NAME, 'with', task); console.log('RUN', UpgradeBuildingTask.NAME, 'with', task);
const args: Args = { ...task.cmd.args, taskId: task.id }; const args: Args = { ...task.cmd.args, taskId: task.id };
this.scheduler.scheduleActions([ this.scheduler.scheduleActions([
new Command(GoToPageAction.NAME, { ...args, path: '/dorf1.php' }), new Command(GoToPageAction.name, { ...args, path: '/dorf1.php' }),
new Command(CheckBuildingRemainingTimeAction.NAME, args), new Command(CheckBuildingRemainingTimeAction.name, args),
new Command(GoToPageAction.NAME, { new Command(GoToPageAction.name, {
...args, ...args,
path: '/build.php?id=' + args.id, path: '/build.php?id=' + args.id,
}), }),
new Command(UpgradeBuildingAction.NAME, args), new Command(UpgradeBuildingAction.name, args),
new Command(CompleteTaskAction.NAME, args), new Command(CompleteTaskAction.name, args),
]); ]);
} }
} }

View File

@ -1,6 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"experimentalDecorators": true,
"isolatedModules": true, "isolatedModules": true,
"module": "commonjs", "module": "commonjs",
"outDir": "./dist", "outDir": "./dist",