More clear storage time calculation

This commit is contained in:
Anton Vakhrushev 2020-05-08 12:39:30 +03:00
parent 4f7e38a953
commit f648f7dd5a
4 changed files with 151 additions and 77 deletions

View File

@ -1,7 +1,40 @@
import { Resources } from './Resources'; import { Resources } from './Resources';
type GatheringNever = 'never'; type GatheringPlain = number | 'never';
type GatheringTime = number | GatheringNever;
enum GatheringType {
Counting,
Never,
}
export class GatheringTime {
private readonly _type: GatheringType;
private readonly _seconds: number;
static makeNever(): GatheringTime {
return new GatheringTime(GatheringType.Never, 0);
}
static makeCounting(value: number): GatheringTime {
return new GatheringTime(GatheringType.Counting, value);
}
constructor(type: GatheringType, seconds: number) {
this._type = type;
this._seconds = seconds;
}
get never(): boolean {
return this._type === GatheringType.Never;
}
get seconds(): number {
if (this.never) {
throw new Error('Never');
}
return this._seconds;
}
}
export class GatheringTimings { export class GatheringTimings {
readonly lumber: GatheringTime; readonly lumber: GatheringTime;
@ -9,6 +42,17 @@ export class GatheringTimings {
readonly iron: GatheringTime; readonly iron: GatheringTime;
readonly crop: GatheringTime; readonly crop: GatheringTime;
static create(
lumber: GatheringPlain,
clay: GatheringPlain,
iron: GatheringPlain,
crop: GatheringPlain
): GatheringTimings {
const factory = (p: GatheringPlain) =>
p === 'never' ? GatheringTime.makeNever() : GatheringTime.makeCounting(p);
return new GatheringTimings(factory(lumber), factory(clay), factory(iron), factory(crop));
}
constructor(lumber: GatheringTime, clay: GatheringTime, iron: GatheringTime, crop: GatheringTime) { constructor(lumber: GatheringTime, clay: GatheringTime, iron: GatheringTime, crop: GatheringTime) {
this.lumber = lumber; this.lumber = lumber;
this.clay = clay; this.clay = clay;
@ -16,34 +60,41 @@ export class GatheringTimings {
this.crop = crop; this.crop = crop;
} }
private get common(): GatheringTime { get slowest(): GatheringTime {
const xs = [this.lumber, this.clay, this.iron, this.crop]; const xs = [this.lumber, this.clay, this.iron, this.crop];
return xs.reduce((m, t) => (m === 'never' || t === 'never' ? 'never' : Math.max(m, t)), 0); const init = new GatheringTime(GatheringType.Counting, 0);
return xs.reduce(
(m, t) =>
m.never || t.never
? GatheringTime.makeNever()
: GatheringTime.makeCounting(Math.max(m.seconds, t.seconds)),
init
);
} }
get hours(): number { get fastest(): GatheringTime {
const common = this.common; const xs = [this.lumber, this.clay, this.iron, this.crop];
if (common === 'never') { const init = new GatheringTime(GatheringType.Counting, Number.MAX_SAFE_INTEGER);
throw Error('Never'); return xs.reduce(
} (m, t) =>
return common; m.never || t.never
} ? GatheringTime.makeNever()
: GatheringTime.makeCounting(Math.min(m.seconds, t.seconds)),
get never(): boolean { init
return this.common === 'never'; );
} }
} }
function calcGatheringTime(val: number, desired: number, speed: number): GatheringTime { function calcGatheringTime(val: number, desired: number, speed: number): GatheringTime {
const diff = desired - val; const diff = desired - val;
if (diff > 0 && speed <= 0) { if (diff > 0 && speed <= 0) {
return 'never'; return GatheringTime.makeNever();
} }
if (diff <= 0) { if (diff <= 0) {
return 0; return GatheringTime.makeCounting(0);
} }
return diff / speed; return GatheringTime.makeCounting((diff / speed) * 3600);
} }
export function calcGatheringTimings(resources: Resources, desired: Resources, speed: Resources): GatheringTimings { export function calcGatheringTimings(resources: Resources, desired: Resources, speed: Resources): GatheringTimings {

View File

@ -8,8 +8,7 @@
<th class="right">Глина</th> <th class="right">Глина</th>
<th class="right">Железо</th> <th class="right">Железо</th>
<th class="right">Зерно</th> <th class="right">Зерно</th>
<th class="right">Склад</th> <th class="right">Время</th>
<th class="right">Амбар</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -46,10 +45,7 @@
:speed="villageState.performance.crop" :speed="villageState.performance.crop"
></filling> ></filling>
</td> </td>
<td class="right"> <td class="right" v-text="storageTime(villageState)"></td>
<a :href="warehousePath(villageState.village)" v-text="villageState.storage.lumber"></a>
</td>
<td class="right" v-text="villageState.storage.crop"></td>
</tr> </tr>
<tr class="performance-line"> <tr class="performance-line">
<td class="right">Прирост:</td> <td class="right">Прирост:</td>
@ -66,7 +62,6 @@
<resource :value="villageState.performance.crop"></resource> <resource :value="villageState.performance.crop"></resource>
</td> </td>
<td></td> <td></td>
<td></td>
</tr> </tr>
<tr class="required-line"> <tr class="required-line">
<td class="right">След. задача:</td> <td class="right">След. задача:</td>
@ -102,8 +97,7 @@
:sign="false" :sign="false"
></resource> ></resource>
</td> </td>
<td class="right" v-text="secondsToRequiredTime(villageState.buildRemainingSeconds)"></td> <td class="right" v-text="renderTimeInSeconds(villageState.buildRemainingSeconds)"></td>
<td></td>
</tr> </tr>
<tr class="required-line"> <tr class="required-line">
<td class="right">Баланс задачи:</td> <td class="right">Баланс задачи:</td>
@ -119,8 +113,7 @@
<td class="right"> <td class="right">
<resource :value="villageState.required.balance.crop"></resource> <resource :value="villageState.required.balance.crop"></resource>
</td> </td>
<td class="right" v-text="secondsToRequiredTime(villageState.required.time)"></td> <td class="right" v-text="renderGatheringTime(villageState.required.time)"></td>
<td></td>
</tr> </tr>
<tr class="required-line"> <tr class="required-line">
<td class="right">Баланс очереди:</td> <td class="right">Баланс очереди:</td>
@ -136,8 +129,7 @@
<td class="right"> <td class="right">
<resource :value="villageState.totalRequired.balance.crop"></resource> <resource :value="villageState.totalRequired.balance.crop"></resource>
</td> </td>
<td class="right" v-text="secondsToRequiredTime(villageState.totalRequired.time)"></td> <td class="right" v-text="renderGatheringTime(villageState.totalRequired.time)"></td>
<td></td>
</tr> </tr>
<tr class="commitments-line" v-if="!villageState.commitments.empty()"> <tr class="commitments-line" v-if="!villageState.commitments.empty()">
<td class="right">Обязательства:</td> <td class="right">Обязательства:</td>
@ -154,7 +146,6 @@
<resource :value="villageState.commitments.crop" :hide-zero="true"></resource> <resource :value="villageState.commitments.crop" :hide-zero="true"></resource>
</td> </td>
<td></td> <td></td>
<td></td>
</tr> </tr>
<tr class="incoming-line" v-if="!villageState.incomingResources.empty()"> <tr class="incoming-line" v-if="!villageState.incomingResources.empty()">
<td class="right">Торговцы:</td> <td class="right">Торговцы:</td>
@ -171,11 +162,10 @@
<resource :value="villageState.incomingResources.crop" :hide-zero="true"></resource> <resource :value="villageState.incomingResources.crop" :hide-zero="true"></resource>
</td> </td>
<td></td> <td></td>
<td></td>
</tr> </tr>
<tr class="normal-line"> <tr class="normal-line">
<td></td> <td></td>
<td class="right" colspan="6"> <td class="right" colspan="5">
<a <a
v-for="s in shared.villageStates" v-for="s in shared.villageStates"
v-if="s.id !== villageState.id" v-if="s.id !== villageState.id"
@ -199,9 +189,19 @@
<script> <script>
import ResourceBalance from './ResourceBalance'; import ResourceBalance from './ResourceBalance';
import VillageResource from './VillageResource'; import VillageResource from './VillageResource';
import { COLLECTION_POINT_ID, HORSE_STABLE_ID, MARKET_ID, QUARTERS_ID, WAREHOUSE_ID } from '../Core/Buildings'; import { COLLECTION_POINT_ID, HORSE_STABLE_ID, MARKET_ID, QUARTERS_ID } from '../Core/Buildings';
import { path } from '../Helpers/Path'; import { path } from '../Helpers/Path';
function secondsToTime(value) {
if (value === 0) {
return '';
}
const hours = Math.floor(value / 3600);
const minutes = Math.floor((value % 3600) / 60);
const seconds = Math.floor(value % 60);
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
export default { export default {
components: { components: {
resource: ResourceBalance, resource: ResourceBalance,
@ -217,6 +217,11 @@ export default {
path(name, args) { path(name, args) {
return path(name, args); return path(name, args);
}, },
storageTime(villageState) {
const toZero = villageState.storageBalance.timeToZero;
const toFull = villageState.storageBalance.timeToFull;
return this.renderGatheringTime(toFull.never ? toZero : toFull);
},
marketPath(fromVillage, toVillage) { marketPath(fromVillage, toVillage) {
return path('/build.php', { return path('/build.php', {
newdid: fromVillage.id, newdid: fromVillage.id,
@ -226,9 +231,6 @@ export default {
y: toVillage.crd.y, y: toVillage.crd.y,
}); });
}, },
warehousePath(village) {
return path('/build.php', { newdid: village.id, gid: WAREHOUSE_ID });
},
collectionPointPath(village) { collectionPointPath(village) {
return path('/build.php', { newdid: village.id, gid: COLLECTION_POINT_ID, tt: 1 }); return path('/build.php', { newdid: village.id, gid: COLLECTION_POINT_ID, tt: 1 });
}, },
@ -238,20 +240,18 @@ export default {
horseStablePath(village) { horseStablePath(village) {
return path('/build.php', { newdid: village.id, gid: HORSE_STABLE_ID }); return path('/build.php', { newdid: village.id, gid: HORSE_STABLE_ID });
}, },
secondsToTime(value) { renderTimeInSeconds(value) {
if (value === 0) { return secondsToTime(value);
return '';
}
const hours = Math.floor(value / 3600);
const minutes = Math.floor((value % 3600) / 60);
const seconds = Math.floor(value % 60);
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}, },
secondsToRequiredTime(value) { /**
if (value === -1) { * @param {GatheringTime} value
* @returns {string}
*/
renderGatheringTime(value) {
if (value.never) {
return 'never'; return 'never';
} }
return this.secondsToTime(value); return secondsToTime(value.seconds);
}, },
}, },
}; };

View File

@ -2,12 +2,21 @@ import { Village } from './Core/Village';
import { Scheduler } from './Scheduler'; import { Scheduler } from './Scheduler';
import { Resources } from './Core/Resources'; import { Resources } from './Core/Resources';
import { VillageStorage } from './Storage/VillageStorage'; import { VillageStorage } from './Storage/VillageStorage';
import { calcGatheringTimings } from './Core/GatheringTimings'; import { calcGatheringTimings, GatheringTime } from './Core/GatheringTimings';
interface StorageBalance {
resources: Resources;
storage: Resources;
balance: Resources;
performance: Resources;
timeToZero: GatheringTime;
timeToFull: GatheringTime;
}
interface RequiredResources { interface RequiredResources {
resources: Resources; resources: Resources;
balance: Resources; balance: Resources;
time: number; time: GatheringTime;
} }
interface VillageOwnState { interface VillageOwnState {
@ -15,10 +24,11 @@ interface VillageOwnState {
village: Village; village: Village;
resources: Resources; resources: Resources;
performance: Resources; performance: Resources;
storage: Resources;
storageBalance: StorageBalance;
required: RequiredResources; required: RequiredResources;
totalRequired: RequiredResources; totalRequired: RequiredResources;
incomingResources: Resources; incomingResources: Resources;
storage: Resources;
buildRemainingSeconds: number; buildRemainingSeconds: number;
} }
@ -39,19 +49,31 @@ export interface VillageState extends VillageOwnState {
function calcResourceBalance(resources: Resources, current: Resources, performance: Resources): RequiredResources { function calcResourceBalance(resources: Resources, current: Resources, performance: Resources): RequiredResources {
return { return {
resources: resources, resources,
balance: current.sub(resources), balance: current.sub(resources),
time: timeToResources(current, resources, performance), time: timeToAllResources(current, resources, performance),
}; };
} }
function timeToResources(current: Resources, desired: Resources, performance: Resources): number { function calcStorageBalance(resources: Resources, storage: Resources, performance: Resources): StorageBalance {
const timings = calcGatheringTimings(current, desired, performance); return {
if (timings.never) { resources,
return -1; storage,
} performance,
balance: storage.sub(resources),
timeToZero: timeToFastestResource(resources, Resources.zero(), performance),
timeToFull: timeToFastestResource(resources, storage, performance),
};
}
return timings.hours * 3600; function timeToAllResources(current: Resources, desired: Resources, performance: Resources): GatheringTime {
const timings = calcGatheringTimings(current, desired, performance);
return timings.slowest;
}
function timeToFastestResource(current: Resources, desired: Resources, performance: Resources): GatheringTime {
const timings = calcGatheringTimings(current, desired, performance);
return timings.fastest;
} }
function calcIncomingResources(storage: VillageStorage): Resources { function calcIncomingResources(storage: VillageStorage): Resources {
@ -72,9 +94,10 @@ function createVillageOwnState(village: Village, scheduler: Scheduler): VillageO
village, village,
resources, resources,
performance, performance,
storage: Resources.fromStorage(resourceStorage),
storageBalance: calcStorageBalance(resources, Resources.fromStorage(resourceStorage), performance),
required: calcResourceBalance(requiredResources, resources, performance), required: calcResourceBalance(requiredResources, resources, performance),
totalRequired: calcResourceBalance(totalRequiredResources, resources, performance), totalRequired: calcResourceBalance(totalRequiredResources, resources, performance),
storage: Resources.fromStorage(resourceStorage),
buildRemainingSeconds: buildQueueInfo.seconds, buildRemainingSeconds: buildQueueInfo.seconds,
incomingResources: calcIncomingResources(storage), incomingResources: calcIncomingResources(storage),
}; };

View File

@ -6,18 +6,18 @@ import { calcGatheringTimings, GatheringTimings } from '../../src/Core/Gathering
describe('Gathering timings', function() { describe('Gathering timings', function() {
it('Can calc common from numbers', function() { it('Can calc common from numbers', function() {
const timings = new GatheringTimings(10, 20, 15, 5); const timings = GatheringTimings.create(10, 20, 15, 5);
expect(20).to.equals(timings.hours); expect(20).to.equals(timings.slowest.seconds);
}); });
it('Can calc common with never', function() { it('Can calc common with never', function() {
const timings = new GatheringTimings(10, 20, 'never', 5); const timings = GatheringTimings.create(10, 20, 'never', 5);
expect(true).to.equals(timings.never); expect(true).to.equals(timings.slowest.never);
}); });
it('Can calc common with all never', function() { it('Can calc common with all never', function() {
const timings = new GatheringTimings('never', 'never', 'never', 'never'); const timings = GatheringTimings.create('never', 'never', 'never', 'never');
expect(true).to.equals(timings.never); expect(true).to.equals(timings.slowest.never);
}); });
it('Can calc timings', function() { it('Can calc timings', function() {
@ -25,7 +25,7 @@ describe('Gathering timings', function() {
const desired = new Resources(60, 60, 60, 60); const desired = new Resources(60, 60, 60, 60);
const speed = new Resources(5, 5, 5, 5); const speed = new Resources(5, 5, 5, 5);
const timings = calcGatheringTimings(resources, desired, speed); const timings = calcGatheringTimings(resources, desired, speed);
expect(10).to.equals(timings.hours); expect(10 * 3600).to.equals(timings.slowest.seconds);
}); });
it('Can calc timings with different speed', function() { it('Can calc timings with different speed', function() {
@ -33,11 +33,11 @@ describe('Gathering timings', function() {
const desired = new Resources(60, 60, 60, 60); const desired = new Resources(60, 60, 60, 60);
const speed = new Resources(5, 10, 25, 5); const speed = new Resources(5, 10, 25, 5);
const timings = calcGatheringTimings(resources, desired, speed); const timings = calcGatheringTimings(resources, desired, speed);
expect(10).to.equals(timings.lumber); expect(10 * 3600).to.equals(timings.lumber.seconds);
expect(5).to.equals(timings.clay); expect(5 * 3600).to.equals(timings.clay.seconds);
expect(2).to.equals(timings.iron); expect(2 * 3600).to.equals(timings.iron.seconds);
expect(10).to.equals(timings.crop); expect(10 * 3600).to.equals(timings.crop.seconds);
expect(10).to.equals(timings.hours); expect(10 * 3600).to.equals(timings.slowest.seconds);
}); });
it('Can calc timings with negative speed', function() { it('Can calc timings with negative speed', function() {
@ -45,10 +45,10 @@ describe('Gathering timings', function() {
const desired = new Resources(60, 60, 60, 60); const desired = new Resources(60, 60, 60, 60);
const speed = new Resources(5, 10, 25, -5); const speed = new Resources(5, 10, 25, -5);
const timings = calcGatheringTimings(resources, desired, speed); const timings = calcGatheringTimings(resources, desired, speed);
expect(10).to.equals(timings.lumber); expect(10 * 3600).to.equals(timings.lumber.seconds);
expect(5).to.equals(timings.clay); expect(5 * 3600).to.equals(timings.clay.seconds);
expect(2).to.equals(timings.iron); expect(2 * 3600).to.equals(timings.iron.seconds);
expect('never').to.equals(timings.crop); expect(true).to.equals(timings.crop.never);
expect(true).to.equals(timings.never); expect(true).to.equals(timings.slowest.never);
}); });
}); });