Add sending overflow resources

This commit is contained in:
Anton Vakhrushev 2020-06-06 20:38:40 +03:00
parent 74cfeb6075
commit d29e95f511
9 changed files with 142 additions and 106 deletions

View File

@ -9,7 +9,11 @@ import { clickSendButton, fillSendResourcesForm } from '../Page/BuildingPage/Mar
import { VillageState } from '../VillageState';
import { MerchantsInfo } from '../Core/Market';
import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles';
import { ResourceTransferCalculator, ResourceTransferReport } from '../ResourceTransfer';
import {
compareReports,
ResourceTransferCalculator,
ResourceTransferReport,
} from '../ResourceTransfer';
import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
import { path } from '../Helpers/Path';
import { MARKET_ID } from '../Core/Buildings';
@ -29,7 +33,7 @@ export class FindSendResourcesPath extends ActionController {
}
}
reports.sort((r1, r2) => r2.score - r1.score);
reports.sort(compareReports);
const bestReport = reports.shift();

View File

@ -4,7 +4,7 @@
<script>
export default {
props: ['value', 'max', 'speed'],
props: ['value', 'warning', 'critical', 'full'],
data() {
return {};
},
@ -13,31 +13,18 @@ export default {
return this.value;
},
percent() {
return Math.floor((this.value / this.max) * 100);
return Math.floor((this.value / this.full) * 100);
},
title() {
if (this.speed < 0) {
const time = this.fractionalHourToTime(this.value / this.speed);
return `${this.value}, ${this.percent}%, опустеет через ${time}`;
} else {
const time = this.fractionalHourToTime((this.max - this.value) / this.speed);
return `${this.value}, ${this.percent}%, заполнится через ${time}`;
}
return `${this.value}/${this.full}, ${this.percent}%`;
},
classes() {
return {
warning: this.percent >= 70 && this.percent < 95,
bad: this.percent >= 95,
warning: this.value >= this.warning && this.value < this.critical,
bad: this.value >= this.critical,
};
},
},
methods: {
fractionalHourToTime(value) {
const hours = Math.floor(value);
const minutes = Math.round((value - hours) * 60);
return `${hours}:${String(minutes).padStart(2, '0')}`;
},
},
};
</script>

View File

@ -30,52 +30,40 @@
<td class="right">
<filling
:value="villageState.resources.lumber"
:max="villageState.storage.capacity.lumber"
:speed="villageState.performance.lumber"
:warning="villageState.storageOptimumFullness.lumber"
:critical="villageState.upperCriticalLevel.lumber"
:full="villageState.storage.capacity.lumber"
></filling>
</td>
<td class="right">
<filling
:value="villageState.resources.clay"
:max="villageState.storage.capacity.clay"
:speed="villageState.performance.clay"
:warning="villageState.storageOptimumFullness.clay"
:critical="villageState.upperCriticalLevel.clay"
:full="villageState.storage.capacity.clay"
></filling>
</td>
<td class="right">
<filling
:value="villageState.resources.iron"
:max="villageState.storage.capacity.iron"
:speed="villageState.performance.iron"
:warning="villageState.storageOptimumFullness.iron"
:critical="villageState.upperCriticalLevel.iron"
:full="villageState.storage.capacity.iron"
></filling>
</td>
<td class="right">
<filling
:value="villageState.resources.crop"
:max="villageState.storage.capacity.crop"
:speed="villageState.performance.crop"
:warning="villageState.storageOptimumFullness.crop"
:critical="villageState.upperCriticalLevel.crop"
:full="villageState.storage.capacity.crop"
></filling>
</td>
<td class="right" v-text="storageTime(villageState)"></td>
<td></td>
</tr>
<tr class="performance-line">
<td class="right">Прирост:</td>
<td class="right">
<resource :value="villageState.performance.lumber"></resource>
</td>
<td class="right">
<resource :value="villageState.performance.clay"></resource>
</td>
<td class="right">
<resource :value="villageState.performance.iron"></resource>
</td>
<td class="right">
<resource :value="villageState.performance.crop"></resource>
</td>
<td></td>
<td></td>
</tr>
<resource-line :title="'Прирост:'" :resources="villageState.performance" />
<resource-line
v-if="isExtended(villageState.id)"
@ -84,43 +72,11 @@
:resources="villageState.upperCriticalLevel"
/>
<tr class="required-line" v-if="villageState.required.active">
<td class="right">След. задача:</td>
<td class="right">
<resource
:value="villageState.required.resources.lumber"
:hide-zero="true"
:color="false"
:sign="false"
></resource>
</td>
<td class="right">
<resource
:value="villageState.required.resources.clay"
:hide-zero="true"
:color="false"
:sign="false"
></resource>
</td>
<td class="right">
<resource
:value="villageState.required.resources.iron"
:hide-zero="true"
:color="false"
:sign="false"
></resource>
</td>
<td class="right">
<resource
:value="villageState.required.resources.crop"
:hide-zero="true"
:color="false"
:sign="false"
></resource>
</td>
<td></td>
<td></td>
</tr>
<resource-line
v-if="villageState.required.active"
:title="'След. задача:'"
:resources="villageState.required.resources"
/>
<resource-line
v-if="villageState.required.active"
@ -169,6 +125,8 @@
:title="'Крит. уровень:'"
:hint="'Критический уровень'"
:hide-zero="true"
:color="false"
:sign="false"
:resources="villageState.upperCriticalLevel"
/>
@ -177,6 +135,8 @@
:title="'Опт. уровень:'"
:hint="'Оптимальный уровень'"
:hide-zero="true"
:color="false"
:sign="false"
:resources="villageState.storageOptimumFullness"
/>
@ -192,6 +152,11 @@
"
>$->{{ s.village.name }}</a
>
</td>
</tr>
<tr class="normal-line">
<td class="right" colspan="7">
<a class="village-quick-link" :href="quartersPath(villageState.village)">Казармы</a>
<a class="village-quick-link" :href="horseStablePath(villageState.village)"
>Конюшни</a

