Improve village state management

This commit is contained in:
Anton Vakhrushev 2020-05-24 19:30:03 +03:00
parent 8bea617f5b
commit 301b1a6ca9
25 changed files with 328 additions and 275 deletions

View File

@ -5,7 +5,7 @@ import { aroundMinutes } from '../utils';
import { Args } from '../Queue/Args';
import { Task } from '../Queue/TaskProvider';
import { VillageStorage } from '../Storage/VillageStorage';
import { VillageStateRepository } from '../VillageState';
import { VillageFactory } from '../VillageFactory';
const actionMap: { [name: string]: Function | undefined } = {};
@ -16,22 +16,22 @@ export function registerAction(constructor: Function) {
export function createActionHandler(
name: string,
scheduler: Scheduler,
villageStateRepository: VillageStateRepository
villageFactory: VillageFactory
): ActionController | undefined {
const storedFunction = actionMap[name];
if (storedFunction === undefined) {
return undefined;
}
const constructor = (storedFunction as unknown) as typeof ActionController;
return new constructor(scheduler, villageStateRepository);
return new constructor(scheduler, villageFactory);
}
export class ActionController {
protected scheduler: Scheduler;
protected villageStateRepository: VillageStateRepository;
constructor(scheduler: Scheduler, villageStateRepository: VillageStateRepository) {
protected readonly scheduler: Scheduler;
protected readonly villageFactory: VillageFactory;
constructor(scheduler: Scheduler, villageFactory: VillageFactory) {
this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.villageFactory = villageFactory;
}
async run(args: Args, task: Task) {}

View File

@ -18,7 +18,7 @@ export class BalanceHeroResourcesAction extends ActionController {
return;
}
const thisVillageState = this.villageStateRepository.getVillageState(thisVillageId);
const thisVillageState = this.villageFactory.createState(thisVillageId);
const requirements = [
thisVillageState.required.balance,

View File

@ -1,5 +1,5 @@
import { ActionController, registerAction } from './ActionController';
import { AbortTaskError, taskError } from '../Errors';
import { taskError } from '../Errors';
import { Args } from '../Queue/Args';
import { Task } from '../Queue/TaskProvider';

View File

@ -18,8 +18,8 @@ export class SendResourcesAction extends ActionController {
const coordinates = Coordinates.fromObject(args.coordinates || taskError('No coordinates'));
const senderVillage = this.villageStateRepository.getVillageState(senderVillageId);
const recipientVillage = this.villageStateRepository.getVillageState(targetVillageId);
const senderVillage = this.villageFactory.createState(senderVillageId);
const recipientVillage = this.villageFactory.createState(targetVillageId);
const readyToTransfer = this.getResourcesForTransfer(senderVillage, recipientVillage);

View File

@ -1,5 +1,5 @@
import { ActionController, registerAction } from './ActionController';
import { AbortTaskError, ActionError, taskError, TryLaterError } from '../Errors';
import { ActionError, taskError, TryLaterError } from '../Errors';
import { grabResourceDeposits } from '../Page/SlotBlock';
import { UpgradeBuildingTask } from '../Task/UpgradeBuildingTask';
import { ResourceDeposit } from '../Game';

View File

@ -8,9 +8,8 @@ import { DataStorageTaskProvider } from './Queue/DataStorageTaskProvider';
import { Statistics } from './Statistics';
import { StatisticsStorage } from './Storage/StatisticsStorage';
import { VillageRepository } from './VillageRepository';
import { VillageStateRepository } from './VillageState';
import { LogStorage } from './Storage/LogStorage';
import { VillageControllerFactory } from './VillageControllerFactory';
import { VillageFactory } from './VillageFactory';
import { GrabberManager } from './Grabber/GrabberManager';
export class Container {
@ -42,15 +41,15 @@ export class Container {
return this._statistics;
}
private _villageControllerFactory: VillageControllerFactory | undefined;
private _villageFactory: VillageFactory | undefined;
get villageControllerFactory(): VillageControllerFactory {
this._villageControllerFactory =
this._villageControllerFactory ||
get villageFactory(): VillageFactory {
this._villageFactory =
this._villageFactory ||
(() => {
return new VillageControllerFactory(this.villageRepository);
return new VillageFactory(this.villageRepository);
})();
return this._villageControllerFactory;
return this._villageFactory;
}
private _scheduler: Scheduler | undefined;
@ -68,31 +67,20 @@ export class Container {
taskQueue,
actionQueue,
this.villageRepository,
this.villageControllerFactory,
this.villageFactory,
new ConsoleLogger(Scheduler.name)
);
})();
return this._scheduler;
}
private _villageStateRepository: VillageStateRepository | undefined;
get villageStateRepository(): VillageStateRepository {
this._villageStateRepository =
this._villageStateRepository ||
(() => {
return new VillageStateRepository(this.villageRepository, this.villageControllerFactory);
})();
return this._villageStateRepository;
}
private _grabberManager: GrabberManager | undefined;
get grabberManager(): GrabberManager {
this._grabberManager =
this._grabberManager ||
(() => {
return new GrabberManager(this.villageControllerFactory);
return new GrabberManager(this.villageFactory);
})();
return this._grabberManager;
}
@ -110,8 +98,7 @@ export class Container {
return new Executor(
this.version,
this.scheduler,
this.villageStateRepository,
this.villageControllerFactory,
this.villageFactory,
this.grabberManager,
this.statistics,
logger
@ -126,12 +113,7 @@ export class Container {
this._controlPanel =
this._controlPanel ||
(() => {
return new ControlPanel(
this.version,
this.scheduler,
this.villageStateRepository,
this.villageControllerFactory
);
return new ControlPanel(this.version, this.scheduler, this.villageFactory);
})();
return this._controlPanel;
}

View File

@ -18,11 +18,11 @@ import { ConsoleLogger, Logger } from './Logger';
import { DataStorage } from './DataStorage';
import { getBuildingPageAttributes, isBuildingPage } from './Page/PageDetectors';
import { ExecutionStorage } from './Storage/ExecutionStorage';
import { VillageState, VillageStateRepository } from './VillageState';
import { VillageState } from './VillageState';
import { Task } from './Queue/TaskProvider';
import { Action } from './Queue/ActionQueue';
import { createStore } from './DashboardView/Store';
import { VillageControllerFactory } from './VillageControllerFactory';
import { VillageFactory } from './VillageFactory';
Vue.use(Vuex);
@ -52,20 +52,13 @@ interface GameState {
export class ControlPanel {
private readonly version: string;
private readonly scheduler: Scheduler;
private readonly villageStateRepository: VillageStateRepository;
private readonly logger: Logger;
private villageControllerFactory: VillageControllerFactory;
private readonly villageFactory: VillageFactory;
constructor(
version: string,
scheduler: Scheduler,
villageStateRepository: VillageStateRepository,
villageControllerFactory: VillageControllerFactory
) {
constructor(version: string, scheduler: Scheduler, villageFactory: VillageFactory) {
this.version = version;
this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.villageControllerFactory = villageControllerFactory;
this.villageFactory = villageFactory;
this.logger = new ConsoleLogger(this.constructor.name);
}
@ -78,7 +71,7 @@ export class ControlPanel {
const villageId = grabActiveVillageId();
const scheduler = this.scheduler;
const villageStateRepository = this.villageStateRepository;
const villageFactory = this.villageFactory;
const executionState = new ExecutionStorage();
@ -106,7 +99,7 @@ export class ControlPanel {
},
refreshVillages() {
this.villageStates = villageStateRepository.getAllVillageStates();
this.villageStates = villageFactory.getAllVillageStates();
for (let state of this.villageStates) {
if (state.village.active) {
this.activeVillageState = state;
@ -136,8 +129,8 @@ export class ControlPanel {
DataStorage.onChange(() => state.refresh());
const getBuildingsInQueue = () =>
this.villageControllerFactory
.create(villageId)
this.villageFactory
.createTaskCollection(villageId)
.getTasks()
.filter(t => t.name === UpgradeBuildingTask.name)
.map(t => t.args.buildId || 0);
@ -163,21 +156,21 @@ export class ControlPanel {
const buildPage = new BuildingPageController(
this.scheduler,
getBuildingPageAttributes(),
this.villageControllerFactory.create(villageId)
this.villageFactory.createController(villageId)
);
buildPage.run();
}
this.createControlPanel(state, villageStateRepository);
this.createControlPanel(state, villageFactory);
}
private createControlPanel(gameState: GameState, villageStateRepository: VillageStateRepository) {
private createControlPanel(gameState: GameState, villageFactory: VillageFactory) {
const appId = `app-${uniqId()}`;
jQuery('body').prepend(`<div id="${appId}"></div>`);
new Vue({
el: `#${appId}`,
data: gameState,
store: createStore(villageStateRepository),
store: createStore(villageFactory),
render: h => h(DashboardApp),
});
}

View File

@ -1,9 +1,9 @@
import Vuex from 'vuex';
import { LogStorage } from '../Storage/LogStorage';
import { VillageStateRepository } from '../VillageState';
import { VillageSettings, VillageSettingsDefaults } from '../Core/Village';
import { getNumber, notify } from '../utils';
import { VillageStorage } from '../Storage/VillageStorage';
import { VillageFactory } from '../VillageFactory';
export enum Mutations {
showLogs = 'showLogs',
@ -22,7 +22,7 @@ export enum Actions {
SaveVillageSettings = 'save_village_settings',
}
export function createStore(villageStateRepository: VillageStateRepository) {
export function createStore(villageFactory: VillageFactory) {
const store = new Vuex.Store({
state: {
views: {
@ -74,7 +74,7 @@ export function createStore(villageStateRepository: VillageStateRepository) {
},
actions: {
[Actions.OpenVillageEditor]({ commit }, { villageId }) {
const state = villageStateRepository.getVillageState(villageId);
const state = villageFactory.createState(villageId);
const settings = state.settings;
commit(Mutations.SetVillageSettings, {
villageId: state.id,

View File

@ -10,8 +10,8 @@ import { ExecutionStorage } from './Storage/ExecutionStorage';
import { Action } from './Queue/ActionQueue';
import { Task } from './Queue/TaskProvider';
import { createTaskHandler } from './Task/TaskMap';
import { VillageStateRepository } from './VillageState';
import { VillageControllerFactory } from './VillageControllerFactory';
import { VillageStateFactory } from './VillageState';
import { VillageFactory } from './VillageFactory';
export interface ExecutionSettings {
pauseTs: number;
@ -20,8 +20,7 @@ export interface ExecutionSettings {
export class Executor {
private readonly version: string;
private readonly scheduler: Scheduler;
private readonly villageStateRepository: VillageStateRepository;
private villageControllerFactory: VillageControllerFactory;
private villageFactory: VillageFactory;
private grabberManager: GrabberManager;
private statistics: Statistics;
private executionState: ExecutionStorage;
@ -30,16 +29,14 @@ export class Executor {
constructor(
version: string,
scheduler: Scheduler,
villageStateRepository: VillageStateRepository,
villageControllerFactory: VillageControllerFactory,
villageFactory: VillageFactory,
grabberManager: GrabberManager,
statistics: Statistics,
logger: Logger
) {
this.version = version;
this.scheduler = scheduler;
this.villageStateRepository = villageStateRepository;
this.villageControllerFactory = villageControllerFactory;
this.villageFactory = villageFactory;
this.grabberManager = grabberManager;
this.statistics = statistics;
this.executionState = new ExecutionStorage();
@ -109,7 +106,7 @@ export class Executor {
}
private async processActionCommand(action: Action, task: Task) {
const actionHandler = createActionHandler(action.name, this.scheduler, this.villageStateRepository);
const actionHandler = createActionHandler(action.name, this.scheduler, this.villageFactory);
this.logger.info('Process action', action.name, actionHandler);
if (actionHandler) {
this.statistics.incrementAction(timestamp());

View File

@ -20,7 +20,7 @@ export class BuildingContractGrabber extends Grabber {
const contract = grabContractResources();
this.controller.updateResources(contract, {
this.taskCollection.updateResources(contract, {
type: ContractType.UpgradeBuilding,
buildId: building.buildId,
});

View File

@ -20,7 +20,7 @@ export class ForgePageGrabber extends Grabber {
const contracts = grabImprovementContracts();
for (let { resources, unitId } of contracts) {
this.controller.updateResources(resources, {
this.taskCollection.updateResources(resources, {
type: ContractType.ImproveTrooper,
buildId,
unitId,
@ -29,8 +29,7 @@ export class ForgePageGrabber extends Grabber {
}
private grabTimer(): void {
const storage = this.controller.getStorage();
const seconds = grabRemainingSeconds();
storage.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0);
this.storage.storeQueueTaskEnding(ProductionQueue.UpgradeUnit, seconds ? seconds + timestamp() : 0);
}
}

View File

@ -1,10 +1,13 @@
import { VillageController } from '../VillageController';
import { VillageTaskCollection } from '../VillageTaskCollection';
import { VillageStorage } from '../Storage/VillageStorage';
export abstract class Grabber {
protected controller: VillageController;
protected taskCollection: VillageTaskCollection;
protected storage: VillageStorage;
constructor(controller: VillageController) {
this.controller = controller;
constructor(taskCollection: VillageTaskCollection, storage: VillageStorage) {
this.taskCollection = taskCollection;
this.storage = storage;
}
abstract grab(): void;

View File

@ -6,12 +6,12 @@ import { MarketPageGrabber } from './MarketPageGrabber';
import { BuildingContractGrabber } from './BuildingContractGrabber';
import { ForgePageGrabber } from './ForgePageGrabber';
import { GuildHallPageGrabber } from './GuildHallPageGrabber';
import { VillageControllerFactory } from '../VillageControllerFactory';
import { VillageFactory } from '../VillageFactory';
export class GrabberManager {
private factory: VillageControllerFactory;
private factory: VillageFactory;
constructor(factory: VillageControllerFactory) {
constructor(factory: VillageFactory) {
this.factory = factory;
}
@ -23,15 +23,16 @@ export class GrabberManager {
}
private createGrabbers(): Array<Grabber> {
const controller = this.factory.getActive();
const storage = this.factory.createStorageForActiveVillage();
const taskCollection = this.factory.createTaskCollectionForActiveVillage();
const grabbers: Array<Grabber> = [];
grabbers.push(new VillageResourceGrabber(controller));
grabbers.push(new VillageOverviewPageGrabber(controller));
grabbers.push(new HeroPageGrabber(controller));
grabbers.push(new MarketPageGrabber(controller));
grabbers.push(new BuildingContractGrabber(controller));
grabbers.push(new ForgePageGrabber(controller));
grabbers.push(new GuildHallPageGrabber(controller));
grabbers.push(new VillageResourceGrabber(taskCollection, storage));
grabbers.push(new VillageOverviewPageGrabber(taskCollection, storage));
grabbers.push(new HeroPageGrabber(taskCollection, storage));
grabbers.push(new MarketPageGrabber(taskCollection, storage));
grabbers.push(new BuildingContractGrabber(taskCollection, storage));
grabbers.push(new ForgePageGrabber(taskCollection, storage));
grabbers.push(new GuildHallPageGrabber(taskCollection, storage));
return grabbers;
}
}

View File

@ -11,7 +11,6 @@ export class GuildHallPageGrabber extends Grabber {
}
const seconds = grabRemainingSeconds();
const storage = this.controller.getStorage();
storage.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0);
this.storage.storeQueueTaskEnding(ProductionQueue.Celebration, seconds ? seconds + timestamp() : 0);
}
}

View File

@ -8,7 +8,6 @@ export class MarketPageGrabber extends Grabber {
return;
}
const storage = this.controller.getStorage();
storage.storeIncomingMerchants(grabIncomingMerchants());
this.storage.storeIncomingMerchants(grabIncomingMerchants());
}
}

View File

@ -12,13 +12,12 @@ export class VillageOverviewPageGrabber extends Grabber {
return;
}
const storage = this.controller.getStorage();
storage.storeResourcesPerformance(grabResourcesPerformance());
storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault());
this.storage.storeResourcesPerformance(grabResourcesPerformance());
this.storage.storeBuildingQueueInfo(this.grabBuildingQueueInfoOrDefault());
const buildingQueueInfo = this.grabBuildingQueueInfoOrDefault();
const buildingEndTime = buildingQueueInfo.seconds ? buildingQueueInfo.seconds + timestamp() : 0;
storage.storeQueueTaskEnding(ProductionQueue.Building, buildingEndTime);
this.storage.storeQueueTaskEnding(ProductionQueue.Building, buildingEndTime);
}
private grabBuildingQueueInfoOrDefault() {

View File

@ -3,8 +3,7 @@ import { grabVillageResources, grabVillageResourceStorage } from '../Page/Resour
export class VillageResourceGrabber extends Grabber {
grab(): void {
const storage = this.controller.getStorage();
storage.storeResources(grabVillageResources());
storage.storeResourceStorage(grabVillageResourceStorage());
this.storage.storeResources(grabVillageResources());
this.storage.storeResourceStorage(grabVillageResourceStorage());
}
}

View File

@ -12,8 +12,9 @@ import { ImmutableTaskList, Task, TaskId, uniqTaskId, withTime } from './Queue/T
import { MARKET_ID } from './Core/Buildings';
import { VillageRepositoryInterface } from './VillageRepository';
import { isProductionTask } from './Core/ProductionQueue';
import { VillageControllerFactory } from './VillageControllerFactory';
import { VillageFactory } from './VillageFactory';
import { RunVillageProductionTask } from './Task/RunVillageProductionTask';
import { VillageNotFound } from './Errors';
export interface NextExecution {
task?: Task;
@ -24,14 +25,14 @@ export class Scheduler {
private taskQueue: TaskQueue;
private actionQueue: ActionQueue;
private villageRepository: VillageRepositoryInterface;
private villageControllerFactory: VillageControllerFactory;
private villageControllerFactory: VillageFactory;
private logger: Logger;
constructor(
taskQueue: TaskQueue,
actionQueue: ActionQueue,
villageRepository: VillageRepositoryInterface,
villageControllerFactory: VillageControllerFactory,
villageControllerFactory: VillageFactory,
logger: Logger
) {
this.taskQueue = taskQueue;
@ -96,13 +97,13 @@ 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.create(villageId);
const controller = this.villageControllerFactory.createController(villageId);
const villageTask = controller.getReadyProductionTask();
if (villageTask) {
this.removeTask(task.id);
const newTask = new Task(villageTask.id, 0, villageTask.name, {
...villageTask.args,
villageId: controller.villageId,
villageId: controller.getVillageId(),
});
this.taskQueue.add(newTask);
return newTask;
@ -113,7 +114,7 @@ export class Scheduler {
scheduleTask(name: string, args: Args, ts?: number | undefined): void {
if (isProductionTask(name) && args.villageId) {
const controller = this.villageControllerFactory.create(args.villageId);
const controller = this.villageControllerFactory.createController(args.villageId);
controller.addTask(name, args);
} else {
this.logger.info('Schedule task', name, args, ts);
@ -143,7 +144,7 @@ export class Scheduler {
const task = this.taskQueue.findById(taskId);
const villageId = task ? task.args.villageId : undefined;
if (villageId) {
const controller = this.villageControllerFactory.create(villageId);
const controller = this.villageControllerFactory.createController(villageId);
controller.removeTask(taskId);
}
this.removeTask(taskId);
@ -156,7 +157,7 @@ export class Scheduler {
}
if (isProductionTask(task.name) && task.args.villageId) {
const controller = this.villageControllerFactory.create(task.args.villageId);
const controller = this.villageControllerFactory.createController(task.args.villageId);
controller.postponeTask(taskId, seconds);
this.removeTask(taskId);
} else {
@ -186,7 +187,7 @@ export class Scheduler {
this.dropResourceTransferTasks(fromVillageId, toVillageId);
const village = this.villageRepository.all().find(v => v.id === toVillageId);
if (!village) {
throw new Error('No village');
throw new VillageNotFound(`Village ${toVillageId} not found`);
}
this.scheduleTask(SendResourcesTask.name, {
villageId: fromVillageId,

View File

@ -1,5 +1,4 @@
import { DataStorage } from '../DataStorage';
import { ActionStatistics, StatisticsStorageInterface } from '../Statistics';
import { LogStorageInterface, StorageLogRecord } from '../Logger';
const NAMESPACE = 'logs.v1';

View File

@ -112,41 +112,7 @@ export class VillageStorage {
});
}
addTask(task: Task): void {
const tasks = this.getTasks();
tasks.push(task);
this.storeTaskList(tasks);
}
modifyTasks(predicate: (t: Task) => boolean, modifier: (t: Task) => Task): number {
const [matched, other] = this.split(predicate);
const modified = matched.map(modifier);
const modifiedCount = modified.length;
this.storeTaskList(modified.concat(other));
return modifiedCount;
}
removeTasks(predicate: (t: Task) => boolean): number {
const [_, other] = this.split(predicate);
const result = other.length;
this.storeTaskList(other);
return result;
}
private split(predicate: (t: Task) => boolean): [TaskList, TaskList] {
const matched: TaskList = [];
const other: TaskList = [];
this.getTasks().forEach(t => {
if (predicate(t)) {
matched.push(t);
} else {
other.push(t);
}
});
return [matched, other];
}
private storeTaskList(tasks: Array<Task>): void {
storeTaskList(tasks: Array<Task>): void {
this.storage.set(TASK_LIST_KEY, tasks);
}
}

View File

@ -1,93 +1,36 @@
import { Task, TaskId, uniqTaskId, withResources, withTime } from './Queue/TaskProvider';
import { VillageStorage } from './Storage/VillageStorage';
import { VillageTaskCollection } from './VillageTaskCollection';
import { Task, TaskId } from './Queue/TaskProvider';
import { Args } from './Queue/Args';
import { isProductionTask, ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
import { Resources } from './Core/Resources';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { ForgeImprovementTask } from './Task/ForgeImprovementTask';
import { ContractType, ContractAttributes } from './Core/Contract';
import { timestamp } from './utils';
import { getProductionQueue } from './Task/TaskMap';
import { VillageState } from './VillageState';
export class VillageController {
private readonly _villageId: number;
private readonly _storage: VillageStorage;
private readonly villageId: number;
private taskCollection: VillageTaskCollection;
private readonly state: VillageState;
constructor(villageId: number, storage: VillageStorage) {
this._villageId = villageId;
this._storage = storage;
constructor(villageId: number, taskCollection: VillageTaskCollection, state: VillageState) {
this.villageId = villageId;
this.taskCollection = taskCollection;
this.state = state;
}
get villageId() {
return this._villageId;
}
getStorage(): VillageStorage {
return this._storage;
}
addTask(name: string, args: Args) {
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 });
this._storage.addTask(task);
}
getTasks(): Array<Task> {
return this._storage.getTasks();
}
removeTask(taskId: TaskId) {
this._storage.removeTasks(t => t.id === taskId);
}
getTasksInProductionQueue(queue: ProductionQueue): Array<Task> {
return this._storage.getTasks().filter(task => getProductionQueue(task.name) === queue);
getVillageId() {
return this.villageId;
}
getReadyProductionTask(): Task | undefined {
let sortedTasks: Array<Task> = [];
for (let queue of ProductionQueueTypes) {
const tasks = this.getTasksInProductionQueue(queue);
sortedTasks = sortedTasks.concat(tasks);
}
return sortedTasks.shift();
return this.taskCollection.getReadyProductionTask();
}
addTask(name: string, args: Args) {
this.taskCollection.addTask(name, args);
}
removeTask(taskId: TaskId) {
this.taskCollection.removeTask(taskId);
}
postponeTask(taskId: TaskId, seconds: number) {
const modifyTime = withTime(timestamp() + seconds);
this._storage.modifyTasks(task => task.id === taskId, modifyTime);
}
updateResources(resources: Resources, attr: ContractAttributes): void {
if (attr.type === ContractType.UpgradeBuilding && attr.buildId) {
const predicate = (t: Task) => t.name === UpgradeBuildingTask.name && t.args.buildId === attr.buildId;
this._storage.modifyTasks(predicate, withResources(resources));
}
if (attr.type === ContractType.ImproveTrooper && attr.buildId && attr.unitId) {
const predicate = (t: Task) =>
t.name === ForgeImprovementTask.name &&
t.args.buildId === attr.buildId &&
t.args.unitId === attr.unitId;
this._storage.modifyTasks(predicate, withResources(resources));
}
}
getVillageRequiredResources(): Resources {
const tasks = this._storage.getTasks().filter(t => t.args.resources);
const first = tasks.shift();
if (first && first.args.resources) {
return Resources.fromObject(first.args.resources);
}
return Resources.zero();
}
getTotalVillageRequiredResources(): Resources {
const tasks = this._storage.getTasks().filter(t => t.args.resources);
return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero());
this.taskCollection.postponeTask(taskId, seconds);
}
}

View File

@ -1,21 +0,0 @@
import { VillageController } from './VillageController';
import { VillageStorage } from './Storage/VillageStorage';
import { VillageRepository } from './VillageRepository';
export class VillageControllerFactory {
private villageRepository: VillageRepository;
constructor(villageRepository: VillageRepository) {
this.villageRepository = villageRepository;
}
create(villageId: number): VillageController {
const village = this.villageRepository.get(villageId);
return new VillageController(village.id, new VillageStorage(village.id));
}
getActive(): VillageController {
const village = this.villageRepository.getActive();
return this.create(village.id);
}
}

57
src/VillageFactory.ts Normal file
View File

@ -0,0 +1,57 @@
import { VillageController } from './VillageController';
import { VillageStorage } from './Storage/VillageStorage';
import { VillageRepository } from './VillageRepository';
import { VillageTaskCollection } from './VillageTaskCollection';
import { VillageState, VillageStateFactory } from './VillageState';
export class VillageFactory {
private readonly villageRepository: VillageRepository;
constructor(villageRepository: VillageRepository) {
this.villageRepository = villageRepository;
}
createStorage(villageId: number): VillageStorage {
const village = this.villageRepository.get(villageId);
return new VillageStorage(village.id);
}
createStorageForActiveVillage(): VillageStorage {
const village = this.villageRepository.getActive();
return this.createStorage(village.id);
}
createTaskCollection(villageId: number): VillageTaskCollection {
const village = this.villageRepository.get(villageId);
return new VillageTaskCollection(village.id, this.createStorage(villageId));
}
createTaskCollectionForActiveVillage(): VillageTaskCollection {
const village = this.villageRepository.getActive();
return this.createTaskCollection(village.id);
}
createState(villageId: number): VillageState {
const village = this.villageRepository.get(villageId);
const stateFactory = new VillageStateFactory(
this.villageRepository,
(id: number) => this.createStorage(id),
(id: number) => this.createTaskCollection(id)
);
return stateFactory.getVillageState(village.id);
}
getAllVillageStates(): Array<VillageState> {
const stateFactory = new VillageStateFactory(
this.villageRepository,
(id: number) => this.createStorage(id),
(id: number) => this.createTaskCollection(id)
);
return stateFactory.getAllVillageStates();
}
createController(villageId: number): VillageController {
const village = this.villageRepository.get(villageId);
return new VillageController(village.id, this.createTaskCollection(village.id), this.createState(village.id));
}
}

View File

@ -7,8 +7,7 @@ import { VillageNotFound } from './Errors';
import { ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
import { Task } from './Queue/TaskProvider';
import { timestamp } from './utils';
import { VillageControllerFactory } from './VillageControllerFactory';
import { VillageController } from './VillageController';
import { VillageTaskCollection } from './VillageTaskCollection';
interface VillageStorageState {
resources: Resources;
@ -134,12 +133,12 @@ function taskResourceReducer(resources: Resources, task: Task) {
function createProductionQueueState(
queue: ProductionQueue,
controller: VillageController
storage: VillageStorage,
taskCollection: VillageTaskCollection
): VillageProductionQueueState {
const storage = controller.getStorage();
const resources = storage.getResources();
const performance = storage.getResourcesPerformance();
const tasks = controller.getTasksInProductionQueue(queue);
const tasks = taskCollection.getTasksInProductionQueue(queue);
const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero());
const allTaskResources = tasks.reduce(taskResourceReducer, Resources.zero());
@ -156,33 +155,36 @@ function createProductionQueueState(
};
}
function createAllProductionQueueStates(controller: VillageController) {
function createAllProductionQueueStates(storage: VillageStorage, taskCollection: VillageTaskCollection) {
let result: { [queue: string]: VillageProductionQueueState } = {};
for (let queue of ProductionQueueTypes) {
result[queue] = createProductionQueueState(queue, controller);
result[queue] = createProductionQueueState(queue, storage, taskCollection);
}
return result;
}
function calcFrontierResources(controller: VillageController): Resources {
function calcFrontierResources(taskCollection: VillageTaskCollection): Resources {
let result = Resources.zero();
for (let queue of ProductionQueueTypes) {
const tasks = controller.getTasksInProductionQueue(queue);
const tasks = taskCollection.getTasksInProductionQueue(queue);
const firstTaskResources = tasks.slice(0, 1).reduce(taskResourceReducer, Resources.zero());
result = result.add(firstTaskResources);
}
return result;
}
function createVillageOwnState(village: Village, controller: VillageController): VillageOwnState {
const storage = controller.getStorage();
function createVillageOwnState(
village: Village,
storage: VillageStorage,
taskCollection: VillageTaskCollection
): VillageOwnState {
const resources = storage.getResources();
const resourceStorage = storage.getResourceStorage();
const performance = storage.getResourcesPerformance();
const buildQueueInfo = storage.getBuildingQueueInfo();
const requiredResources = controller.getVillageRequiredResources();
const frontierResources = calcFrontierResources(controller);
const totalRequiredResources = controller.getTotalVillageRequiredResources();
const requiredResources = taskCollection.getVillageRequiredResources();
const frontierResources = calcFrontierResources(taskCollection);
const totalRequiredResources = taskCollection.getTotalVillageRequiredResources();
return {
id: village.id,
@ -196,18 +198,22 @@ function createVillageOwnState(village: Village, controller: VillageController):
buildRemainingSeconds: buildQueueInfo.seconds,
incomingResources: calcIncomingResources(storage),
settings: storage.getSettings(),
queues: createAllProductionQueueStates(controller),
queues: createAllProductionQueueStates(storage, taskCollection),
};
}
function createVillageOwnStates(
villages: Array<Village>,
villageControllerFactory: VillageControllerFactory
storageFactory: VillageStorageFactory,
taskCollectionFactory: VillageTaskCollectionFactory
): VillageOwnStateDictionary {
const result: VillageOwnStateDictionary = {};
for (let village of villages) {
const villageController = villageControllerFactory.create(village.id);
result[village.id] = createVillageOwnState(village, villageController);
result[village.id] = createVillageOwnState(
village,
storageFactory(village.id),
taskCollectionFactory(village.id)
);
}
return result;
}
@ -226,27 +232,42 @@ function createVillageState(state: VillageOwnState, ownStates: VillageOwnStateDi
function getVillageStates(
villages: Array<Village>,
villageControllerFactory: VillageControllerFactory
storageFactory: VillageStorageFactory,
taskCollectionFactory: VillageTaskCollectionFactory
): Array<VillageState> {
const ownStates = createVillageOwnStates(villages, villageControllerFactory);
const ownStates = createVillageOwnStates(villages, storageFactory, taskCollectionFactory);
return villages.map(village => createVillageState(ownStates[village.id], ownStates));
}
export class VillageStateRepository {
private villageRepository: VillageRepositoryInterface;
private villageControllerFactory: VillageControllerFactory;
interface VillageStorageFactory {
(villageId: number): VillageStorage;
}
constructor(villageRepository: VillageRepositoryInterface, villageControllerFactory: VillageControllerFactory) {
interface VillageTaskCollectionFactory {
(villageId: number): VillageTaskCollection;
}
export class VillageStateFactory {
private readonly villageRepository: VillageRepositoryInterface;
private readonly storageFactory: VillageStorageFactory;
private readonly taskCollectionFactory: VillageTaskCollectionFactory;
constructor(
villageRepository: VillageRepositoryInterface,
storageFactory: VillageStorageFactory,
taskCollectionFactory: VillageTaskCollectionFactory
) {
this.villageRepository = villageRepository;
this.villageControllerFactory = villageControllerFactory;
this.storageFactory = storageFactory;
this.taskCollectionFactory = taskCollectionFactory;
}
getAllVillageStates(): Array<VillageState> {
return getVillageStates(this.villageRepository.all(), this.villageControllerFactory);
return getVillageStates(this.villageRepository.all(), this.storageFactory, this.taskCollectionFactory);
}
getVillageState(villageId: number): VillageState {
const states = getVillageStates(this.villageRepository.all(), this.villageControllerFactory);
const states = getVillageStates(this.villageRepository.all(), this.storageFactory, this.taskCollectionFactory);
const needle = states.find(s => s.id === villageId);
if (!needle) {
throw new VillageNotFound(`Village ${villageId} not found`);

View File

@ -0,0 +1,116 @@
import { VillageStorage } from './Storage/VillageStorage';
import { Task, TaskId, TaskList, uniqTaskId, withResources, withTime } from './Queue/TaskProvider';
import { Args } from './Queue/Args';
import { isProductionTask, ProductionQueue, ProductionQueueTypes } from './Core/ProductionQueue';
import { getProductionQueue } from './Task/TaskMap';
import { timestamp } from './utils';
import { Resources } from './Core/Resources';
import { ContractAttributes, ContractType } from './Core/Contract';
import { UpgradeBuildingTask } from './Task/UpgradeBuildingTask';
import { ForgeImprovementTask } from './Task/ForgeImprovementTask';
export class VillageTaskCollection {
private readonly storage: VillageStorage;
private readonly villageId: number;
constructor(villageId: number, storage: VillageStorage) {
this.villageId = villageId;
this.storage = storage;
}
getTasks(): Array<Task> {
return this.storage.getTasks();
}
private modifyTasks(predicate: (t: Task) => boolean, modifier: (t: Task) => Task): number {
const [matched, other] = this.split(predicate);
const modified = matched.map(modifier);
const modifiedCount = modified.length;
this.storage.storeTaskList(modified.concat(other));
return modifiedCount;
}
private removeTasks(predicate: (t: Task) => boolean): number {
const [_, other] = this.split(predicate);
const result = other.length;
this.storage.storeTaskList(other);
return result;
}
private split(predicate: (t: Task) => boolean): [TaskList, TaskList] {
const matched: TaskList = [];
const other: TaskList = [];
this.getTasks().forEach(t => {
if (predicate(t)) {
matched.push(t);
} else {
other.push(t);
}
});
return [matched, other];
}
addTask(name: string, args: Args) {
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);
}
removeTask(taskId: TaskId) {
this.removeTasks(t => t.id === taskId);
}
getTasksInProductionQueue(queue: ProductionQueue): Array<Task> {
return this.storage.getTasks().filter(task => getProductionQueue(task.name) === queue);
}
getReadyProductionTask(): Task | undefined {
let sortedTasks: Array<Task> = [];
for (let queue of ProductionQueueTypes) {
const tasks = this.getTasksInProductionQueue(queue);
sortedTasks = sortedTasks.concat(tasks);
}
return sortedTasks.shift();
}
postponeTask(taskId: TaskId, seconds: number) {
const modifyTime = withTime(timestamp() + seconds);
this.modifyTasks(task => task.id === taskId, modifyTime);
}
updateResources(resources: Resources, attr: ContractAttributes): void {
if (attr.type === ContractType.UpgradeBuilding && attr.buildId) {
const predicate = (t: Task) => t.name === UpgradeBuildingTask.name && t.args.buildId === attr.buildId;
this.modifyTasks(predicate, withResources(resources));
}
if (attr.type === ContractType.ImproveTrooper && attr.buildId && attr.unitId) {
const predicate = (t: Task) =>
t.name === ForgeImprovementTask.name &&
t.args.buildId === attr.buildId &&
t.args.unitId === attr.unitId;
this.modifyTasks(predicate, withResources(resources));
}
}
getVillageRequiredResources(): Resources {
const tasks = this.storage.getTasks().filter(t => t.args.resources);
const first = tasks.shift();
if (first && first.args.resources) {
return Resources.fromObject(first.args.resources);
}
return Resources.zero();
}
getTotalVillageRequiredResources(): Resources {
const tasks = this.storage.getTasks().filter(t => t.args.resources);
return tasks.reduce((acc, t) => acc.add(t.args.resources!), Resources.zero());
}
}