diff --git a/.eslintrc.js b/.eslintrc.js index a2e5bd1..ebe45d0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,7 +49,7 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-unused-vars': [ - 'error', + 'warn', { args: 'after-used', argsIgnorePattern: '^_', @@ -73,7 +73,6 @@ module.exports = { }, ], 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', 'no-empty-function': 'off', diff --git a/packages/game/src/Quest.ts b/packages/game/src/Quest.ts new file mode 100644 index 0000000..0a352b8 --- /dev/null +++ b/packages/game/src/Quest.ts @@ -0,0 +1,16 @@ +import { IQuest, IStage } from './schema' +import { Stage } from './Stage' + +export class Quest { + private stages: Stage[] + private quest: IQuest + + constructor(quest: IQuest) { + this.quest = quest + this.stages = quest.stages.map((stage: IStage) => new Stage(stage)) + } + + getActiveStages(completed: string[]) { + return this.stages.filter((stage) => stage.active(completed)) + } +} diff --git a/packages/game/src/Session.ts b/packages/game/src/Session.ts index e8b5417..4664100 100644 --- a/packages/game/src/Session.ts +++ b/packages/game/src/Session.ts @@ -1,10 +1,14 @@ +import { Quest } from './Quest' +import { IQuest } from './schema' import { ISession } from './types' export class Session implements ISession { private completed: string[] = [] + private quest: Quest - constructor({ completedStages }: ISession) { + constructor({ completedStages }: ISession, quest: IQuest) { this.completed = completedStages + this.quest = new Quest(quest) } get completedStages() { @@ -14,4 +18,8 @@ export class Session implements ISession { complete(stageId: string) { this.completed.push(stageId) } + + getActiveStages(): string[] { + return this.quest.getActiveStages(this.completed).map((stage) => stage.id) + } } diff --git a/packages/game/src/Stage.ts b/packages/game/src/Stage.ts index e440488..1c83b71 100644 --- a/packages/game/src/Stage.ts +++ b/packages/game/src/Stage.ts @@ -1,4 +1,4 @@ -import { IStage, ICommand, IFileExistenceChecker, ISession } from './types' +import { IStage, ICommand, IFileExistenceChecker } from './types' import { Condition } from './condition/Condition' export class Stage { @@ -21,17 +21,15 @@ export class Stage { return this.condition.match(command) && this.condition.check(checker) } - private checkRequirements(session: ISession): boolean { - return this.requirements.every((req) => - session.completedStages.includes(req) - ) + private checkRequirements(completed: string[]): boolean { + return this.requirements.every((req) => completed.includes(req)) } - private finished(session: ISession): boolean { - return session.completedStages.includes(this.id) + private finished(completed: string[]): boolean { + return completed.includes(this.id) } - active(session: ISession): boolean { - return !this.finished(session) && this.checkRequirements(session) + active(completed: string[]): boolean { + return !this.finished(completed) && this.checkRequirements(completed) } } diff --git a/packages/game/src/condition/Condition.ts b/packages/game/src/condition/Condition.ts index 82c6c57..bce7e7f 100644 --- a/packages/game/src/condition/Condition.ts +++ b/packages/game/src/condition/Condition.ts @@ -1,9 +1,9 @@ import { ICommand, - ICondition, IFileExistenceChecker, FileExistenceInput, } from '../types.js' +import { ICondition } from '../schema/condition.js' import { PwdMatcher } from './PwdMatcher.js' import { CommandMatcher } from './CommandMatcher.js' import { ErrorMatcher } from './ErrorMatcher.js' diff --git a/packages/game/src/schema/condition.ts b/packages/game/src/schema/condition.ts index 5d055c2..f387c89 100644 --- a/packages/game/src/schema/condition.ts +++ b/packages/game/src/schema/condition.ts @@ -1,8 +1,9 @@ import { z } from 'zod' +import { FileType } from '../types.js' export const fileInputSchema = z.object({ path: z.string(), - type: z.enum(['file', 'folder']), + type: z.nativeEnum(FileType), }) export type IFileInput = z.infer @@ -28,6 +29,6 @@ export type ICondition = z.infer & { export const conditionSchema: z.ZodType = baseConditionSchema.extend({ - or: z.lazy(() => z.array(conditionSchema)).optional(), - not: z.lazy(() => conditionSchema).optional(), + or: z.lazy(() => z.array(conditionSchema).optional()), + not: z.lazy(() => conditionSchema.optional()), }) diff --git a/packages/game/src/schema/index.ts b/packages/game/src/schema/index.ts index 777cf0a..357a272 100644 --- a/packages/game/src/schema/index.ts +++ b/packages/game/src/schema/index.ts @@ -1,7 +1,5 @@ export { conditionSchema, ICondition } from './condition.js' export { stageSchema, IStage } from './stage.js' -export { - requirementsSchema, - IRequirements, - responseSchema, -} from './requirements.js' +export { requirementsSchema, IRequirements } from './requirements.js' +export { responseSchema, IResponse } from './response.js' +export { questSchema, IQuest } from './quest.js' diff --git a/packages/game/src/schema/quest.ts b/packages/game/src/schema/quest.ts new file mode 100644 index 0000000..fc626aa --- /dev/null +++ b/packages/game/src/schema/quest.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' +import { stageSchema } from './stage.js' +import { requirementsSchema } from './requirements.js' +// import { globalExceptionSchema } from './exception.js' +// import { requirementSchema } from './requirement.js' + +export const questSchema = z.object({ + id: z.string(), + title: z.string(), + image: z.string(), + instruction: z.string(), + requirements: requirementsSchema, + stages: z.array(stageSchema), + // exceptions: z.array(globalExceptionSchema).default([]), +}) + +export type IQuest = z.infer diff --git a/packages/game/src/schema/requirements.ts b/packages/game/src/schema/requirements.ts index 3bbea7d..3ee80d3 100644 --- a/packages/game/src/schema/requirements.ts +++ b/packages/game/src/schema/requirements.ts @@ -1,12 +1,5 @@ import { z } from 'zod' -export const requirementsSchema = z.object({ - requirements: z.array(z.string()), -}) +export const requirementsSchema = z.array(z.string()) export type IRequirements = z.infer - -export const responseSchema = z.object({ - type: z.enum(['narrative', 'dialogue']), - content: z.string(), -}) diff --git a/packages/game/src/schema/response.ts b/packages/game/src/schema/response.ts new file mode 100644 index 0000000..c085d77 --- /dev/null +++ b/packages/game/src/schema/response.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +export const responseSchema = z.object({ + type: z.enum(['narrative', 'dialogue']), + content: z.string(), +}) + +export type IResponse = z.infer diff --git a/packages/game/src/schema/stage.ts b/packages/game/src/schema/stage.ts index 8f5a1cf..818dae0 100644 --- a/packages/game/src/schema/stage.ts +++ b/packages/game/src/schema/stage.ts @@ -1,5 +1,6 @@ import { z } from 'zod' -import { conditionSchema, responseSchema } from './index.js' +import { conditionSchema } from './condition.js' +import { responseSchema } from './response.js' export const stageSchema = z.object({ id: z.string().regex(/^[a-zA-Z0-9_-]+$/), diff --git a/packages/game/test/session.test.ts b/packages/game/test/session.test.ts new file mode 100644 index 0000000..73dfee2 --- /dev/null +++ b/packages/game/test/session.test.ts @@ -0,0 +1,58 @@ +import { expect, describe, it } from '@jest/globals' +import { IQuest } from '../src/schema' +import { Session } from '../src/Session' + +const quest: IQuest = { + id: 'quest1', + title: 'Quest 1', + image: 'image1', + instruction: 'Instruction 1', + requirements: [], + stages: [ + { + id: 'stage1', + requirements: [], + condition: {}, + response: { type: 'narrative', content: 'Narrative 1' }, + }, + { + id: 'stage2', + requirements: ['stage1'], + condition: {}, + response: { type: 'narrative', content: 'Narrative 2' }, + }, + { + id: 'stage3', + requirements: ['stage1'], + condition: {}, + response: { type: 'narrative', content: 'Narrative 3' }, + }, + { + id: 'stage4', + requirements: ['stage2', 'stage3'], + condition: {}, + response: { type: 'narrative', content: 'Narrative 4' }, + }, + ], +} + +describe('Session', () => { + it('should return active stages', () => { + const session = new Session( + { + completedStages: [], + }, + quest + ) + expect(session.getActiveStages()).toEqual(['stage1']) + + session.complete('stage1') + expect(session.getActiveStages()).toEqual(['stage2', 'stage3']) + + session.complete('stage2') + expect(session.getActiveStages()).toEqual(['stage3']) + + session.complete('stage3') + expect(session.getActiveStages()).toEqual(['stage4']) + }) +}) diff --git a/packages/game/test/stage.test.ts b/packages/game/test/stage.test.ts index de81ade..5631e21 100644 --- a/packages/game/test/stage.test.ts +++ b/packages/game/test/stage.test.ts @@ -2,7 +2,6 @@ import { test, expect } from '@jest/globals' import { Stage } from '../src/Stage' import { FileType } from '../src/types' import { MockFileChecker } from './asyncCondition.test' -import { Session } from '../src/Session' const checker = new MockFileChecker() @@ -68,21 +67,3 @@ test('check stage with async condition falsy', async () => { }) expect(await stage.satisfies({}, checker)).toBe(false) }) - -test('stage active checks', () => { - const session = new Session({ - completedStages: ['stage1'], - }) - const stage3 = new Stage({ - id: 'stage3', - requirements: ['stage1', 'stage2'], - condition: {}, - }) - expect(stage3.active(session)).toBe(false) - - session.complete('stage2') - expect(stage3.active(session)).toBe(true) - - session.complete('stage3') - expect(stage3.active(session)).toBe(false) -})