View File

@ -2,16 +2,16 @@
<tr class="resource-line">
<td class="title" v-text="title" :title="hint"></td>
<td class="lumber">
<resource :value="resources.lumber" :hide-zero="hideZero" />
<resource :value="resources.lumber" :hide-zero="hideZero" :color="color" :sign="sign" />
</td>
<td class="clay">
<resource :value="resources.clay" :hide-zero="hideZero" />
<resource :value="resources.clay" :hide-zero="hideZero" :color="color" :sign="sign" />
</td>
<td class="iron">
<resource :value="resources.iron" :hide-zero="hideZero" />
<resource :value="resources.iron" :hide-zero="hideZero" :color="color" :sign="sign" />
</td>
<td class="crop">
<resource :value="resources.crop" :hide-zero="hideZero" />
<resource :value="resources.crop" :hide-zero="hideZero" :color="color" :sign="sign" />
</td>
<td class="time1" v-text="time1"></td>
<td class="time2" v-text="time2"></td>
@ -48,6 +48,14 @@ export default {
type: Boolean,
default: false,
},
color: {
type: Boolean,
default: true,
},
sign: {
type: Boolean,
default: true,
},
},
};
</script>

View File

@ -1,11 +1,26 @@
import { VillageFactory } from './VillageFactory';
import { ResourcesInterface } from './Core/Resources';
import { Resources, ResourcesInterface } from './Core/Resources';
import { VillageController } from './VillageController';
export interface ResourceTransferScore {
amount: number;
overflow: boolean;
}
export interface ResourceTransferReport {
fromVillageId: number;
toVillageId: number;
resources: ResourcesInterface;
score: number;
score: ResourceTransferScore;
}
export function compareReports(r1: ResourceTransferReport, r2: ResourceTransferReport): number {
if (r1.score.overflow !== r2.score.overflow) {
const o1 = r1.score.overflow ? 1 : 0;
const o2 = r2.score.overflow ? 1 : 0;
return o2 - o1;
}
return r2.score.amount - r1.score.amount;
}
export class ResourceTransferCalculator {
@ -15,20 +30,21 @@ export class ResourceTransferCalculator {
}
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 sender = this.factory.createController(fromVillageId);
const recipient = this.factory.createController(toVillageId);
const recipientController = this.factory.createController(toVillageId);
let [senderReadyResources, recipientNeedResources] = this.getTransferResourcePair(
sender,
recipient
);
const multiplier = sender.getSendResourcesMultiplier();
senderReadyResources = senderReadyResources.downTo(multiplier);
recipientNeedResources = recipientNeedResources.upTo(multiplier);
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 merchantsInfo = sender.getMerchantsInfo();
const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry;
let readyToTransfer = contractResources;
@ -50,7 +66,21 @@ export class ResourceTransferCalculator {
fromVillageId,
toVillageId,
resources: readyToTransfer,
score: readyToTransfer.amount(),
score: {
amount: readyToTransfer.amount(),
overflow: sender.getState().isOverflowing,
},
};
}
private getTransferResourcePair(
sender: VillageController,
recipient: VillageController
): [Resources, Resources] {
if (sender.getState().isOverflowing) {
return [sender.getOverflowResources(), recipient.getAvailableToReceiveResources()];
}
return [sender.getFreeResources(), recipient.getRequiredResources()];
}
}

