diff --git a/package.json b/package.json index c29c181..c2add15 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "scripts": { "build": "tsup", "test": "vitest", - "test:run": "vitest run" + "test:run": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@preact/signals-core": "^1.5.1", - "boardgame-core": "file:", "inline-schema": "git+https://gitea.ayi-games.online/hypercross/inline-schema" }, "devDependencies": { diff --git a/src/core/context.ts b/src/core/context.ts index e4e635b..a20230e 100644 --- a/src/core/context.ts +++ b/src/core/context.ts @@ -10,21 +10,25 @@ export type Context = { export const GameContext = createModel((root: Context) => { const parts = createEntityCollection(); const regions = createEntityCollection(); - const contexts = signal([signal(root)]); + const contexts = signal[]>([]); + contexts.value = [signal(root)]; function pushContext(context: Context) { const ctxSignal = signal(context); contexts.value = [...contexts.value, ctxSignal]; return context; } function popContext() { - contexts.value = contexts.value.slice(0, -1); + if (contexts.value.length > 1) { + contexts.value = contexts.value.slice(0, -1); + } } - function latestContext(type: T['type']){ + function latestContext(type: T['type']): Signal | undefined { for(let i = contexts.value.length - 1; i >= 0; i--){ if(contexts.value[i].value.type === type){ return contexts.value[i] as Signal; } } + return undefined; } return { diff --git a/src/core/region.ts b/src/core/region.ts index 57871a5..5205aac 100644 --- a/src/core/region.ts +++ b/src/core/region.ts @@ -28,36 +28,36 @@ export type RegionAxis = { export function applyAlign(region: Region){ if (region.children.length === 0) return; - // 对每个 axis 分别处理,但保持空间关系 + // Process each axis independently while preserving spatial relationships for (let axisIndex = 0; axisIndex < region.axes.length; axisIndex++) { const axis = region.axes[axisIndex]; if (!axis.align) continue; - // 收集当前轴上的所有唯一位置值,保持原有顺序 + // Collect all unique position values on this axis, preserving original order const positionValues = new Set(); for (const accessor of region.children) { positionValues.add(accessor.value.position[axisIndex] ?? 0); } - // 排序位置值 + // Sort position values const sortedPositions = Array.from(positionValues).sort((a, b) => a - b); - // 创建位置映射:原位置 -> 新位置 + // Create position mapping: old position -> new position const positionMap = new Map(); if (axis.align === 'start' && axis.min !== undefined) { - // 从 min 开始紧凑排列,保持相对顺序 + // Compact from min, preserving relative order sortedPositions.forEach((pos, index) => { positionMap.set(pos, axis.min! + index); }); } else if (axis.align === 'end' && axis.max !== undefined) { - // 从 max 开始向前紧凑排列 + // Compact towards max const count = sortedPositions.length; sortedPositions.forEach((pos, index) => { positionMap.set(pos, axis.max! - (count - 1 - index)); }); } else if (axis.align === 'center') { - // 居中排列 + // Center alignment const count = sortedPositions.length; const min = axis.min ?? 0; const max = axis.max ?? count - 1; @@ -70,14 +70,14 @@ export function applyAlign(region: Region){ }); } - // 应用位置映射到所有 part + // Apply position mapping to all parts for (const accessor of region.children) { const currentPos = accessor.value.position[axisIndex] ?? 0; accessor.value.position[axisIndex] = positionMap.get(currentPos) ?? currentPos; } } - // 最后按所有轴排序 children + // Sort children by all axes at the end region.children.sort((a, b) => { for (let i = 0; i < region.axes.length; i++) { const diff = (a.value.position[i] ?? 0) - (b.value.position[i] ?? 0); diff --git a/src/core/rule.ts b/src/core/rule.ts index d29cdc9..09e12e8 100644 --- a/src/core/rule.ts +++ b/src/core/rule.ts @@ -1,4 +1,4 @@ -import {Context} from "./context"; +import {Context} from "./context"; import {Command} from "../utils/command"; import {effect} from "@preact/signals-core"; @@ -17,8 +17,8 @@ export type RuleContext = Context & { * @returns 规则执行结果 */ export function invokeRuleContext( - pushContext: (context: Context) => void, - type: string, + pushContext: (context: Context) => void, + type: string, rule: Generator ): RuleContext { const ctx: RuleContext = { @@ -29,41 +29,39 @@ export function invokeRuleContext( resolution: undefined, }; - // 执行生成器直到完成或需要等待动作 + let disposed = false; + const executeRule = () => { + if (disposed || ctx.resolution !== undefined) return; + try { const result = rule.next(); - + if (result.done) { - // 规则执行完成,设置结果 ctx.resolution = result.value; return; } - // 如果生成器 yield 了一个动作类型,等待处理 - // 这里可以扩展为实际的动作处理逻辑 const actionType = result.value; - - // 继续执行直到有动作需要处理或规则完成 - if (!result.done) { - executeRule(); + + if (actionType) { + // 暂停于 yield 点,等待外部处理动作 + // 当外部更新 actions 后,effect 会重新触发 } } catch (error) { - // 规则执行出错,抛出错误 throw error; } }; - // 使用 effect 来跟踪响应式依赖 const dispose = effect(() => { if (ctx.resolution !== undefined) { dispose(); + disposed = true; return; } executeRule(); }); - // 将规则上下文推入栈中 pushContext(ctx); return ctx; @@ -77,12 +75,6 @@ export function invokeRuleContext( export function createRule( type: string, fn: (ctx: RuleContext) => Generator -): Generator { - return fn({ - type, - actions: [], - handledActions: 0, - invocations: [], - resolution: undefined, - }); +): (ctx: RuleContext) => Generator { + return fn; } diff --git a/src/utils/command.ts b/src/utils/command.ts index 4789cfa..b02f214 100644 --- a/src/utils/command.ts +++ b/src/utils/command.ts @@ -284,10 +284,12 @@ export function parseCommandSchema(schemaStr: string): CommandSchema { let parsedSchema: ParsedSchema | undefined; if (paramContent.includes(':')) { - const [name, typeStr] = paramContent.split(':').map(s => s.trim()); + const colonIndex = paramContent.indexOf(':'); + const name = paramContent.slice(0, colonIndex).trim(); + const typeStr = paramContent.slice(colonIndex + 1).trim(); try { parsedSchema = defineSchema(typeStr); - } catch { + } catch (e) { // 不是有效的 schema } paramContent = name; @@ -331,7 +333,10 @@ export function parseCommandSchema(schemaStr: string): CommandSchema { i++; } else if (token.startsWith('<') && token.endsWith('>')) { const isVariadic = token.endsWith('...>'); - let paramContent = token.replace(/^[<]+|[>.>]+$/g, ''); + let paramContent = token.replace(/^<+|>+$/g, ''); + if (isVariadic) { + paramContent = paramContent.replace(/\.\.\.$/, ''); + } let parsedSchema: ParsedSchema | undefined; if (paramContent.includes(':')) { @@ -541,14 +546,25 @@ export function validateCommand( command: Command, schema: CommandSchema ): { valid: true } | { valid: false; errors: string[] } { + const errors = validateCommandCore(command, schema); + + if (errors.length > 0) { + return { valid: false, errors }; + } + + return { valid: true }; +} + +/** + * 核心验证逻辑,返回错误数组 + */ +function validateCommandCore(command: Command, schema: CommandSchema): string[] { const errors: string[] = []; - // 验证命令名称 if (command.name !== schema.name) { errors.push(`命令名称不匹配:期望 "${schema.name}",实际 "${command.name}"`); } - // 验证参数数量 const requiredParams = schema.params.filter(p => p.required); const variadicParam = schema.params.find(p => p.variadic); @@ -556,30 +572,19 @@ export function validateCommand( errors.push(`参数不足:至少需要 ${requiredParams.length} 个参数,实际 ${command.params.length} 个`); } - // 如果有可变参数,参数数量可以超过必需参数数量 - // 否则,检查是否有多余参数 if (!variadicParam && command.params.length > schema.params.length) { errors.push(`参数过多:最多 ${schema.params.length} 个参数,实际 ${command.params.length} 个`); } - // 验证必需的选项 const requiredOptions = schema.options.filter(o => o.required); for (const opt of requiredOptions) { - // 检查长格式或短格式 const hasOption = opt.name in command.options || (opt.short && opt.short in command.options); if (!hasOption) { errors.push(`缺少必需选项:--${opt.name}${opt.short ? ` 或 -${opt.short}` : ''}`); } } - // 验证标志(标志都是可选的,除非未来扩展支持必需标志) - // 目前只检查是否有未定义的标志(可选的严格模式) - - if (errors.length > 0) { - return { valid: false, errors }; - } - - return { valid: true }; + return errors; } /** @@ -605,45 +610,13 @@ export function parseCommandWithSchema( const schema = parseCommandSchema(schemaStr); const command = parseCommand(input); - // 验证命令名称 - if (command.name !== schema.name) { - return { - command, - valid: false, - errors: [`命令名称不匹配:期望 "${schema.name}",实际 "${command.name}"`], - }; - } - - const errors: string[] = []; - - // 验证参数数量 - const requiredParams = schema.params.filter(p => p.required); - const variadicParam = schema.params.find(p => p.variadic); - - if (command.params.length < requiredParams.length) { - errors.push(`参数不足:至少需要 ${requiredParams.length} 个参数,实际 ${command.params.length} 个`); - return { command, valid: false, errors }; - } - - if (!variadicParam && command.params.length > schema.params.length) { - errors.push(`参数过多:最多 ${schema.params.length} 个参数,实际 ${command.params.length} 个`); - return { command, valid: false, errors }; - } - - // 验证必需的选项 - const requiredOptions = schema.options.filter(o => o.required); - for (const opt of requiredOptions) { - const hasOption = opt.name in command.options || (opt.short && opt.short in command.options); - if (!hasOption) { - errors.push(`缺少必需选项:--${opt.name}${opt.short ? ` 或 -${opt.short}` : ''}`); - } - } - + const errors = validateCommandCore(command, schema); if (errors.length > 0) { return { command, valid: false, errors }; } - // 使用 schema 解析参数值 + const parseErrors: string[] = []; + const parsedParams: unknown[] = []; for (let i = 0; i < command.params.length; i++) { const paramValue = command.params[i]; @@ -651,21 +624,19 @@ export function parseCommandWithSchema( if (paramSchema) { try { - // 如果是字符串值,使用 schema 解析 const parsed = typeof paramValue === 'string' ? paramSchema.parse(paramValue) : paramValue; parsedParams.push(parsed); } catch (e) { const err = e as ParseError; - errors.push(`参数 "${schema.params[i]?.name}" 解析失败:${err.message}`); + parseErrors.push(`参数 "${schema.params[i]?.name}" 解析失败:${err.message}`); } } else { parsedParams.push(paramValue); } } - // 使用 schema 解析选项值 const parsedOptions: Record = { ...command.options }; for (const [key, value] of Object.entries(command.options)) { const optSchema = schema.options.find(o => o.name === key || o.short === key); @@ -674,13 +645,13 @@ export function parseCommandWithSchema( parsedOptions[key] = optSchema.schema.parse(value); } catch (e) { const err = e as ParseError; - errors.push(`选项 "--${key}" 解析失败:${err.message}`); + parseErrors.push(`选项 "--${key}" 解析失败:${err.message}`); } } } - if (errors.length > 0) { - return { command: { ...command, params: parsedParams, options: parsedOptions }, valid: false, errors }; + if (parseErrors.length > 0) { + return { command: { ...command, params: parsedParams, options: parsedOptions }, valid: false, errors: parseErrors }; } return { diff --git a/src/utils/rng.ts b/src/utils/rng.ts index 625559a..c1a6fc1 100644 --- a/src/utils/rng.ts +++ b/src/utils/rng.ts @@ -1,4 +1,4 @@ -export interface RNG { +export interface RNG { /** 设置随机数种子 */ setSeed(seed: number): void; @@ -20,7 +20,7 @@ export function createRNG(seed?: number): RNG { } /** Mulberry32RNG 类实现(用于类型兼容) */ -export class Mulberry32RNG { +export class Mulberry32RNG implements RNG { private seed: number = 1; constructor(seed?: number) { @@ -30,7 +30,7 @@ export class Mulberry32RNG { } /** 设置随机数种子 */ - call(seed: number): void { + setSeed(seed: number): void { this.seed = seed; } @@ -48,11 +48,6 @@ export class Mulberry32RNG { return Math.floor(this.next(max)); } - /** 重新设置种子 */ - setSeed(seed: number): void { - this.seed = seed; - } - /** 获取当前种子 */ getSeed(): number { return this.seed;