boardgame-core/src/rules/RuleEngine.ts

379 lines
9.4 KiB
TypeScript

import type { GameState } from '../core/GameState';
import type { Command, CommandExecutionResult } from '../commands/Command';
import { CommandExecutor } from '../commands/CommandExecutor';
import type {
Rule,
RuleContext,
RuleResult,
ValidationRule,
EffectRule,
TriggerRule,
RuleLogEntry,
} from './Rule';
import { isValidationRule, isEffectRule, isTriggerRule } from './Rule';
/**
* 规则引擎配置
*/
export interface RuleEngineOptions {
/** 游戏类型 */
gameType?: string;
/** 是否启用规则日志 */
enableLogging?: boolean;
/** 是否自动执行触发规则 */
autoExecuteTriggers?: boolean;
}
/**
* 规则引擎执行结果
*/
export interface RuleEngineExecutionResult extends CommandExecutionResult {
/** 执行的验证规则 */
validationRules: RuleLogEntry[];
/** 执行的效果规则 */
effectRules: RuleLogEntry[];
/** 触发的规则 */
triggerRules: RuleLogEntry[];
/** 触发的额外命令 */
triggeredCommands: Command[];
}
/**
* 规则引擎
* 负责在命令执行前后运行规则,并处理触发规则
*/
export class RuleEngine {
private gameState: GameState;
private executor: CommandExecutor;
private rules: Rule[] = [];
private options: RuleEngineOptions;
private logs: RuleLogEntry[] = [];
private isExecuting: boolean = false;
private triggerQueue: Command[] = [];
constructor(gameState: GameState, options: RuleEngineOptions = {}) {
this.gameState = gameState;
this.executor = new CommandExecutor(gameState);
this.options = {
enableLogging: true,
autoExecuteTriggers: true,
...options,
};
}
/**
* 注册规则
*/
registerRule(rule: Rule): void {
// 如果指定了游戏类型,只有匹配时才注册
if (this.options.gameType && rule.gameType && rule.gameType !== this.options.gameType) {
return;
}
this.rules.push(rule);
// 按优先级排序
this.rules.sort((a, b) => a.priority - b.priority);
}
/**
* 注册多个规则
*/
registerRules(rules: Rule[]): void {
for (const rule of rules) {
this.registerRule(rule);
}
}
/**
* 移除规则
*/
unregisterRule(ruleId: string): void {
this.rules = this.rules.filter((r) => r.id !== ruleId);
}
/**
* 清除所有规则
*/
clearRules(): void {
this.rules = [];
}
/**
* 获取所有规则
*/
getRules(): Rule[] {
return [...this.rules];
}
/**
* 执行命令(带规则验证)
*/
async executeCommand(command: Command): Promise<RuleEngineExecutionResult> {
if (this.isExecuting) {
throw new Error('Rule engine is already executing a command');
}
this.isExecuting = true;
const validationLogs: RuleLogEntry[] = [];
const effectLogs: RuleLogEntry[] = [];
const triggerLogs: RuleLogEntry[] = [];
const triggeredCommands: Command[] = [];
try {
// 创建规则上下文
const context: RuleContext = {
gameState: this.gameState,
command,
metadata: {},
};
// 1. 执行验证规则
const validationRules = this.rules.filter(isValidationRule);
for (const rule of validationRules) {
if (!this.isRuleApplicable(rule, command)) {
continue;
}
const result = await rule.validate(context);
const logEntry = this.createLogEntry(rule, 'validation', result, command.id);
validationLogs.push(logEntry);
if (!result.success) {
return this.createFailedResult(validationLogs, effectLogs, triggerLogs, triggeredCommands, result.error);
}
if (result.blockCommand) {
return this.createFailedResult(
validationLogs,
effectLogs,
triggerLogs,
triggeredCommands,
`Command blocked by rule: ${rule.name}`
);
}
// 应用状态更新
if (result.stateUpdates) {
Object.assign(context.metadata, result.stateUpdates);
}
// 收集触发的命令
if (result.triggeredCommands) {
triggeredCommands.push(...result.triggeredCommands);
}
}
// 2. 执行命令
const executionResult = this.executor.execute(command);
context.executionResult = executionResult;
if (!executionResult.success) {
return this.createFailedResult(
validationLogs,
effectLogs,
triggerLogs,
triggeredCommands,
executionResult.error
);
}
// 3. 执行效果规则
const effectRules = this.rules.filter(isEffectRule);
for (const rule of effectRules) {
if (!this.isRuleApplicable(rule, command)) {
continue;
}
const result = await rule.apply(context);
const logEntry = this.createLogEntry(rule, 'effect', result, command.id);
effectLogs.push(logEntry);
if (!result.success) {
// 效果规则失败不影响命令执行,只记录日志
continue;
}
// 应用状态更新
if (result.stateUpdates) {
Object.assign(context.metadata, result.stateUpdates);
}
// 收集触发的命令
if (result.triggeredCommands) {
triggeredCommands.push(...result.triggeredCommands);
}
}
// 4. 执行触发规则
if (this.options.autoExecuteTriggers) {
const triggerRules = this.rules.filter(isTriggerRule);
for (const rule of triggerRules) {
const shouldTrigger = await rule.condition(context);
if (shouldTrigger) {
const result = await rule.action(context);
const logEntry = this.createLogEntry(rule, 'trigger', result, command.id);
triggerLogs.push(logEntry);
if (result.triggeredCommands) {
triggeredCommands.push(...result.triggeredCommands);
}
}
}
}
// 5. 执行触发的命令(在循环外执行,避免递归)
} finally {
this.isExecuting = false;
}
// 在主要执行完成后执行触发的命令
for (const triggeredCommand of triggeredCommands) {
try {
const triggerResult = await this.executeCommand(triggeredCommand);
if (!triggerResult.success) {
// 触发命令失败,记录但不影响主命令
this.logs.push({
timestamp: Date.now(),
ruleId: 'triggered-command',
ruleName: 'Triggered Command',
ruleType: 'trigger',
result: { success: false, error: `Triggered command ${triggeredCommand.id} failed: ${triggerResult.error}` },
commandId: triggeredCommand.id,
});
}
} catch (error) {
// 忽略触发命令的异常
}
}
return {
success: true,
executedSteps: executionResult.executedSteps,
totalSteps: executionResult.totalSteps,
validationRules: validationLogs,
effectRules: effectLogs,
triggerRules: triggerLogs,
triggeredCommands,
};
}
/**
* 检查规则是否适用于当前命令
*/
private isRuleApplicable(
rule: ValidationRule | EffectRule,
command: Command
): boolean {
// 检查游戏类型
if (rule.gameType && rule.gameType !== this.options.gameType) {
return false;
}
// 检查命令名称
if (rule.applicableCommands && !rule.applicableCommands.includes(command.name)) {
return false;
}
return true;
}
/**
* 创建日志条目
*/
private createLogEntry(
rule: Rule,
ruleType: 'validation' | 'effect' | 'trigger',
result: RuleResult,
commandId: string
): RuleLogEntry {
const entry: RuleLogEntry = {
timestamp: Date.now(),
ruleId: rule.id,
ruleName: rule.name,
ruleType,
result,
commandId,
};
if (this.options.enableLogging) {
this.logs.push(entry);
}
return entry;
}
/**
* 创建失败结果
*/
private createFailedResult(
validationLogs: RuleLogEntry[],
effectLogs: RuleLogEntry[],
triggerLogs: RuleLogEntry[],
triggeredCommands: Command[],
error?: string
): RuleEngineExecutionResult {
return {
success: false,
error,
executedSteps: 0,
totalSteps: 0,
validationRules: validationLogs,
effectRules: effectLogs,
triggerRules: triggerLogs,
triggeredCommands,
};
}
/**
* 获取规则日志
*/
getLogs(): RuleLogEntry[] {
return [...this.logs];
}
/**
* 清除日志
*/
clearLogs(): void {
this.logs = [];
}
/**
* 获取游戏状态
*/
getGameState(): GameState {
return this.gameState;
}
/**
* 手动触发规则
*/
async triggerRules(): Promise<RuleLogEntry[]> {
const context: RuleContext = {
gameState: this.gameState,
command: { id: 'trigger-manual', name: 'manual-trigger', steps: [] },
metadata: {},
};
const logs: RuleLogEntry[] = [];
const triggerRules = this.rules.filter(isTriggerRule);
for (const rule of triggerRules) {
const shouldTrigger = await rule.condition(context);
if (shouldTrigger) {
const result = await rule.action(context);
const logEntry = this.createLogEntry(rule, 'trigger', result, 'manual');
logs.push(logEntry);
}
}
return logs;
}
}
/**
* 创建规则引擎
*/
export function createRuleEngine(gameState: GameState, options?: RuleEngineOptions): RuleEngine {
return new RuleEngine(gameState, options);
}