From 0f0f43b8382c877913e24dc01d687fa195d8203d Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Mon, 21 Apr 2025 12:23:09 +0800 Subject: [PATCH] refactor: Create a new super class Event and prevent duplicate logic --- packages/game/src/Event.ts | 48 +++++++++++++++++++++++++++ packages/game/src/Exception.ts | 18 ++++++++++ packages/game/src/Quest.ts | 37 ++++++++++++--------- packages/game/src/Stage.ts | 48 ++++++++++----------------- packages/game/src/schema/exception.ts | 6 ++-- packages/game/src/schema/index.ts | 1 + 6 files changed, 109 insertions(+), 49 deletions(-) create mode 100644 packages/game/src/Event.ts create mode 100644 packages/game/src/Exception.ts diff --git a/packages/game/src/Event.ts b/packages/game/src/Event.ts new file mode 100644 index 0000000..33073ec --- /dev/null +++ b/packages/game/src/Event.ts @@ -0,0 +1,48 @@ +import { Condition } from './condition/Condition' +import { + ICommand, + ICondition, + IFileExistenceChecker, + IRequirements, + IResponse, +} from './schema' + +export class Event { + id: string + private condition: Condition + private requirements: string[] + + constructor( + id: string, + condition: ICondition, + private response: IResponse, + requirements?: IRequirements + ) { + this.id = id + this.condition = new Condition(condition) + this.requirements = requirements || [] + } + + async satisfies( + command: ICommand, + checker: IFileExistenceChecker + ): Promise { + return this.condition.satisfies(command, checker) + } + + private checkRequirements(completed: string[]): boolean { + return this.requirements.every((req) => completed.includes(req)) + } + + private finished(completed: string[]): boolean { + return completed.includes(this.id) + } + + active(completed: string[]): boolean { + return !this.finished(completed) && this.checkRequirements(completed) + } + + getResponse(): IResponse { + return this.response + } +} diff --git a/packages/game/src/Exception.ts b/packages/game/src/Exception.ts new file mode 100644 index 0000000..687a9b3 --- /dev/null +++ b/packages/game/src/Exception.ts @@ -0,0 +1,18 @@ +/* eslint-disable max-classes-per-file */ +import { Event } from './Event' +import { IStageException, IGlobalException } from './schema/exception' + +export class StageException extends Event { + readonly catchAll: boolean + + constructor({ id, condition, response, catchAll }: IStageException) { + super(id, condition, response) + this.catchAll = catchAll + } +} + +export class GlobalException extends Event { + constructor({ id, condition, response, requirements }: IGlobalException) { + super(id, condition, response, requirements) + } +} diff --git a/packages/game/src/Quest.ts b/packages/game/src/Quest.ts index 56985d7..4ac8248 100644 --- a/packages/game/src/Quest.ts +++ b/packages/game/src/Quest.ts @@ -1,5 +1,7 @@ /* eslint-disable no-await-in-loop */ import { Condition } from './condition/Condition' +import { Event } from './Event' +import { GlobalException, StageException } from './Exception' import { IQuest, IStage, @@ -10,15 +12,28 @@ import { import { Stage } from './Stage' export class Quest { - private stages: Stage[] private quest: IQuest + private stages: Stage[] + private exceptions: GlobalException[] = [] + private events: Map = new Map() constructor( quest: IQuest, private checker: IFileExistenceChecker ) { this.quest = quest - this.stages = quest.stages.map((stage: IStage) => new Stage(stage)) + this.stages = quest.stages.map((stage) => new Stage(stage)) + for (const stage of this.stages) { + this.events.set(stage.id, stage) + for (const exception of stage.getExceptions()) { + this.events.set(exception.id, exception) + } + } + for (const exception of quest.exceptions ?? []) { + const globalException = new GlobalException(exception) + this.exceptions.push(globalException) + this.events.set(globalException.id, globalException) + } } getActiveStages(completed: string[]) { @@ -26,15 +41,7 @@ export class Quest { } private getActiveExceptions(completed: string[]) { - return ( - this.quest.exceptions?.filter( - (exception) => - !completed.includes(exception.id) && - exception.requirements.every((requirement) => - completed.includes(requirement) - ) - ) ?? [] - ) + return this.exceptions.filter((exception) => exception.active(completed)) } async findSatisfiedEvent( @@ -49,17 +56,15 @@ export class Quest { return stage.id } } - for (const exception of stages.flatMap((stage) => stage.exceptions)) { - const condition = new Condition(exception.condition) + for (const exception of stages.flatMap((stage) => stage.getExceptions())) { if (exception.catchAll && !catchAllException) { catchAllException = exception.id - } else if (await condition.satisfies(command, this.checker)) { + } else if (await exception.satisfies(command, this.checker)) { return exception.id } } for (const exception of this.getActiveExceptions(completed)) { - const condition = new Condition(exception.condition) - if (await condition.satisfies(command, this.checker)) { + if (await exception.satisfies(command, this.checker)) { return exception.id } } diff --git a/packages/game/src/Stage.ts b/packages/game/src/Stage.ts index d0c250f..ae455d2 100644 --- a/packages/game/src/Stage.ts +++ b/packages/game/src/Stage.ts @@ -1,38 +1,26 @@ -import { IStage, ICommand, IFileExistenceChecker } from './schema' -import { Condition } from './condition/Condition' -import { StageException } from './schema/exception' +import { IStage } from './schema' +import { StageException } from './Exception' +import { Event } from './Event' -export class Stage { - id: string +export class Stage extends Event { name: string - public readonly condition: Condition - private requirements: string[] - public readonly exceptions: StageException[] + private exceptions: StageException[] - constructor({ id, name, condition, requirements, exceptions }: IStage) { - this.id = id + constructor({ + id, + name, + condition, + requirements, + exceptions, + response, + }: IStage) { + super(id, condition, response, requirements) this.name = name || '' - this.condition = new Condition(condition) - this.requirements = requirements || [] - this.exceptions = exceptions || [] + this.exceptions = + exceptions?.map((exception) => new StageException(exception)) || [] } - async satisfies( - command: ICommand, - checker: IFileExistenceChecker - ): Promise { - return this.condition.satisfies(command, checker) - } - - private checkRequirements(completed: string[]): boolean { - return this.requirements.every((req) => completed.includes(req)) - } - - private finished(completed: string[]): boolean { - return completed.includes(this.id) - } - - active(completed: string[]): boolean { - return !this.finished(completed) && this.checkRequirements(completed) + getExceptions(): StageException[] { + return this.exceptions } } diff --git a/packages/game/src/schema/exception.ts b/packages/game/src/schema/exception.ts index a0da264..24b0aab 100644 --- a/packages/game/src/schema/exception.ts +++ b/packages/game/src/schema/exception.ts @@ -9,16 +9,16 @@ export const exceptionSchema = z.object({ response: responseSchema, }) -export type Exception = z.infer +export type IException = z.infer export const stageExceptionSchema = exceptionSchema.extend({ catchAll: z.boolean().default(false), }) -export type StageException = z.infer +export type IStageException = z.infer export const globalExceptionSchema = exceptionSchema.extend({ requirements: requirementsSchema.default([]), }) -export type GlobalException = z.infer +export type IGlobalException = z.infer diff --git a/packages/game/src/schema/index.ts b/packages/game/src/schema/index.ts index 8b74f38..bba3834 100644 --- a/packages/game/src/schema/index.ts +++ b/packages/game/src/schema/index.ts @@ -3,6 +3,7 @@ export { stageSchema, IStage } from './stage.js' export { requirementsSchema, IRequirements } from './requirements.js' export { responseSchema, IResponse } from './response.js' export { questSchema, IQuest } from './quest.js' +export { exceptionSchema, IException } from './exception.js' export interface ICommand { command?: string