From 7653c7b6e735c67444d63f9cf4d3c1ecb9d54ded Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Wed, 1 Jul 2020 13:36:09 +0300 Subject: [PATCH] Add crop buildings planning --- src/Action/TrainTrooperAction.ts | 2 +- src/Action/UpgradeResourceToLevel.ts | 10 ++--- src/ControlPanel.ts | 4 +- src/Game.ts | 16 ++++++-- src/Grabber/VillageOverviewPageGrabber.ts | 2 + src/Page/SlotBlock.ts | 23 ++++++------ src/Scheduler.ts | 16 ++++++-- src/Storage/VillageStorage.ts | 13 +++++++ src/VillageController.ts | 46 +++++++++++++++++++++++ src/VillageTaskCollection.ts | 18 +++++++-- 10 files changed, 120 insertions(+), 30 deletions(-) diff --git a/src/Action/TrainTrooperAction.ts b/src/Action/TrainTrooperAction.ts index e186265..d8d7916 100644 --- a/src/Action/TrainTrooperAction.ts +++ b/src/Action/TrainTrooperAction.ts @@ -25,7 +25,7 @@ export class TrainTrooperAction extends ActionController { const nextToTrainCount = trainCount - readyToTrainCount; if (readyToTrainCount <= 0) { - throw new TryLaterError(aroundMinutes(15), 'No ready to train troops'); + throw new TryLaterError(aroundMinutes(15), 'No isReady to train troops'); } if (nextToTrainCount > 0) { diff --git a/src/Action/UpgradeResourceToLevel.ts b/src/Action/UpgradeResourceToLevel.ts index 32c8195..9eed6c8 100644 --- a/src/Action/UpgradeResourceToLevel.ts +++ b/src/Action/UpgradeResourceToLevel.ts @@ -1,8 +1,8 @@ import { ActionController, registerAction } from './ActionController'; import { ActionError, taskError, TryLaterError } from '../Errors'; -import { grabResourceDeposits } from '../Page/SlotBlock'; +import { grabResourceSlots } from '../Page/SlotBlock'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; -import { ResourceDeposit } from '../Game'; +import { ResourceSlot } from '../Game'; import { aroundMinutes, getNumber } from '../utils'; import { Args } from '../Queue/Args'; import { Task } from '../Queue/TaskProvider'; @@ -10,7 +10,7 @@ import { Task } from '../Queue/TaskProvider'; @registerAction export class UpgradeResourceToLevel extends ActionController { async run(args: Args, task: Task): Promise { - const deposits = grabResourceDeposits(); + const deposits = grabResourceSlots(); if (deposits.length === 0) { throw new ActionError('No deposits'); } @@ -20,7 +20,7 @@ export class UpgradeResourceToLevel extends ActionController { const requiredLevel = getNumber(args.level); const notUpgraded = deposits.filter( - dep => !dep.underConstruction && requiredLevel > dep.level + dep => !dep.isUnderConstruction && requiredLevel > dep.level ); if (notUpgraded.length === 0) { @@ -52,7 +52,7 @@ export class UpgradeResourceToLevel extends ActionController { throw new TryLaterError(aroundMinutes(10), 'Sleep for next round'); } - private isTaskNotInQueue(villageId: number, dep: ResourceDeposit): boolean { + private isTaskNotInQueue(villageId: number, dep: ResourceSlot): boolean { const tasks = this.scheduler.getTaskItems(); return ( undefined === diff --git a/src/ControlPanel.ts b/src/ControlPanel.ts index a408f26..9d5dd72 100644 --- a/src/ControlPanel.ts +++ b/src/ControlPanel.ts @@ -4,7 +4,7 @@ import { BuildingPageController } from './Page/BuildingPageController'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; import { grabActiveVillageId } from './Page/VillageBlock'; import { - grabResourceDeposits, + grabResourceSlots, onBuildingSlotCtrlClick, onResourceSlotCtrlClick, showBuildingSlotIds, @@ -166,7 +166,7 @@ export class ControlPanel { } private createDepositsQuickActions(villageId: number) { - const deposits = grabResourceDeposits(); + const deposits = grabResourceSlots(); if (deposits.length === 0) { return []; } diff --git a/src/Game.ts b/src/Game.ts index 8e0bf76..a2d6afc 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -7,10 +7,20 @@ export class BuildingQueueInfo { } } -export interface ResourceDeposit { +export interface ResourceSlot { readonly buildId: number; readonly type: ResourceType; readonly level: number; - readonly ready: boolean; - readonly underConstruction: boolean; + readonly isReady: boolean; + readonly isUnderConstruction: boolean; + readonly isMaxLevel: boolean; } + +export const ResourceSlotDefaults: ResourceSlot = { + buildId: 0, + type: ResourceType.Lumber, + level: 0, + isReady: false, + isUnderConstruction: false, + isMaxLevel: false, +}; diff --git a/src/Grabber/VillageOverviewPageGrabber.ts b/src/Grabber/VillageOverviewPageGrabber.ts index 92b3dc8..9021dd7 100644 --- a/src/Grabber/VillageOverviewPageGrabber.ts +++ b/src/Grabber/VillageOverviewPageGrabber.ts @@ -4,6 +4,7 @@ import { parseLocation, timestamp } from '../utils'; import { GrabError } from '../Errors'; import { BuildingQueueInfo } from '../Game'; import { ProductionQueue } from '../Core/ProductionQueue'; +import { grabResourceSlots } from '../Page/SlotBlock'; export class VillageOverviewPageGrabber extends Grabber { grab(): void { @@ -13,6 +14,7 @@ export class VillageOverviewPageGrabber extends Grabber { } this.storage.storeResourcesPerformance(grabResourcesPerformance()); + this.storage.storeResourceSlots(grabResourceSlots()); const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); const buildingEndTime = buildingQueueInfo.seconds diff --git a/src/Page/SlotBlock.ts b/src/Page/SlotBlock.ts index 49ca1c5..c465cd7 100644 --- a/src/Page/SlotBlock.ts +++ b/src/Page/SlotBlock.ts @@ -1,14 +1,14 @@ import { elClassId, getNumber } from '../utils'; -import { ResourceDeposit } from '../Game'; +import { ResourceSlot } from '../Game'; import { numberToResourceType } from '../Core/ResourceType'; -interface Slot { +interface SlotElement { el: HTMLElement; buildId: number; } -function slotElements(prefix: string): Array { - const result: Array = []; +function slotElements(prefix: string): Array { + const result: Array = []; jQuery('.level.colorLayer').each((idx, el) => { const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix)); result.push({ el, buildId }); @@ -74,18 +74,19 @@ export function onBuildingSlotCtrlClick(onClickHandler: (buildId: number) => voi onSlotCtrlClick('aid', onClickHandler); } -function slotToDepositMapper(slot: Slot): ResourceDeposit { - const el = slot.el; - const classes = jQuery(el).attr('class'); +function makeResourceSlot(slot: SlotElement): ResourceSlot { + const $el = jQuery(slot.el); + const classes = $el.attr('class'); return { buildId: getNumber(elClassId(classes, 'buildingSlot')), type: numberToResourceType(getNumber(elClassId(classes, 'gid'))), level: getNumber(elClassId(classes, 'level')), - ready: !jQuery(el).hasClass('notNow'), - underConstruction: jQuery(el).hasClass('underConstruction'), + isReady: !$el.hasClass('notNow'), + isUnderConstruction: $el.hasClass('underConstruction'), + isMaxLevel: $el.hasClass('maxLevel'), }; } -export function grabResourceDeposits(): Array { - return slotElements('buildingSlot').map(slotToDepositMapper); +export function grabResourceSlots(): Array { + return slotElements('buildingSlot').map(makeResourceSlot); } diff --git a/src/Scheduler.ts b/src/Scheduler.ts index 9f86e08..5b43a22 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -74,7 +74,7 @@ export class Scheduler { nextTask(ts: number): NextExecution { const task = this.taskQueue.get(ts); - // Task not found - next task not ready or queue is empty + // Task not found - next task not isReady or queue is empty if (!task) { this.clearActions(); return {}; @@ -99,13 +99,21 @@ export class Scheduler { private replaceTask(task: Task): Task | undefined { if (task.name === RunVillageProductionTask.name && task.args.villageId) { const villageId = task.args.villageId; - const controller = this.villageControllerFactory.createController(villageId); - const villageTask = controller.getReadyProductionTask(); + + // First stage - plan new tasks if needed. + const controllerForPlan = this.villageControllerFactory.createController(villageId); + controllerForPlan.planTasks(); + + // Second stage - select isReady for production task. + // We recreate controller, because need new village state. + const controllerForSelect = this.villageControllerFactory.createController(villageId); + const villageTask = controllerForSelect.getReadyProductionTask(); + if (villageTask) { this.removeTask(task.id); const newTask = new Task(villageTask.id, 0, villageTask.name, { ...villageTask.args, - villageId: controller.getVillageId(), + villageId: controllerForSelect.getVillageId(), }); this.taskQueue.add(newTask); return newTask; diff --git a/src/Storage/VillageStorage.ts b/src/Storage/VillageStorage.ts index f92a40a..e2a0a53 100644 --- a/src/Storage/VillageStorage.ts +++ b/src/Storage/VillageStorage.ts @@ -6,12 +6,15 @@ import { VillageSettings, VillageSettingsDefaults } from '../Core/Village'; import { ProductionQueue } from '../Core/ProductionQueue'; import { getNumber } from '../utils'; import { Task, uniqTaskId } from '../Queue/TaskProvider'; +import { ResourceSlot, ResourceSlotDefaults } from '../Game'; const RESOURCES_KEY = 'resources'; const CAPACITY_KEY = 'capacity'; const PERFORMANCE_KEY = 'performance'; const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; const MERCHANTS_INFO_KEY = 'merchants_info'; +const RESOURCE_SLOTS_KEY = 'resource_slots'; +const BUILDING_SLOTS_KEY = 'building_slots'; const SETTINGS_KEY = 'settings'; const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; const TASK_LIST_KEY = 'tasks'; @@ -83,6 +86,16 @@ export class VillageStorage { }); } + getResourceSlots(): ReadonlyArray { + return this.storage.getTypedList(RESOURCE_SLOTS_KEY, { + factory: () => Object.assign({}, ResourceSlotDefaults), + }); + } + + storeResourceSlots(slots: ReadonlyArray): void { + this.storage.set(RESOURCE_SLOTS_KEY, slots); + } + getSettings(): VillageSettings { return this.storage.getTyped(SETTINGS_KEY, { factory: () => Object.assign({}, VillageSettingsDefaults), diff --git a/src/VillageController.ts b/src/VillageController.ts index 3d018e8..f4fc030 100644 --- a/src/VillageController.ts +++ b/src/VillageController.ts @@ -6,6 +6,9 @@ import { Resources } from './Core/Resources'; import { MerchantsInfo } from './Core/Market'; import { VillageStorage } from './Storage/VillageStorage'; import { ReceiveResourcesMode } from './Core/Village'; +import { ResourceType } from './Core/ResourceType'; +import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; +import * as _ from 'underscore'; export class VillageController { private readonly villageId: number; @@ -147,4 +150,47 @@ export class VillageController { const newSettings = { ...this.state.settings, receiveResourcesMode: next }; this.storage.storeSettings(newSettings); } + + planTasks(): void { + const performance = this.state.performance; + + if (performance.crop < 100) { + this.planCropBuilding(); + } + } + + private planCropBuilding() { + const resourceSlots = this.storage.getResourceSlots(); + const tasks = this.taskCollection.getTasks(); + + const cropSlots = resourceSlots.filter(s => s.type === ResourceType.Crop); + + // Check, if crop field is building now + const isCropBuilding = cropSlots.filter(s => s.isUnderConstruction); + if (isCropBuilding) { + return; + } + + // Check, if we already have crop task in queue + const cropBuildIds = cropSlots.map(s => s.buildId); + const cropBuildingTaskInQueue = tasks.find( + t => t.args.buildId && cropBuildIds.includes(t.args.buildId) + ); + if (cropBuildingTaskInQueue !== undefined) { + return; + } + + // Find ready for building slots and sort them by level + const readyCropSlots = cropSlots.filter(s => !s.isMaxLevel); + readyCropSlots.sort((s1, s2) => s1.level - s2.level); + + const targetCropBuildId = _.first(readyCropSlots)?.buildId; + if (!targetCropBuildId) { + return; + } + + this.taskCollection.addTaskAsFirst(UpgradeBuildingTask.name, { + buildId: targetCropBuildId, + }); + } } diff --git a/src/VillageTaskCollection.ts b/src/VillageTaskCollection.ts index f102c08..ab4bd8b 100644 --- a/src/VillageTaskCollection.ts +++ b/src/VillageTaskCollection.ts @@ -40,19 +40,29 @@ export class VillageTaskCollection { } addTask(name: string, args: Args) { + const tasks = this.getTasks(); + tasks.push(this.createVillageTask(name, args)); + this.storage.storeTaskList(tasks); + } + + addTaskAsFirst(name: string, args: Args) { + const tasks = this.getTasks(); + tasks.unshift(this.createVillageTask(name, args)); + this.storage.storeTaskList(tasks); + } + + private createVillageTask(name: string, args: Args): Task { if (!isProductionTask(name)) { throw new Error(`Task "${name}" is not production task`); } + if (args.villageId !== this.villageId) { throw new Error( `Task village id (${args.villageId}) not equal controller village id (${this.villageId}` ); } - const task = new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args }); - const tasks = this.getTasks(); - tasks.push(task); - this.storage.storeTaskList(tasks); + return new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args }); } removeTask(taskId: TaskId) {