View File

@ -26,7 +26,10 @@ export class ResourceTransferStorage {
iron: 0,
crop: 0,
},
score: 0,
score: {
amount: 0,
overflow: false,
},
}),
});
}

View File

@ -5,14 +5,23 @@ import { VillageState } from './VillageState';
import { Resources } from './Core/Resources';
import { TryLaterError } from './Errors';
import { aroundMinutes } from './utils';
import { MerchantsInfo } from './Core/Market';
import { VillageStorage } from './Storage/VillageStorage';
export class VillageController {
private readonly villageId: number;
private readonly storage: VillageStorage;
private readonly taskCollection: VillageTaskCollection;
private readonly state: VillageState;
constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) {
constructor(
villageId: number,
storage: VillageStorage,
taskCollection: VillageTaskCollection,
state: VillageState
) {
this.villageId = villageId;
this.storage = storage;
this.taskCollection = taskCollection;
this.state = state;
}
@ -21,6 +30,10 @@ export class VillageController {
return this.villageId;
}
getState(): VillageState {
return this.state;
}
getReadyProductionTask(): Task | undefined {
return this.taskCollection.getReadyForProductionTask();
}
@ -37,7 +50,22 @@ export class VillageController {
this.taskCollection.postponeTask(taskId, seconds);
}
getAvailableForSendResources(): Resources {
getMerchantsInfo(): MerchantsInfo {
return this.storage.getMerchantsInfo();
}
getSendResourcesMultiplier(): number {
return this.state.settings.sendResourcesMultiplier;
}
getOverflowResources(): Resources {
const limit = this.state.storageOptimumFullness;
const currentResources = this.state.resources;
return currentResources.sub(limit).max(Resources.zero());
}
getFreeResources(): Resources {
const balance = this.state.required.balance;
const free = balance.max(Resources.zero());
@ -57,11 +85,11 @@ export class VillageController {
}
getRequiredResources(): Resources {
const performance = this.state.performance;
const maxPossibleToStore = this.state.storage.capacity.sub(performance);
const maxPossibleToStore = this.state.storageOptimumFullness;
const currentResources = this.state.resources;
const incomingResources = this.state.incomingResources;
const requirementResources = this.state.required.resources;
const missingResources = requirementResources
.min(maxPossibleToStore)
.sub(incomingResources)
@ -78,4 +106,11 @@ export class VillageController {
return missingResources;
}
getAvailableToReceiveResources(): Resources {
const maxPossibleToStore = this.state.storageOptimumFullness;
const currentResources = this.state.resources;
return maxPossibleToStore.sub(currentResources).max(Resources.zero());
}
}

View File

@ -63,6 +63,7 @@ export class VillageFactory {
const village = this.villageRepository.get(villageId);
return new VillageController(
village.id,
this.createStorage(village.id),
this.createTaskCollection(village.id),
this.createState(village.id)
);

View File

@ -67,6 +67,7 @@ interface VillageOwnState {
storage: VillageStorageState;
upperCriticalLevel: Resources;
storageOptimumFullness: Resources;
isOverflowing: boolean;
/**
* Required resources for nearest task
*/
@ -197,8 +198,9 @@ function createVillageOwnState(
const resources = storage.getResources();
const resourceStorage = storage.getResourceStorage();
const performance = storage.getResourcesPerformance();
const upperCriticalLevel = Resources.fromStorage(resourceStorage).sub(performance.scale(2));
const storageOptimumFullness = Resources.fromStorage(resourceStorage).sub(performance.scale(3));
const upperCriticalLevel = Resources.fromStorage(resourceStorage).sub(performance.scale(1));
const storageOptimumFullness = Resources.fromStorage(resourceStorage).sub(performance.scale(2));
const isOverflowing = upperCriticalLevel.anyLower(resources);
const requiredResources = taskCollection.getReadyTaskRequiredResources();
const frontierResources = taskCollection.getFrontierResources();
const totalRequiredResources = taskCollection.getAllTasksRequiredResources();
@ -212,6 +214,7 @@ function createVillageOwnState(
required: calcResourceBalance(requiredResources, resources, performance),
upperCriticalLevel,
storageOptimumFullness,
isOverflowing,
frontierRequired: calcResourceBalance(frontierResources, resources, performance),
totalRequired: calcResourceBalance(totalRequiredResources, resources, performance),
incomingResources: calcIncomingResources(storage),