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 { VillageState } from '../VillageState';
import { MerchantsInfo } from '../Core/Market'; import { MerchantsInfo } from '../Core/Market';
import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles'; import { goToMarketSendResourcesPage, goToResourceViewPage } from '../Task/ActionBundles';
import { ResourceTransferCalculator, ResourceTransferReport } from '../ResourceTransfer'; import {
compareReports,
ResourceTransferCalculator,
ResourceTransferReport,
} from '../ResourceTransfer';
import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage'; import { ResourceTransferStorage } from '../Storage/ResourceTransferStorage';
import { path } from '../Helpers/Path'; import { path } from '../Helpers/Path';
import { MARKET_ID } from '../Core/Buildings'; 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(); const bestReport = reports.shift();

View File

@ -4,7 +4,7 @@
<script> <script>
export default { export default {
props: ['value', 'max', 'speed'], props: ['value', 'warning', 'critical', 'full'],
data() { data() {
return {}; return {};
}, },
@ -13,31 +13,18 @@ export default {
return this.value; return this.value;
}, },
percent() { percent() {
return Math.floor((this.value / this.max) * 100); return Math.floor((this.value / this.full) * 100);
}, },
title() { title() {
if (this.speed < 0) { return `${this.value}/${this.full}, ${this.percent}%`;
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}`;
}
}, },
classes() { classes() {
return { return {
warning: this.percent >= 70 && this.percent < 95, warning: this.value >= this.warning && this.value < this.critical,
bad: this.percent >= 95, 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> </script>

View File

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

View File

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

View File

@ -1,11 +1,26 @@
import { VillageFactory } from './VillageFactory'; 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 { export interface ResourceTransferReport {
fromVillageId: number; fromVillageId: number;
toVillageId: number; toVillageId: number;
resources: ResourcesInterface; 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 { export class ResourceTransferCalculator {
@ -15,20 +30,21 @@ export class ResourceTransferCalculator {
} }
calc(fromVillageId: number, toVillageId: number): ResourceTransferReport { calc(fromVillageId: number, toVillageId: number): ResourceTransferReport {
const senderState = this.factory.createState(fromVillageId); const sender = this.factory.createController(fromVillageId);
const senderController = this.factory.createController(fromVillageId); const recipient = this.factory.createController(toVillageId);
const senderStorage = this.factory.createStorage(fromVillageId);
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 contractResources = senderReadyResources.min(recipientNeedResources);
const merchantsInfo = senderStorage.getMerchantsInfo(); const merchantsInfo = sender.getMerchantsInfo();
const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry; const merchantsCapacity = merchantsInfo.available * merchantsInfo.carry;
let readyToTransfer = contractResources; let readyToTransfer = contractResources;
@ -50,7 +66,21 @@ export class ResourceTransferCalculator {
fromVillageId, fromVillageId,
toVillageId, toVillageId,
resources: readyToTransfer, 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, iron: 0,
crop: 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 { Resources } from './Core/Resources';
import { TryLaterError } from './Errors'; import { TryLaterError } from './Errors';
import { aroundMinutes } from './utils'; import { aroundMinutes } from './utils';
import { MerchantsInfo } from './Core/Market';
import { VillageStorage } from './Storage/VillageStorage';
export class VillageController { export class VillageController {
private readonly villageId: number; private readonly villageId: number;
private readonly storage: VillageStorage;
private readonly taskCollection: VillageTaskCollection; private readonly taskCollection: VillageTaskCollection;
private readonly state: VillageState; private readonly state: VillageState;
constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) { constructor(
villageId: number,
storage: VillageStorage,
taskCollection: VillageTaskCollection,
state: VillageState
) {
this.villageId = villageId; this.villageId = villageId;
this.storage = storage;
this.taskCollection = taskCollection; this.taskCollection = taskCollection;
this.state = state; this.state = state;
} }
@ -21,6 +30,10 @@ export class VillageController {
return this.villageId; return this.villageId;
} }
getState(): VillageState {
return this.state;
}
getReadyProductionTask(): Task | undefined { getReadyProductionTask(): Task | undefined {
return this.taskCollection.getReadyForProductionTask(); return this.taskCollection.getReadyForProductionTask();
} }
@ -37,7 +50,22 @@ export class VillageController {
this.taskCollection.postponeTask(taskId, seconds); 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 balance = this.state.required.balance;
const free = balance.max(Resources.zero()); const free = balance.max(Resources.zero());
@ -57,11 +85,11 @@ export class VillageController {
} }
getRequiredResources(): Resources { getRequiredResources(): Resources {
const performance = this.state.performance; const maxPossibleToStore = this.state.storageOptimumFullness;
const maxPossibleToStore = this.state.storage.capacity.sub(performance);
const currentResources = this.state.resources; const currentResources = this.state.resources;
const incomingResources = this.state.incomingResources; const incomingResources = this.state.incomingResources;
const requirementResources = this.state.required.resources; const requirementResources = this.state.required.resources;
const missingResources = requirementResources const missingResources = requirementResources
.min(maxPossibleToStore) .min(maxPossibleToStore)
.sub(incomingResources) .sub(incomingResources)
@ -78,4 +106,11 @@ export class VillageController {
return missingResources; 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); const village = this.villageRepository.get(villageId);
return new VillageController( return new VillageController(
village.id, village.id,
this.createStorage(village.id),
this.createTaskCollection(village.id), this.createTaskCollection(village.id),
this.createState(village.id) this.createState(village.id)
); );

View File

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