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 { ActionController, registerAction } from './ActionController';
import { Args } from '../Common'; import { Args } from '../Common';
import { Task } from '../Storage/TaskQueue'; import { Task } from '../Storage/TaskQueue';
import { BuildingQueueFullError } from '../Errors'; import { BuildingQueueFullError, GrabError } from '../Errors';
import { grabActiveVillageId, grabBuildingQueueInfo } from '../Page/VillageBlock'; import { grabActiveVillageId, grabBuildingQueueInfo } from '../Page/VillageBlock';
import { BuildingQueueInfo } from '../Game';
@registerAction @registerAction
export class CheckBuildingRemainingTimeAction extends ActionController { export class CheckBuildingRemainingTimeAction extends ActionController {
async run(args: Args, task: Task): Promise<any> { async run(args: Args, task: Task): Promise<any> {
const info = grabBuildingQueueInfo(); const info = this.grabBuildingQueueInfoOrDefault();
if (info.seconds > 0) { if (info.seconds > 0) {
throw new BuildingQueueFullError( throw new BuildingQueueFullError(
task.id, 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 { grabResourceDeposits } from '../Page/SlotBlock';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask'; import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { ResourceDeposit } from '../Game'; import { ResourceDeposit } from '../Game';
import { aroundMinutes } from '../utils';
@registerAction @registerAction
export class UpgradeResourceToLevel extends ActionController { export class UpgradeResourceToLevel extends ActionController {
@ -35,19 +36,16 @@ export class UpgradeResourceToLevel extends ActionController {
task.args.buildId === dep.buildId task.args.buildId === dep.buildId
); );
const available = deposits const notUpgraded = deposits.sort((x, y) => x.level - y.level).filter(isDepositTaskNotInQueue);
.sort((x, y) => x.level - y.level)
.filter(dep => dep.ready)
.filter(isDepositTaskNotInQueue);
if (available.length === 0) { if (notUpgraded.length === 0) {
throw new TryLaterError(task.id, 10 * 60, 'No available deposits'); 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, aroundMinutes(10), 'Sleep for next round');
throw new TryLaterError(task.id, 20 * 60, 'Sleep for next round');
} }
} }

View File

@ -1,8 +1,11 @@
import { ResourcesInterface } from './Game';
export interface Args { export interface Args {
villageId?: number; villageId?: number;
buildId?: number; buildId?: number;
categoryId?: number; categoryId?: number;
buildTypeId?: number; buildTypeId?: number;
resources?: ResourcesInterface;
[name: string]: any; [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 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 lumber: number;
readonly clay: number; readonly clay: number;
readonly iron: number; readonly iron: number;

View File

@ -4,7 +4,7 @@ import { Scheduler } from '../Scheduler';
import { TrainTroopTask } from '../Task/TrainTroopTask'; import { TrainTroopTask } from '../Task/TrainTroopTask';
import { grabActiveVillageId } from './VillageBlock'; import { grabActiveVillageId } from './VillageBlock';
import { ConsoleLogger, Logger } from '../Logger'; import { ConsoleLogger, Logger } from '../Logger';
import { createBuildButton, createUpgradeButton } from './BuildingPage'; import { createBuildButton, createUpgradeButton, grabContractResources } from './BuildingPage';
import { BuildBuildingTask } from '../Task/BuildBuildingTask'; import { BuildBuildingTask } from '../Task/BuildBuildingTask';
const QUARTERS_ID = 19; const QUARTERS_ID = 19;
@ -38,14 +38,16 @@ export class BuildPage {
const buildId = this.buildId; const buildId = this.buildId;
const categoryId = this.categoryId; const categoryId = this.categoryId;
const villageId = grabActiveVillageId(); 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`); notify(`Building ${buildId} scheduled`);
} }
private onScheduleUpgradeBuilding() { private onScheduleUpgradeBuilding() {
const buildId = this.buildId; const buildId = this.buildId;
const villageId = grabActiveVillageId(); 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`); notify(`Upgrading ${buildId} scheduled`);
} }

View File

@ -1,5 +1,6 @@
import { GrabError } from '../Errors'; import { GrabError } from '../Errors';
import { getNumber, trimPrefix, uniqId } from '../utils'; import { getNumber, trimPrefix, uniqId } from '../utils';
import { Resources } from '../Game';
export function clickBuildButton(typeId: number) { export function clickBuildButton(typeId: number) {
const section = jQuery(`#contract_building${typeId}`); 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() { export function clickUpgradeButton() {
const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build'); const btn = jQuery('.upgradeButtonsContainer .section1 button.green.build');
if (btn.length !== 1) { if (btn.length !== 1) {
@ -45,3 +51,17 @@ export function createUpgradeButton(onClickHandler: () => void) {
onClickHandler(); 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 { export function grabResourcesPerformance(): Resources {
const $el = jQuery('#production'); const $el = jQuery('#production');
if ($el.length !== 1) { if ($el.length !== 1) {
throw new GrabError(); throw new GrabError('No production element');
} }
const $nums = $el.find('td.num'); const $nums = $el.find('td.num');
@ -63,7 +63,7 @@ export function grabResourcesPerformance(): Resources {
export function grabBuildingQueueInfo(): BuildingQueueInfo { export function grabBuildingQueueInfo(): BuildingQueueInfo {
const timer = jQuery('.buildDuration .timer'); const timer = jQuery('.buildDuration .timer');
if (timer.length !== 1) { if (timer.length !== 1) {
throw new GrabError(); throw new GrabError('No building queue timer element');
} }
const remainingSeconds = getNumber(timer.attr('value')); const remainingSeconds = getNumber(timer.attr('value'));

View File

@ -1,6 +1,6 @@
import { timestamp } from './utils'; import { timestamp } from './utils';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask'; 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 { Args, Command } from './Common';
import { SendOnAdventureTask } from './Task/SendOnAdventureTask'; import { SendOnAdventureTask } from './Task/SendOnAdventureTask';
import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask'; import { BalanceHeroResourcesTask } from './Task/BalanceHeroResourcesTask';
@ -50,7 +50,17 @@ export class Scheduler {
scheduleTask(name: string, args: Args): void { scheduleTask(name: string, args: Args): void {
this.logger.log('PUSH TASK', name, args); 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) { completeTask(id: TaskId) {
@ -69,11 +79,7 @@ export class Scheduler {
postponeBuildingsInVillage(villageId: number, seconds: number) { postponeBuildingsInVillage(villageId: number, seconds: number) {
this.taskQueue.modify( this.taskQueue.modify(
t => t.name === BuildBuildingTask.name && t.args.villageId === villageId, t => isBuildingTask(t.name) && sameVillage(villageId, t.args),
t => t.withTime(timestamp() + seconds)
);
this.taskQueue.modify(
t => t.name === UpgradeBuildingTask.name && t.args.villageId === villageId,
t => t.withTime(timestamp() + seconds) t => t.withTime(timestamp() + seconds)
); );
} }
@ -86,3 +92,11 @@ export class Scheduler {
this.actionQueue.clear(); 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 { grabActiveVillageId, grabBuildingQueueInfo, grabResourcesPerformance } from '../Page/VillageBlock';
import { VillageState } from './VillageState'; import { VillageState } from './VillageState';
import { parseLocation } from '../utils'; import { parseLocation } from '../utils';
import { GrabError } from '../Errors';
import { BuildingQueueInfo } from '../Game';
export class VillageOverviewPageGrabber extends StateGrabber { export class VillageOverviewPageGrabber extends StateGrabber {
grab(): void { grab(): void {
@ -13,6 +15,17 @@ export class VillageOverviewPageGrabber extends StateGrabber {
const villageId = grabActiveVillageId(); const villageId = grabActiveVillageId();
const state = new VillageState(villageId); const state = new VillageState(villageId);
state.storeResourcesPerformance(grabResourcesPerformance()); 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; 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 { get(ts: number): Task | undefined {
const readyItems = this.getItems().filter(t => t.ts <= ts); const readyItems = this.getItems().filter(t => t.ts <= ts);
if (readyItems.length === 0) { if (readyItems.length === 0) {
@ -133,3 +142,27 @@ export class TaskQueue {
this.storage.set(QUEUE_NAME, normalized); 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); 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() { export async function waitForLoad() {
return new Promise(resolve => jQuery(resolve)); return new Promise(resolve => jQuery(resolve));
} }