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; const nextToTrainCount = trainCount - readyToTrainCount;
if (readyToTrainCount <= 0) { 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) { if (nextToTrainCount > 0) {

View File

@ -1,8 +1,8 @@
import { ActionController, registerAction } from './ActionController'; import { ActionController, registerAction } from './ActionController';
import { ActionError, taskError, TryLaterError } from '../Errors'; import { ActionError, taskError, TryLaterError } from '../Errors';
import { grabResourceDeposits } from '../Page/SlotBlock'; import { grabResourceSlots } from '../Page/SlotBlock';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { ResourceDeposit } from '../Game'; import { ResourceSlot } from '../Game';
import { aroundMinutes, getNumber } from '../utils'; import { aroundMinutes, getNumber } from '../utils';
import { Args } from '../Queue/Args'; import { Args } from '../Queue/Args';
import { Task } from '../Queue/TaskProvider'; import { Task } from '../Queue/TaskProvider';
@ -10,7 +10,7 @@ import { Task } from '../Queue/TaskProvider';
@registerAction @registerAction
export class UpgradeResourceToLevel extends ActionController { export class UpgradeResourceToLevel extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const deposits = grabResourceDeposits(); const deposits = grabResourceSlots();
if (deposits.length === 0) { if (deposits.length === 0) {
throw new ActionError('No deposits'); throw new ActionError('No deposits');
} }
@ -20,7 +20,7 @@ export class UpgradeResourceToLevel extends ActionController {
const requiredLevel = getNumber(args.level); const requiredLevel = getNumber(args.level);
const notUpgraded = deposits.filter( const notUpgraded = deposits.filter(
dep => !dep.underConstruction && requiredLevel > dep.level dep => !dep.isUnderConstruction && requiredLevel > dep.level
); );
if (notUpgraded.length === 0) { if (notUpgraded.length === 0) {
@ -52,7 +52,7 @@ export class UpgradeResourceToLevel extends ActionController {
throw new TryLaterError(aroundMinutes(10), 'Sleep for next round'); 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(); const tasks = this.scheduler.getTaskItems();
return ( return (
undefined === undefined ===

View File

@ -4,7 +4,7 @@ import { BuildingPageController } from './Page/BuildingPageController';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { grabActiveVillageId } from './Page/VillageBlock'; import { grabActiveVillageId } from './Page/VillageBlock';
import { import {
grabResourceDeposits, grabResourceSlots,
onBuildingSlotCtrlClick, onBuildingSlotCtrlClick,
onResourceSlotCtrlClick, onResourceSlotCtrlClick,
showBuildingSlotIds, showBuildingSlotIds,
@ -166,7 +166,7 @@ export class ControlPanel {
} }
private createDepositsQuickActions(villageId: number) { private createDepositsQuickActions(villageId: number) {
const deposits = grabResourceDeposits(); const deposits = grabResourceSlots();
if (deposits.length === 0) { if (deposits.length === 0) {
return []; return [];
} }

View File

@ -7,10 +7,20 @@ export class BuildingQueueInfo {
} }
} }
export interface ResourceDeposit { export interface ResourceSlot {
readonly buildId: number; readonly buildId: number;
readonly type: ResourceType; readonly type: ResourceType;
readonly level: number; readonly level: number;
readonly ready: boolean; readonly isReady: boolean;
readonly underConstruction: 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 { GrabError } from '../Errors';
import { BuildingQueueInfo } from '../Game'; import { BuildingQueueInfo } from '../Game';
import { ProductionQueue } from '../Core/ProductionQueue'; import { ProductionQueue } from '../Core/ProductionQueue';
import { grabResourceSlots } from '../Page/SlotBlock';
export class VillageOverviewPageGrabber extends Grabber { export class VillageOverviewPageGrabber extends Grabber {
grab(): void { grab(): void {
@ -13,6 +14,7 @@ export class VillageOverviewPageGrabber extends Grabber {
} }
this.storage.storeResourcesPerformance(grabResourcesPerformance()); this.storage.storeResourcesPerformance(grabResourcesPerformance());
this.storage.storeResourceSlots(grabResourceSlots());
const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault(); const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault();
const buildingEndTime = buildingQueueInfo.seconds const buildingEndTime = buildingQueueInfo.seconds

View File

@ -1,14 +1,14 @@
import { elClassId, getNumber } from '../utils'; import { elClassId, getNumber } from '../utils';
import { ResourceDeposit } from '../Game'; import { ResourceSlot } from '../Game';
import { numberToResourceType } from '../Core/ResourceType'; import { numberToResourceType } from '../Core/ResourceType';
interface Slot { interface SlotElement {
el: HTMLElement; el: HTMLElement;
buildId: number; buildId: number;
} }
function slotElements(prefix: string): Array<Slot> { function slotElements(prefix: string): Array<SlotElement> {
const result: Array<Slot> = []; const result: Array<SlotElement> = [];
jQuery('.level.colorLayer').each((idx, el) => { jQuery('.level.colorLayer').each((idx, el) => {
const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix)); const buildId = getNumber(elClassId(jQuery(el).attr('class'), prefix));
result.push({ el, buildId }); result.push({ el, buildId });
@ -74,18 +74,19 @@ export function onBuildingSlotCtrlClick(onClickHandler: (buildId: number) => voi
onSlotCtrlClick('aid', onClickHandler); onSlotCtrlClick('aid', onClickHandler);
} }
function slotToDepositMapper(slot: Slot): ResourceDeposit { function makeResourceSlot(slot: SlotElement): ResourceSlot {
const el = slot.el; const $el = jQuery(slot.el);
const classes = jQuery(el).attr('class'); const classes = $el.attr('class');
return { return {
buildId: getNumber(elClassId(classes, 'buildingSlot')), buildId: getNumber(elClassId(classes, 'buildingSlot')),
type: numberToResourceType(getNumber(elClassId(classes, 'gid'))), type: numberToResourceType(getNumber(elClassId(classes, 'gid'))),
level: getNumber(elClassId(classes, 'level')), level: getNumber(elClassId(classes, 'level')),
ready: !jQuery(el).hasClass('notNow'), isReady: !$el.hasClass('notNow'),
underConstruction: jQuery(el).hasClass('underConstruction'), isUnderConstruction: $el.hasClass('underConstruction'),
isMaxLevel: $el.hasClass('maxLevel'),
}; };
} }
export function grabResourceDeposits(): Array<ResourceDeposit> { export function grabResourceSlots(): Array<ResourceSlot> {
return slotElements('buildingSlot').map(slotToDepositMapper); return slotElements('buildingSlot').map(makeResourceSlot);
} }

View File

@ -74,7 +74,7 @@ export class Scheduler {
nextTask(ts: number): NextExecution { nextTask(ts: number): NextExecution {
const task = this.taskQueue.get(ts); 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) { if (!task) {
this.clearActions(); this.clearActions();
return {}; return {};
@ -99,13 +99,21 @@ export class Scheduler {
private replaceTask(task: Task): Task | undefined { private replaceTask(task: Task): Task | undefined {
if (task.name === RunVillageProductionTask.name && task.args.villageId) { if (task.name === RunVillageProductionTask.name && task.args.villageId) {
const villageId = 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) { if (villageTask) {
this.removeTask(task.id); this.removeTask(task.id);
const newTask = new Task(villageTask.id, 0, villageTask.name, { const newTask = new Task(villageTask.id, 0, villageTask.name, {
...villageTask.args, ...villageTask.args,
villageId: controller.getVillageId(), villageId: controllerForSelect.getVillageId(),
}); });
this.taskQueue.add(newTask); this.taskQueue.add(newTask);
return newTask; return newTask;

View File

@ -6,12 +6,15 @@ import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
import { ProductionQueue } from '../Core/ProductionQueue'; import { ProductionQueue } from '../Core/ProductionQueue';
import { getNumber } from '../utils'; import { getNumber } from '../utils';
import { Task, uniqTaskId } from '../Queue/TaskProvider'; import { Task, uniqTaskId } from '../Queue/TaskProvider';
import { ResourceSlot, ResourceSlotDefaults } from '../Game';
const RESOURCES_KEY = 'resources'; const RESOURCES_KEY = 'resources';
const CAPACITY_KEY = 'capacity'; const CAPACITY_KEY = 'capacity';
const PERFORMANCE_KEY = 'performance'; const PERFORMANCE_KEY = 'performance';
const INCOMING_MERCHANTS_KEY = 'incoming_merchants'; const INCOMING_MERCHANTS_KEY = 'incoming_merchants';
const MERCHANTS_INFO_KEY = 'merchants_info'; const MERCHANTS_INFO_KEY = 'merchants_info';
const RESOURCE_SLOTS_KEY = 'resource_slots';
const BUILDING_SLOTS_KEY = 'building_slots';
const SETTINGS_KEY = 'settings'; const SETTINGS_KEY = 'settings';
const QUEUE_ENDING_TIME_KEY = 'queue_ending_time'; const QUEUE_ENDING_TIME_KEY = 'queue_ending_time';
const TASK_LIST_KEY = 'tasks'; 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 { getSettings(): VillageSettings {
return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, { return this.storage.getTyped<VillageSettings>(SETTINGS_KEY, {
factory: () => Object.assign({}, VillageSettingsDefaults), factory: () => Object.assign({}, VillageSettingsDefaults),

View File

@ -6,6 +6,9 @@ import { Resources } from './Core/Resources';
import { MerchantsInfo } from './Core/Market'; import { MerchantsInfo } from './Core/Market';
import { VillageStorage } from './Storage/VillageStorage'; import { VillageStorage } from './Storage/VillageStorage';
import { ReceiveResourcesMode } from './Core/Village'; import { ReceiveResourcesMode } from './Core/Village';
import { ResourceType } from './Core/ResourceType';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import * as _ from 'underscore';
export class VillageController { export class VillageController {
private readonly villageId: number; private readonly villageId: number;
@ -147,4 +150,47 @@ export class VillageController {
const newSettings = { ...this.state.settings, receiveResourcesMode: next }; const newSettings = { ...this.state.settings, receiveResourcesMode: next };
this.storage.storeSettings(newSettings); 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) { 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)) { if (!isProductionTask(name)) {
throw new Error(`Task "${name}" is not production task`); throw new Error(`Task "${name}" is not production task`);
} }
if (args.villageId !== this.villageId) { if (args.villageId !== this.villageId) {
throw new Error( throw new Error(
`Task village id (${args.villageId}) not equal controller village id (${this.villageId}` `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(); return new Task(uniqTaskId(), 0, name, { villageId: this.villageId, ...args });
tasks.push(task);
this.storage.storeTaskList(tasks);
} }
removeTask(taskId: TaskId) { removeTask(taskId: TaskId) {