Fix insert build tasks, so order is matter

This commit is contained in:
Anton Vakhrushev 2020-04-18 15:33:32 +03:00
parent db16c54137
commit 57ccf14553
11 changed files with 134 additions and 26 deletions

View File

@ -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<any> {
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;
}
}
}

View File

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

View File

@ -1,8 +1,11 @@
import { ResourcesInterface } from './Game';
export interface Args {
villageId?: number;
buildId?: number;
categoryId?: number;
buildTypeId?: number;
resources?: ResourcesInterface;
[name: string]: any;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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,8 +50,18 @@ export class Scheduler {
scheduleTask(name: string, args: Args): void {
this.logger.log('PUSH TASK', name, args);
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) {
this.taskQueue.complete(id);
@ -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;
}

View File

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

View File

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

View File

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