Add smart resource transfer

This commit is contained in:
2020-05-24 21:05:29 +03:00
parent 301b1a6ca9
commit faed76df4d
17 changed files with 272 additions and 148 deletions

View 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);
}
}

View File

@ -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;
}
}