Add crop buildings planning

This commit is contained in:
Anton Vakhrushev 2020-07-01 13:36:09 +03:00
parent 48bfed5f01
commit 7653c7b6e7
10 changed files with 120 additions and 30 deletions

View File

@ -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) {

View File

@ -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<any> {
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 ===

View File

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

View File

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

View File

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

View File

@ -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<Slot> {
const result: Array<Slot> = [];
function slotElements(prefix: string): Array<SlotElement> {
const result: Array<SlotElement> = [];
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<ResourceDeposit> {
return slotElements('buildingSlot').map(slotToDepositMapper);
export function grabResourceSlots(): Array<ResourceSlot> {
return slotElements('buildingSlot').map(makeResourceSlot);
}

View File

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

View File

@ -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<ResourceSlot> {
return this.storage.getTypedList<ResourceSlot>(RESOURCE_SLOTS_KEY, {
factory: () => Object.assign({}, ResourceSlotDefaults),
});
}
storeResourceSlots(slots: ReadonlyArray<ResourceSlot>): void {
this.storage.set(RESOURCE_SLOTS_KEY, slots);
}
getSettings(): VillageSettings {
return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, {
factory: () => Object.assign({}, VillageSettingsDefaults),

View File

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

View File

@ -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) {