From 57ccf145533882cb4e67ec9a9c59a4b77cc18731 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sat, 18 Apr 2020 15:33:32 +0300 Subject: [PATCH] Fix insert build tasks, so order is matter --- .../CheckBuildingRemainingTimeAction.ts | 16 +++++++-- src/Action/UpgradeResourceToLevel.ts | 18 +++++----- src/Common.ts | 3 ++ src/Game.ts | 9 ++++- src/Page/BuildPage.ts | 8 +++-- src/Page/BuildingPage.ts | 20 +++++++++++ src/Page/VillageBlock.ts | 4 +-- src/Scheduler.ts | 28 ++++++++++++---- src/State/VillageOverviewPageGrabber.ts | 15 ++++++++- src/Storage/TaskQueue.ts | 33 +++++++++++++++++++ src/utils.ts | 6 ++++ 11 files changed, 134 insertions(+), 26 deletions(-) diff --git a/src/Action/CheckBuildingRemainingTimeAction.ts b/src/Action/CheckBuildingRemainingTimeAction.ts index 3e04c2c..9e8facd 100644 --- a/src/Action/CheckBuildingRemainingTimeAction.ts +++ b/src/Action/CheckBuildingRemainingTimeAction.ts @@ -1,13 +1,14 @@ import { ActionController, registerAction } from './ActionController'; import { Args } from '../Common'; import { Task } from '../Storage/TaskQueue'; -import { BuildingQueueFullError } from '../Errors'; +import { BuildingQueueFullError, GrabError } from '../Errors'; import { grabActiveVillageId, grabBuildingQueueInfo } from '../Page/VillageBlock'; +import { BuildingQueueInfo } from '../Game'; @registerAction export class CheckBuildingRemainingTimeAction extends ActionController { async run(args: Args, task: Task): Promise { - const info = grabBuildingQueueInfo(); + const info = this.grabBuildingQueueInfoOrDefault(); if (info.seconds > 0) { throw new BuildingQueueFullError( task.id, @@ -17,4 +18,15 @@ export class CheckBuildingRemainingTimeAction extends ActionController { ); } } + + private grabBuildingQueueInfoOrDefault() { + try { + return grabBuildingQueueInfo(); + } catch (e) { + if (e instanceof GrabError) { + return new BuildingQueueInfo(0); + } + throw e; + } + } } diff --git a/src/Action/UpgradeResourceToLevel.ts b/src/Action/UpgradeResourceToLevel.ts index 5393ecc..2b93982 100644 --- a/src/Action/UpgradeResourceToLevel.ts +++ b/src/Action/UpgradeResourceToLevel.ts @@ -6,6 +6,7 @@ import { clickUpgradeButton } from '../Page/BuildingPage'; import { grabResourceDeposits } from '../Page/SlotBlock'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { ResourceDeposit } from '../Game'; +import { aroundMinutes } from '../utils'; @registerAction export class UpgradeResourceToLevel extends ActionController { @@ -35,19 +36,16 @@ export class UpgradeResourceToLevel extends ActionController { task.args.buildId === dep.buildId ); - const available = deposits - .sort((x, y) => x.level - y.level) - .filter(dep => dep.ready) - .filter(isDepositTaskNotInQueue); + const notUpgraded = deposits.sort((x, y) => x.level - y.level).filter(isDepositTaskNotInQueue); - if (available.length === 0) { - throw new TryLaterError(task.id, 10 * 60, 'No available deposits'); + if (notUpgraded.length === 0) { + throw new TryLaterError(task.id, aroundMinutes(10), 'No available deposits'); } - const targetDep = available[0]; + for (let dep of notUpgraded) { + this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId: dep.buildId }); + } - this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId: targetDep.buildId }); - - throw new TryLaterError(task.id, 20 * 60, 'Sleep for next round'); + throw new TryLaterError(task.id, aroundMinutes(10), 'Sleep for next round'); } } diff --git a/src/Common.ts b/src/Common.ts index cdd8e7e..655f47c 100644 --- a/src/Common.ts +++ b/src/Common.ts @@ -1,8 +1,11 @@ +import { ResourcesInterface } from './Game'; + export interface Args { villageId?: number; buildId?: number; categoryId?: number; buildTypeId?: number; + resources?: ResourcesInterface; [name: string]: any; } diff --git a/src/Game.ts b/src/Game.ts index 6d0059f..e4e95e2 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -23,7 +23,14 @@ export function numberToResourceType(typeAsNumber: number): ResourceType { export type ResourceList = Array<{ num: number; type: ResourceType; value: number }>; -export class Resources { +export interface ResourcesInterface { + lumber: number; + clay: number; + iron: number; + crop: number; +} + +export class Resources implements ResourcesInterface { readonly lumber: number; readonly clay: number; readonly iron: number; diff --git a/src/Page/BuildPage.ts b/src/Page/BuildPage.ts index e39769f..15f11fe 100644 --- a/src/Page/BuildPage.ts +++ b/src/Page/BuildPage.ts @@ -4,7 +4,7 @@ import { Scheduler } from '../Scheduler'; import { TrainTroopTask } from '../Task/TrainTroopTask'; import { grabActiveVillageId } from './VillageBlock'; import { ConsoleLogger, Logger } from '../Logger'; -import { createBuildButton, createUpgradeButton } from './BuildingPage'; +import { createBuildButton, createUpgradeButton, grabContractResources } from './BuildingPage'; import { BuildBuildingTask } from '../Task/BuildBuildingTask'; const QUARTERS_ID = 19; @@ -38,14 +38,16 @@ export class BuildPage { const buildId = this.buildId; const categoryId = this.categoryId; const villageId = grabActiveVillageId(); - this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId }); + const resources = grabContractResources(); + this.scheduler.scheduleTask(BuildBuildingTask.name, { villageId, buildId, categoryId, buildTypeId, resources }); notify(`Building ${buildId} scheduled`); } private onScheduleUpgradeBuilding() { const buildId = this.buildId; const villageId = grabActiveVillageId(); - this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId }); + const resources = grabContractResources(); + this.scheduler.scheduleTask(UpgradeBuildingTask.name, { villageId, buildId, resources }); notify(`Upgrading ${buildId} scheduled`); } diff --git a/src/Page/BuildingPage.ts b/src/Page/BuildingPage.ts index 5a48149..2182bad 100644 --- a/src/Page/BuildingPage.ts +++ b/src/Page/BuildingPage.ts @@ -1,5 +1,6 @@ import { GrabError } from '../Errors'; import { getNumber, trimPrefix, uniqId } from '../utils'; +import { Resources } from '../Game'; export function clickBuildButton(typeId: number) { const section = jQuery(`#contract_building${typeId}`); @@ -27,6 +28,11 @@ export function createBuildButton(onClickHandler: (buildTypeId: number) => void) }); } +export function hasUpgradeButton(): boolean { + const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); + return btn.length === 1; +} + export function clickUpgradeButton() { const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); if (btn.length !== 1) { @@ -45,3 +51,17 @@ export function createUpgradeButton(onClickHandler: () => void) { onClickHandler(); }); } + +export function grabContractResources(): Resources { + const $els = jQuery('#contract .resource'); + if ($els.length === 0) { + throw new GrabError('No resource contract element'); + } + const grab = n => + getNumber( + jQuery($els.get(n)) + .find('.value') + .text() + ); + return new Resources(grab(0), grab(1), grab(2), grab(3)); +} diff --git a/src/Page/VillageBlock.ts b/src/Page/VillageBlock.ts index 375ec62..6f7f3bb 100644 --- a/src/Page/VillageBlock.ts +++ b/src/Page/VillageBlock.ts @@ -47,7 +47,7 @@ function grabVillageInfo($el): Village { export function grabResourcesPerformance(): Resources { const $el = jQuery('#production'); if ($el.length !== 1) { - throw new GrabError(); + throw new GrabError('No production element'); } const $nums = $el.find('td.num'); @@ -63,7 +63,7 @@ export function grabResourcesPerformance(): Resources { export function grabBuildingQueueInfo(): BuildingQueueInfo { const timer = jQuery('.buildDuration .timer'); if (timer.length !== 1) { - throw new GrabError(); + throw new GrabError('No building queue timer element'); } const remainingSeconds = getNumber(timer.attr('value')); diff --git a/src/Scheduler.ts b/src/Scheduler.ts index b363b0e..a9767cc 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -1,6 +1,6 @@ import { timestamp } from './utils'; import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; -import { TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; +import { Task, TaskId, TaskList, TaskQueue } from './Storage/TaskQueue'; import { Args, Command } from './Common'; import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; @@ -50,7 +50,17 @@ export class Scheduler { scheduleTask(name: string, args: Args): void { this.logger.log('PUSH TASK', name, args); - this.taskQueue.push(name, args, timestamp()); + const villageId = args.villageId; + if (isBuildingTask(name)) { + this.taskQueue.insertAfterLast( + t => isBuildingTask(t.name) && sameVillage(villageId, t.args), + name, + args, + timestamp() + ); + } else { + this.taskQueue.push(name, args, timestamp()); + } } completeTask(id: TaskId) { @@ -69,11 +79,7 @@ export class Scheduler { postponeBuildingsInVillage(villageId: number, seconds: number) { this.taskQueue.modify( - t => t.name === BuildBuildingTask.name && t.args.villageId === villageId, - t => t.withTime(timestamp() + seconds) - ); - this.taskQueue.modify( - t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId, + t => isBuildingTask(t.name) && sameVillage(villageId, t.args), t => t.withTime(timestamp() + seconds) ); } @@ -86,3 +92,11 @@ export class Scheduler { this.actionQueue.clear(); } } + +function isBuildingTask(taskName: string) { + return taskName === BuildBuildingTask.name || taskName === UpgradeBuildingTask.name; +} + +function sameVillage(villageId: number | undefined, args: Args) { + return villageId !== undefined && args.villageId === villageId; +} diff --git a/src/State/VillageOverviewPageGrabber.ts b/src/State/VillageOverviewPageGrabber.ts index 1350736..4439946 100644 --- a/src/State/VillageOverviewPageGrabber.ts +++ b/src/State/VillageOverviewPageGrabber.ts @@ -2,6 +2,8 @@ import { StateGrabber } from './StateGrabber'; import { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock'; import { VillageState } from './VillageState'; import { parseLocation } from '../utils'; +import { GrabError } from '../Errors'; +import { BuildingQueueInfo } from '../Game'; export class VillageOverviewPageGrabber extends StateGrabber { grab(): void { @@ -13,6 +15,17 @@ export class VillageOverviewPageGrabber extends StateGrabber { const villageId = grabActiveVillageId(); const state = new VillageState(villageId); state.storeResourcesPerformance(grabResourcesPerformance()); - state.storeBuildingQueueInfo(grabBuildingQueueInfo()); + state.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault()); + } + + private grabBuildingQueueInfoOrDefault() { + try { + return grabBuildingQueueInfo(); + } catch (e) { + if (e instanceof GrabError) { + return new BuildingQueueInfo(0); + } + throw e; + } } } diff --git a/src/Storage/TaskQueue.ts b/src/Storage/TaskQueue.ts index f29944e..1625421 100644 --- a/src/Storage/TaskQueue.ts +++ b/src/Storage/TaskQueue.ts @@ -50,6 +50,15 @@ export class TaskQueue { return task; } + insertAfterLast(predicate: (t: Task) => boolean, name: string, args: Args, ts: number): Task { + const id = uniqTaskId(); + const task = new Task(id, ts, name, args); + this.logger.log('INSERT AFTER TASK', id, ts, name, args); + const items = insertTaskAfter(task, this.getItems(), predicate); + this.flushItems(items); + return task; + } + get(ts: number): Task | undefined { const readyItems = this.getItems().filter(t => t.ts <= ts); if (readyItems.length === 0) { @@ -133,3 +142,27 @@ export class TaskQueue { this.storage.set(QUEUE_NAME, normalized); } } + +function insertTaskAfter(task: Task, tasks: TaskList, predicate: (t: Task) => boolean): TaskList { + const queuedTaskIndex = findLastIndex(tasks, predicate); + if (queuedTaskIndex === undefined) { + tasks.push(task); + return tasks; + } + const queuedTask = tasks[queuedTaskIndex]; + let insertedTask = task.withTime(Math.max(task.ts, queuedTask.ts + 1)); + tasks.splice(queuedTaskIndex, 0, insertedTask); + return tasks; +} + +function findLastIndex(tasks: TaskList, predicate: (t: Task) => boolean): number | undefined { + const count = tasks.length; + const indexInReversed = tasks + .slice() + .reverse() + .findIndex(predicate); + if (indexInReversed < 0) { + return undefined; + } + return count - 1 - indexInReversed; +} diff --git a/src/utils.ts b/src/utils.ts index b9a0ad9..af67a84 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,6 +22,12 @@ export async function sleepLong() { return await sleep(ms); } +export function aroundMinutes(minutes: number) { + const seconds = minutes * 60; + const delta = Math.floor(seconds * 0.9); + return seconds - delta + Math.floor(Math.random() * 2 * delta); +} + export async function waitForLoad() { return new Promise(resolve => jQuery(resolve)); }