refactor: Create a new super class Event and prevent duplicate logic

This commit is contained in:
Justin Lin committed 2025-04-21 12:23:09 +08:00
1 parent 2dc40a2254
commit 0f0f43b838
6 files changed
+109 -49

No files matched your search

+48
View File
@@ -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<boolean> {
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
}
}
+18
View File
@@ -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)
}
}
+21 -16
View File
@@ -1,5 +1,7 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { Condition } from './condition/Condition' import { Condition } from './condition/Condition'
import { Event } from './Event'
import { GlobalException, StageException } from './Exception'
import { import {
IQuest, IQuest,
IStage, IStage,
@@ -10,15 +12,28 @@ import {
import { Stage } from './Stage' import { Stage } from './Stage'
export class Quest { export class Quest {
private stages: Stage[]
private quest: IQuest private quest: IQuest
private stages: Stage[]
private exceptions: GlobalException[] = []
private events: Map<string, Event> = new Map()
constructor( constructor(
quest: IQuest, quest: IQuest,
private checker: IFileExistenceChecker private checker: IFileExistenceChecker
) { ) {
this.quest = quest 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[]) { getActiveStages(completed: string[]) {
@@ -26,15 +41,7 @@ export class Quest {
} }
private getActiveExceptions(completed: string[]) { private getActiveExceptions(completed: string[]) {
return ( return this.exceptions.filter((exception) => exception.active(completed))
this.quest.exceptions?.filter(
(exception) =>
!completed.includes(exception.id) &&
exception.requirements.every((requirement) =>
completed.includes(requirement)
)
) ?? []
)
} }
async findSatisfiedEvent( async findSatisfiedEvent(
@@ -49,17 +56,15 @@ export class Quest {
return stage.id return stage.id
} }
} }
for (const exception of stages.flatMap((stage) => stage.exceptions)) { for (const exception of stages.flatMap((stage) => stage.getExceptions())) {
const condition = new Condition(exception.condition)
if (exception.catchAll && !catchAllException) { if (exception.catchAll && !catchAllException) {
catchAllException = exception.id catchAllException = exception.id
} else if (await condition.satisfies(command, this.checker)) { } else if (await exception.satisfies(command, this.checker)) {
return exception.id return exception.id
} }
} }
for (const exception of this.getActiveExceptions(completed)) { for (const exception of this.getActiveExceptions(completed)) {
const condition = new Condition(exception.condition) if (await exception.satisfies(command, this.checker)) {
if (await condition.satisfies(command, this.checker)) {
return exception.id return exception.id
} }
} }
+18 -30
View File
@@ -1,38 +1,26 @@
import { IStage, ICommand, IFileExistenceChecker } from './schema' import { IStage } from './schema'
import { Condition } from './condition/Condition' import { StageException } from './Exception'
import { StageException } from './schema/exception' import { Event } from './Event'
export class Stage { export class Stage extends Event {
id: string
name: string name: string
public readonly condition: Condition private exceptions: StageException[]
private requirements: string[]
public readonly exceptions: StageException[]
constructor({ id, name, condition, requirements, exceptions }: IStage) { constructor({
this.id = id id,
name,
condition,
requirements,
exceptions,
response,
}: IStage) {
super(id, condition, response, requirements)
this.name = name || '' this.name = name || ''
this.condition = new Condition(condition) this.exceptions =
this.requirements = requirements || [] exceptions?.map((exception) => new StageException(exception)) || []
this.exceptions = exceptions || []
} }
async satisfies( getExceptions(): StageException[] {
command: ICommand, return this.exceptions
checker: IFileExistenceChecker
): Promise<boolean> {
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)
} }
} }
+3 -3
View File
@@ -9,16 +9,16 @@ export const exceptionSchema = z.object({
response: responseSchema, response: responseSchema,
}) })
export type Exception = z.infer<typeof exceptionSchema> export type IException = z.infer<typeof exceptionSchema>
export const stageExceptionSchema = exceptionSchema.extend({ export const stageExceptionSchema = exceptionSchema.extend({
catchAll: z.boolean().default(false), catchAll: z.boolean().default(false),
}) })
export type StageException = z.infer<typeof stageExceptionSchema> export type IStageException = z.infer<typeof stageExceptionSchema>
export const globalExceptionSchema = exceptionSchema.extend({ export const globalExceptionSchema = exceptionSchema.extend({
requirements: requirementsSchema.default([]), requirements: requirementsSchema.default([]),
}) })
export type GlobalException = z.infer<typeof globalExceptionSchema> export type IGlobalException = z.infer<typeof globalExceptionSchema>
+1
View File
@@ -3,6 +3,7 @@ export { stageSchema, IStage } from './stage.js'
export { requirementsSchema, IRequirements } from './requirements.js' export { requirementsSchema, IRequirements } from './requirements.js'
export { responseSchema, IResponse } from './response.js' export { responseSchema, IResponse } from './response.js'
export { questSchema, IQuest } from './quest.js' export { questSchema, IQuest } from './quest.js'
export { exceptionSchema, IException } from './exception.js'
export interface ICommand { export interface ICommand {
command?: string command?: string