Add smart resource transfer
This commit is contained in:
parent
301b1a6ca9
commit
faed76df4d
46
src/Action/FindSendResourcesPath.ts
Normal file
46
src/Action/FindSendResourcesPath.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { ActionController, registerAction } from './ActionController';
|
||||
import { FailTaskError, taskError, TryLaterError } from '../Errors';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { Coordinates } from '../Core/Village';
|
||||
import { aroundMinutes, timestamp } from '../utils';
|
||||
import { Args } from '../Queue/Args';
|
||||
import { Task } from '../Queue/TaskProvider';
|
||||
import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage';
|
||||
import { VillageState } from '../VillageState';
|
||||
import { MerchantsInfo } from '../Core/Market';
|
||||
import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles';
|
||||
import { ResourceTransferCalculator, ResourceTransferReport } from '../ResourceTransfer';
|
||||
import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
|
||||
import { path } from '../Helpers/Path';
|
||||
import { MARKET_ID } from '../Core/Buildings';
|
||||
|
||||
@registerAction
|
||||
export class FindSendResourcesPath extends ActionController {
|
||||
async run(args: Args, task: Task): Promise<any> {
|
||||
const reports: Array<ResourceTransferReport> = [];
|
||||
const calculator = new ResourceTransferCalculator(this.villageFactory);
|
||||
|
||||
const villages = this.villageFactory.getAllVillages();
|
||||
for (let fromVillage of villages) {
|
||||
for (let toVillage of villages) {
|
||||
reports.push(calculator.calc(fromVillage.id, toVillage.id));
|
||||
}
|
||||
}
|
||||
|
||||
reports.sort((r1, r2) => r2.score - r1.score);
|
||||
|
||||
const bestReport = reports.shift();
|
||||
|
||||
if (!bestReport) {
|
||||
throw new FailTaskError('No best report for transfer resources');
|
||||
}
|
||||
|
||||
console.log('Best report', bestReport);
|
||||
|
||||
const storage = new ResourceTransferStorage();
|
||||
storage.storeReport(bestReport);
|
||||
|
||||
const marketPath = path('/build.php', { newdid: bestReport.fromVillageId, gid: MARKET_ID, t: 5 });
|
||||
window.location.assign(marketPath);
|
||||
}
|
||||
}
|
@ -1,123 +1,33 @@
|
||||
import { ActionController, registerAction } from './ActionController';
|
||||
import { taskError, TryLaterError } from '../Errors';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { Coordinates } from '../Core/Village';
|
||||
import { aroundMinutes, timestamp } from '../utils';
|
||||
import { Args } from '../Queue/Args';
|
||||
import { Task } from '../Queue/TaskProvider';
|
||||
import { clickSendButton, fillSendResourcesForm, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
|
||||
import { VillageState } from '../VillageState';
|
||||
import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/MarketPage';
|
||||
import { ResourceTransferCalculator } from '../ResourceTransfer';
|
||||
import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
|
||||
import { Resources } from '../Core/Resources';
|
||||
import { AbortTaskError } from '../Errors';
|
||||
|
||||
@registerAction
|
||||
export class SendResourcesAction extends ActionController {
|
||||
async run(args: Args, task: Task): Promise<any> {
|
||||
this.ensureSameVillage(args, task);
|
||||
const storage = new ResourceTransferStorage();
|
||||
const savedReport = storage.getReport();
|
||||
|
||||
const senderVillageId = args.villageId || taskError('No source village id');
|
||||
const targetVillageId = args.targetVillageId || taskError('No target village id');
|
||||
const fromVillage = this.villageFactory.getVillage(savedReport.fromVillageId);
|
||||
const toVillage = this.villageFactory.getVillage(savedReport.toVillageId);
|
||||
|
||||
const coordinates = Coordinates.fromObject(args.coordinates || taskError('No coordinates'));
|
||||
const coordinates = toVillage.crd;
|
||||
|
||||
const senderVillage = this.villageFactory.createState(senderVillageId);
|
||||
const recipientVillage = this.villageFactory.createState(targetVillageId);
|
||||
const calculator = new ResourceTransferCalculator(this.villageFactory);
|
||||
const report = calculator.calc(fromVillage.id, toVillage.id);
|
||||
|
||||
const readyToTransfer = this.getResourcesForTransfer(senderVillage, recipientVillage);
|
||||
console.log('To transfer report', report);
|
||||
|
||||
console.log('To transfer res', readyToTransfer);
|
||||
if (Resources.fromObject(report.resources).empty()) {
|
||||
throw new AbortTaskError('No resources to transfer');
|
||||
}
|
||||
|
||||
// Schedule recurrent task
|
||||
const timeout = senderVillage.settings.sendResourcesTimeout;
|
||||
this.scheduler.scheduleTask(task.name, task.args, timestamp() + aroundMinutes(timeout));
|
||||
|
||||
fillSendResourcesForm(readyToTransfer, coordinates);
|
||||
fillSendResourcesForm(report.resources, coordinates);
|
||||
clickSendButton();
|
||||
}
|
||||
|
||||
private getMerchantsCapacity(timeout: number): number {
|
||||
const merchants = grabMerchantsInfo();
|
||||
const capacity = merchants.available * merchants.carry;
|
||||
if (!capacity) {
|
||||
throw new TryLaterError(aroundMinutes(timeout), 'No merchants');
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
|
||||
private getSenderAvailableResources(senderState: VillageState): Resources {
|
||||
const balance = senderState.required.balance;
|
||||
const free = balance.max(Resources.zero());
|
||||
|
||||
console.table([
|
||||
{ name: 'Sender balance', ...balance },
|
||||
{ name: 'Sender free', ...free },
|
||||
]);
|
||||
|
||||
const amount = free.amount();
|
||||
const threshold = senderState.settings.sendResourcesThreshold;
|
||||
const timeout = senderState.settings.sendResourcesTimeout;
|
||||
|
||||
if (amount < threshold) {
|
||||
throw new TryLaterError(
|
||||
aroundMinutes(timeout),
|
||||
`No free resources (amount ${amount} < threshold ${threshold})`
|
||||
);
|
||||
}
|
||||
|
||||
return free;
|
||||
}
|
||||
|
||||
private getRecipientRequirements(recipientState: VillageState, timeout: number): Resources {
|
||||
const maxPossibleToStore = recipientState.storage.capacity.sub(recipientState.performance);
|
||||
const currentResources = recipientState.resources;
|
||||
const incomingResources = recipientState.incomingResources;
|
||||
const requirementResources = recipientState.required.resources;
|
||||
const missingResources = requirementResources
|
||||
.min(maxPossibleToStore)
|
||||
.sub(incomingResources)
|
||||
.sub(currentResources)
|
||||
.max(Resources.zero());
|
||||
|
||||
console.table([
|
||||
{ name: 'Recipient max possible', ...maxPossibleToStore },
|
||||
{ name: 'Recipient resources', ...currentResources },
|
||||
{ name: 'Recipient incoming', ...incomingResources },
|
||||
{ name: 'Recipient requirements', ...requirementResources },
|
||||
{ name: 'Recipient missing', ...missingResources },
|
||||
]);
|
||||
|
||||
if (missingResources.empty()) {
|
||||
throw new TryLaterError(aroundMinutes(timeout), 'No missing resources');
|
||||
}
|
||||
|
||||
return missingResources;
|
||||
}
|
||||
|
||||
private getResourcesForTransfer(senderState: VillageState, recipientState: VillageState): Resources {
|
||||
const multiplier = senderState.settings.sendResourcesMultiplier;
|
||||
const timeout = senderState.settings.sendResourcesTimeout;
|
||||
const senderReadyResources = this.getSenderAvailableResources(senderState).downTo(multiplier);
|
||||
const recipientNeedResources = this.getRecipientRequirements(recipientState, timeout).upTo(multiplier);
|
||||
const contractResources = senderReadyResources.min(recipientNeedResources);
|
||||
const merchantsCapacity = this.getMerchantsCapacity(timeout);
|
||||
|
||||
let readyToTransfer = contractResources;
|
||||
if (contractResources.amount() > merchantsCapacity) {
|
||||
const merchantScale = merchantsCapacity / contractResources.amount();
|
||||
readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier);
|
||||
}
|
||||
|
||||
if (readyToTransfer.empty()) {
|
||||
throw new TryLaterError(aroundMinutes(timeout), 'Not enough resources to transfer');
|
||||
}
|
||||
|
||||
console.log('Merchants capacity', merchantsCapacity);
|
||||
|
||||
console.table([
|
||||
{ name: 'Sender', ...senderReadyResources },
|
||||
{ name: 'Recipient', ...recipientNeedResources },
|
||||
{ name: 'Prepared', ...contractResources },
|
||||
{ name: 'Ready to transfer', ...readyToTransfer },
|
||||
]);
|
||||
|
||||
return readyToTransfer;
|
||||
}
|
||||
}
|
||||
|
@ -8,3 +8,8 @@ export class IncomingMerchant {
|
||||
this.ts = ts;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MerchantsInfo {
|
||||
available: number;
|
||||
carry: number;
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import { ExecutionStorage } from './Storage/ExecutionStorage';
|
||||
import { Action } from './Queue/ActionQueue';
|
||||
import { Task } from './Queue/TaskProvider';
|
||||
import { createTaskHandler } from './Task/TaskMap';
|
||||
import { VillageStateFactory } from './VillageState';
|
||||
import { VillageFactory } from './VillageFactory';
|
||||
|
||||
export interface ExecutionSettings {
|
||||
@ -79,8 +78,6 @@ export class Executor {
|
||||
}
|
||||
|
||||
private async doTaskProcessingStep() {
|
||||
this.runGrabbers();
|
||||
|
||||
const currentTs = timestamp();
|
||||
const { task, action } = this.scheduler.nextTask(currentTs);
|
||||
|
||||
@ -92,6 +89,8 @@ export class Executor {
|
||||
|
||||
this.logger.info('CURRENT JOB', 'TASK', task, 'ACTION', action);
|
||||
|
||||
this.runGrabbers();
|
||||
|
||||
try {
|
||||
if (task && action) {
|
||||
return await this.processActionCommand(action, task);
|
||||
@ -117,7 +116,7 @@ export class Executor {
|
||||
}
|
||||
|
||||
private async processTaskCommand(task: Task) {
|
||||
const taskHandler = createTaskHandler(task.name, this.scheduler);
|
||||
const taskHandler = createTaskHandler(task.name, this.scheduler, this.villageFactory);
|
||||
this.logger.info('Process task', task.name, task, taskHandler);
|
||||
if (taskHandler) {
|
||||
await taskHandler.run(task);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Grabber } from './Grabber';
|
||||
import { isMarketSendResourcesPage } from '../Page/PageDetectors';
|
||||
import { grabIncomingMerchants } from '../Page/BuildingPage/MarketPage';
|
||||
import { grabIncomingMerchants, grabMerchantsInfo } from '../Page/BuildingPage/MarketPage';
|
||||
|
||||
export class MarketPageGrabber extends Grabber {
|
||||
grab(): void {
|
||||
@ -9,5 +9,6 @@ export class MarketPageGrabber extends Grabber {
|
||||
}
|
||||
|
||||
this.storage.storeIncomingMerchants(grabIncomingMerchants());
|
||||
this.storage.storeMerchantsInfo(grabMerchantsInfo());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getNumber, uniqId } from '../../utils';
|
||||
import { Resources } from '../../Core/Resources';
|
||||
import { Resources, ResourcesInterface } from '../../Core/Resources';
|
||||
import { Coordinates } from '../../Core/Village';
|
||||
import { IncomingMerchant } from '../../Core/Market';
|
||||
import { IncomingMerchant, MerchantsInfo } from '../../Core/Market';
|
||||
import { grabResourcesFromList } from './BuildingPage';
|
||||
|
||||
interface SendResourcesClickHandler {
|
||||
@ -31,13 +31,13 @@ export function createSendResourcesButton(onClickHandler: SendResourcesClickHand
|
||||
jQuery(`#${id}`).on('click', createHandler());
|
||||
}
|
||||
|
||||
export function grabMerchantsInfo() {
|
||||
export function grabMerchantsInfo(): MerchantsInfo {
|
||||
const available = getNumber(jQuery('.merchantsAvailable').text());
|
||||
const carry = getNumber(jQuery('.carry b').text());
|
||||
return { available, carry };
|
||||
}
|
||||
|
||||
export function fillSendResourcesForm(resources: Resources, crd: Coordinates) {
|
||||
export function fillSendResourcesForm(resources: ResourcesInterface, crd: Coordinates) {
|
||||
const sendSelect = jQuery('#send_select');
|
||||
sendSelect.find('#r1').val(resources.lumber);
|
||||
sendSelect.find('#r2').val(resources.clay);
|
||||
|
49
src/ResourceTransfer.ts
Normal file
49
src/ResourceTransfer.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { VillageFactory } from './VillageFactory';
|
||||
import { ResourcesInterface } from './Core/Resources';
|
||||
|
||||
export interface ResourceTransferReport {
|
||||
fromVillageId: number;
|
||||
toVillageId: number;
|
||||
resources: ResourcesInterface;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export class ResourceTransferCalculator {
|
||||
private factory: VillageFactory;
|
||||
constructor(factory: VillageFactory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
calc(fromVillageId: number, toVillageId: number): ResourceTransferReport {
|
||||
const senderState = this.factory.createState(fromVillageId);
|
||||
const senderController = this.factory.createController(fromVillageId);
|
||||
const senderStorage = this.factory.createStorage(fromVillageId);
|
||||
|
||||
const recipientController = this.factory.createController(toVillageId);
|
||||
|
||||
const multiplier = senderState.settings.sendResourcesMultiplier;
|
||||
const senderReadyResources = senderController.getAvailableForSendResources().downTo(multiplier);
|
||||
const recipientNeedResources = recipientController.getRequiredResources().upTo(multiplier);
|
||||
const contractResources = senderReadyResources.min(recipientNeedResources);
|
||||
|
||||
const merchantsInfo = senderStorage.getMerchantsInfo();
|
||||
const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry;
|
||||
|
||||
let readyToTransfer = contractResources;
|
||||
if (contractResources.amount() && contractResources.amount() > merchantsCapacity) {
|
||||
const merchantScale = merchantsCapacity / contractResources.amount();
|
||||
readyToTransfer = contractResources.scale(merchantScale).downTo(multiplier);
|
||||
}
|
||||
|
||||
console.log('Merchants capacity', merchantsCapacity);
|
||||
|
||||
console.table([
|
||||
{ name: 'Sender', ...senderReadyResources },
|
||||
{ name: 'Recipient', ...recipientNeedResources },
|
||||
{ name: 'Prepared', ...contractResources },
|
||||
{ name: 'Ready to transfer', ...readyToTransfer },
|
||||
]);
|
||||
|
||||
return { fromVillageId, toVillageId, resources: readyToTransfer, score: readyToTransfer.amount() };
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { timestamp } from './utils';
|
||||
import { around, timestamp } from './utils';
|
||||
import { TaskQueue } from './Queue/TaskQueue';
|
||||
import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
|
||||
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
|
||||
@ -22,11 +22,11 @@ export interface NextExecution {
|
||||
}
|
||||
|
||||
export class Scheduler {
|
||||
private taskQueue: TaskQueue;
|
||||
private actionQueue: ActionQueue;
|
||||
private villageRepository: VillageRepositoryInterface;
|
||||
private villageControllerFactory: VillageFactory;
|
||||
private logger: Logger;
|
||||
private readonly taskQueue: TaskQueue;
|
||||
private readonly actionQueue: ActionQueue;
|
||||
private readonly villageRepository: VillageRepositoryInterface;
|
||||
private readonly villageControllerFactory: VillageFactory;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(
|
||||
taskQueue: TaskQueue,
|
||||
@ -51,14 +51,16 @@ export class Scheduler {
|
||||
}
|
||||
|
||||
this.createUniqTaskTimer(5 * 60, GrabVillageState.name);
|
||||
this.createUniqTaskTimer(10 * 60, SendResourcesTask.name);
|
||||
this.createUniqTaskTimer(10 * 60, BalanceHeroResourcesTask.name);
|
||||
this.createUniqTaskTimer(20 * 60, UpdateResourceContracts.name);
|
||||
this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name);
|
||||
// this.createUniqTaskTimer(60 * 60, SendOnAdventureTask.name);
|
||||
}
|
||||
|
||||
private createUniqTaskTimer(seconds: number, name: string, args: Args = {}) {
|
||||
this.scheduleUniqTask(name, args, timestamp() + seconds - 10);
|
||||
setInterval(() => this.scheduleUniqTask(name, args, timestamp()), seconds * 1000);
|
||||
const intervalTime = around(seconds, 0.2) * 1000;
|
||||
setInterval(() => this.scheduleUniqTask(name, args, timestamp()), intervalTime);
|
||||
}
|
||||
|
||||
getTaskItems(): ImmutableTaskList {
|
||||
|
@ -13,20 +13,20 @@ export interface StatisticsStorageInterface {
|
||||
}
|
||||
|
||||
export class Statistics {
|
||||
private state: StatisticsStorageInterface;
|
||||
private readonly storage: StatisticsStorageInterface;
|
||||
|
||||
static readonly keepRecords = KEEP_RECORD_COUNT;
|
||||
|
||||
constructor(storage: StatisticsStorageInterface) {
|
||||
this.state = storage;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
incrementAction(ts: number): void {
|
||||
const stat = this.state.getActionStatistics();
|
||||
const stat = this.storage.getActionStatistics();
|
||||
const key = dateFormat(ts * 1000, KEY_FORMAT);
|
||||
stat[key] = (stat[key] || 0) + 1;
|
||||
this.trimStatistics(stat);
|
||||
this.state.setActionStatistics(stat);
|
||||
this.storage.setActionStatistics(stat);
|
||||
}
|
||||
|
||||
private trimStatistics(stat: ActionStatistics) {
|
||||
@ -49,6 +49,6 @@ export class Statistics {
|
||||
}
|
||||
|
||||
getActionStatistics(): ActionStatistics {
|
||||
return this.state.getActionStatistics();
|
||||
return this.storage.getActionStatistics();
|
||||
}
|
||||
}
|
||||
|
33
src/Storage/ResourceTransferStorage.ts
Normal file
33
src/Storage/ResourceTransferStorage.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { DataStorage } from '../DataStorage';
|
||||
import { ResourceTransferReport } from '../ResourceTransfer';
|
||||
|
||||
const NAMESPACE = 'resource_transfer.v1';
|
||||
|
||||
const REPORT_KEY = 'report';
|
||||
|
||||
export class ResourceTransferStorage {
|
||||
private storage: DataStorage;
|
||||
constructor() {
|
||||
this.storage = new DataStorage(NAMESPACE);
|
||||
}
|
||||
|
||||
storeReport(report: ResourceTransferReport): void {
|
||||
this.storage.set(REPORT_KEY, report);
|
||||
}
|
||||
|
||||
getReport(): ResourceTransferReport {
|
||||
return this.storage.getTyped<ResourceTransferReport>(REPORT_KEY, {
|
||||
factory: () => ({
|
||||
fromVillageId: 0,
|
||||
toVillageId: 0,
|
||||
resources: {
|
||||
lumber: 0,
|
||||
clay: 0,
|
||||
iron: 0,
|
||||
crop: 0,
|
||||
},
|
||||
score: 0,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ import { DataStorage } from '../DataStorage';
|
||||
import { BuildingQueueInfo } from '../Game';
|
||||
import { Resources, ResourcesInterface } from '../Core/Resources';
|
||||
import { ResourceStorage } from '../Core/ResourceStorage';
|
||||
import { IncomingMerchant } from '../Core/Market';
|
||||
import { IncomingMerchant, MerchantsInfo } from '../Core/Market';
|
||||
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
|
||||
import { ProductionQueue } from '../Core/ProductionQueue';
|
||||
import { getNumber } from '../utils';
|
||||
@ -13,6 +13,7 @@ const CAPACITY_KEY = 'capacity';
|
||||
const PERFORMANCE_KEY = 'performance';
|
||||
const BUILDING_QUEUE_INFO_KEY = 'building_queue_info';
|
||||
const INCOMING_MERCHANTS_KEY = 'incoming_merchants';
|
||||
const MERCHANTS_INFO_KEY = 'merchants_info';
|
||||
const SETTINGS_KEY = 'settings';
|
||||
const QUEUE_ENDING_TIME_KEY = 'queue_ending_time';
|
||||
const TASK_LIST_KEY = 'tasks';
|
||||
@ -63,6 +64,16 @@ export class VillageStorage {
|
||||
return Object.assign(res, plain) as BuildingQueueInfo;
|
||||
}
|
||||
|
||||
storeMerchantsInfo(info: MerchantsInfo): void {
|
||||
this.storage.set(MERCHANTS_INFO_KEY, info);
|
||||
}
|
||||
|
||||
getMerchantsInfo(): MerchantsInfo {
|
||||
return this.storage.getTyped<MerchantsInfo>(MERCHANTS_INFO_KEY, {
|
||||
factory: () => ({ available: 0, carry: 0 }),
|
||||
});
|
||||
}
|
||||
|
||||
storeIncomingMerchants(merchants: ReadonlyArray<IncomingMerchant>): void {
|
||||
this.storage.set(
|
||||
INCOMING_MERCHANTS_KEY,
|
||||
|
@ -4,21 +4,23 @@ import { ClickButtonAction } from '../Action/ClickButtonAction';
|
||||
import { goToMarketSendResourcesPage, goToResourceViewPage } from './ActionBundles';
|
||||
import { Task } from '../Queue/TaskProvider';
|
||||
import { registerTask } from './TaskMap';
|
||||
import { taskError } from '../Errors';
|
||||
import { FindSendResourcesPath } from '../Action/FindSendResourcesPath';
|
||||
|
||||
@registerTask()
|
||||
export class SendResourcesTask extends TaskController {
|
||||
defineActions(task: Task): Array<ActionDefinition> {
|
||||
const targetVillageId = task.args.targetVillageId || taskError('Empty target village id');
|
||||
const villageId = task.args.villageId || taskError('Empty village id');
|
||||
const actions: Array<ActionDefinition> = [];
|
||||
|
||||
return [
|
||||
goToResourceViewPage(targetVillageId),
|
||||
goToMarketSendResourcesPage(targetVillageId),
|
||||
goToResourceViewPage(villageId),
|
||||
goToMarketSendResourcesPage(villageId),
|
||||
[SendResourcesAction.name],
|
||||
[ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }],
|
||||
];
|
||||
const villages = this.factory.getAllVillages();
|
||||
for (let village of villages) {
|
||||
actions.push(goToResourceViewPage(village.id));
|
||||
actions.push(goToMarketSendResourcesPage(village.id));
|
||||
}
|
||||
|
||||
actions.push([FindSendResourcesPath.name]);
|
||||
actions.push([SendResourcesAction.name]);
|
||||
actions.push([ClickButtonAction.name, { selector: '#enabledButton.green.sendRessources' }]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,17 @@ import { CompleteTaskAction } from '../Action/CompleteTaskAction';
|
||||
import { Action } from '../Queue/ActionQueue';
|
||||
import { Args } from '../Queue/Args';
|
||||
import { Task } from '../Queue/TaskProvider';
|
||||
import { VillageFactory } from '../VillageFactory';
|
||||
|
||||
export type ActionDefinition = [string] | [string, Args];
|
||||
|
||||
export class TaskController {
|
||||
protected scheduler: Scheduler;
|
||||
protected readonly scheduler: Scheduler;
|
||||
protected readonly factory: VillageFactory;
|
||||
|
||||
constructor(scheduler: Scheduler) {
|
||||
constructor(scheduler: Scheduler, factory: VillageFactory) {
|
||||
this.scheduler = scheduler;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
async run(task: Task) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Scheduler } from '../Scheduler';
|
||||
import { TaskController } from './TaskController';
|
||||
import { ProductionQueue } from '../Core/ProductionQueue';
|
||||
import { VillageFactory } from '../VillageFactory';
|
||||
|
||||
interface TaskOptions {
|
||||
queue?: ProductionQueue;
|
||||
@ -34,11 +35,15 @@ export function getProductionQueue(name: string): ProductionQueue | undefined {
|
||||
return taskDescription.queue;
|
||||
}
|
||||
|
||||
export function createTaskHandler(name: string, scheduler: Scheduler): TaskController | undefined {
|
||||
export function createTaskHandler(
|
||||
name: string,
|
||||
scheduler: Scheduler,
|
||||
factory: VillageFactory
|
||||
): TaskController | undefined {
|
||||
const taskDescription = taskMap[name];
|
||||
if (taskDescription === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const constructor = (taskDescription.ctor as unknown) as typeof TaskController;
|
||||
return new constructor(scheduler);
|
||||
return new constructor(scheduler, factory);
|
||||
}
|
||||
|
@ -2,10 +2,13 @@ import { VillageTaskCollection } from './VillageTaskCollection';
|
||||
import { Task, TaskId } from './Queue/TaskProvider';
|
||||
import { Args } from './Queue/Args';
|
||||
import { VillageState } from './VillageState';
|
||||
import { Resources } from './Core/Resources';
|
||||
import { TryLaterError } from './Errors';
|
||||
import { aroundMinutes } from './utils';
|
||||
|
||||
export class VillageController {
|
||||
private readonly villageId: number;
|
||||
private taskCollection: VillageTaskCollection;
|
||||
private readonly taskCollection: VillageTaskCollection;
|
||||
private readonly state: VillageState;
|
||||
|
||||
constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) {
|
||||
@ -33,4 +36,46 @@ export class VillageController {
|
||||
postponeTask(taskId: TaskId, seconds: number) {
|
||||
this.taskCollection.postponeTask(taskId, seconds);
|
||||
}
|
||||
|
||||
getAvailableForSendResources(): Resources {
|
||||
const balance = this.state.required.balance;
|
||||
const free = balance.max(Resources.zero());
|
||||
|
||||
console.table([
|
||||
{ name: 'Sender balance', ...balance },
|
||||
{ name: 'Sender free', ...free },
|
||||
]);
|
||||
|
||||
const amount = free.amount();
|
||||
const threshold = this.state.settings.sendResourcesThreshold;
|
||||
|
||||
if (amount < threshold) {
|
||||
return Resources.zero();
|
||||
}
|
||||
|
||||
return free;
|
||||
}
|
||||
|
||||
getRequiredResources(): Resources {
|
||||
const performance = this.state.performance;
|
||||
const maxPossibleToStore = this.state.storage.capacity.sub(performance);
|
||||
const currentResources = this.state.resources;
|
||||
const incomingResources = this.state.incomingResources;
|
||||
const requirementResources = this.state.required.resources;
|
||||
const missingResources = requirementResources
|
||||
.min(maxPossibleToStore)
|
||||
.sub(incomingResources)
|
||||
.sub(currentResources)
|
||||
.max(Resources.zero());
|
||||
|
||||
console.table([
|
||||
{ name: 'Recipient max possible', ...maxPossibleToStore },
|
||||
{ name: 'Recipient resources', ...currentResources },
|
||||
{ name: 'Recipient incoming', ...incomingResources },
|
||||
{ name: 'Recipient requirements', ...requirementResources },
|
||||
{ name: 'Recipient missing', ...missingResources },
|
||||
]);
|
||||
|
||||
return missingResources;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { VillageStorage } from './Storage/VillageStorage';
|
||||
import { VillageRepository } from './VillageRepository';
|
||||
import { VillageTaskCollection } from './VillageTaskCollection';
|
||||
import { VillageState, VillageStateFactory } from './VillageState';
|
||||
import { Village } from './Core/Village';
|
||||
|
||||
export class VillageFactory {
|
||||
private readonly villageRepository: VillageRepository;
|
||||
@ -11,6 +12,14 @@ export class VillageFactory {
|
||||
this.villageRepository = villageRepository;
|
||||
}
|
||||
|
||||
getAllVillages(): Array<Village> {
|
||||
return this.villageRepository.all();
|
||||
}
|
||||
|
||||
getVillage(villageId: number): Village {
|
||||
return this.villageRepository.get(villageId);
|
||||
}
|
||||
|
||||
createStorage(villageId: number): VillageStorage {
|
||||
const village = this.villageRepository.get(villageId);
|
||||
return new VillageStorage(village.id);
|
||||
|
@ -10,6 +10,11 @@ export function randomInRange(from: number, to: number): number {
|
||||
return Math.floor(from + variation);
|
||||
}
|
||||
|
||||
export function around(value: number, koeff: number): number {
|
||||
const delta = Math.floor(value * koeff);
|
||||
return randomInRange(value - delta, value + delta);
|
||||
}
|
||||
|
||||
export async function sleepMicro() {
|
||||
const timeInMs = randomInRange(1500, 2500);
|
||||
return await sleep(timeInMs);
|
||||
@ -17,8 +22,7 @@ export async function sleepMicro() {
|
||||
|
||||
export function aroundMinutes(minutes: number) {
|
||||
const seconds = minutes * 60;
|
||||
const delta = Math.floor(seconds * 0.1);
|
||||
return randomInRange(seconds - delta, seconds + delta);
|
||||
return around(seconds, 0.1);
|
||||
}
|
||||
|
||||
export async function waitForLoad() {
|
||||
|
Loading…
Reference in New Issue
Block a user