refactor: yarn runner man

This commit is contained in:
hypercross 2026-03-03 14:57:46 +08:00
parent 8d5b15ad51
commit e13ead2309
5 changed files with 46 additions and 30 deletions

View File

@ -2,9 +2,8 @@ import { customElement, noShadowDOM } from 'solid-element';
import { For, Show, createEffect } from 'solid-js'; import { For, Show, createEffect } from 'solid-js';
import type {TextResult, RuntimeResult, OptionsResult} from '../yarn-spinner/runtime/results'; import type {TextResult, RuntimeResult, OptionsResult} from '../yarn-spinner/runtime/results';
import { createYarnStore } from './stores/yarnStore'; import { createYarnStore } from './stores/yarnStore';
import {RunnerOptions} from "../yarn-spinner/runtime/runner";
customElement<RunnerOptions>('md-yarn-spinner', {startAt: 'start'}, (props, { element }) => { customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { element }) => {
noShadowDOM(); noShadowDOM();
let historyContainer: HTMLDivElement | undefined; let historyContainer: HTMLDivElement | undefined;

View File

@ -38,7 +38,7 @@ export function getTtrpgFunctions(runner: IRunner){
} }
/* stat related */ /* stat related */
function ensure_stat(stat: string){ function ensure_stat(stat: string){
if(!runner.getVariable(stat)){ if(runner.getVariable(stat) === undefined){
const statBase = `${stat}_base`; const statBase = `${stat}_base`;
const statMod = `${stat}_mod`; const statMod = `${stat}_mod`;
const statDamage = `${stat}_damage`; const statDamage = `${stat}_damage`;
@ -74,6 +74,7 @@ export function getTtrpgFunctions(runner: IRunner){
const progress = runner.getVariable(key) as number || 0; const progress = runner.getVariable(key) as number || 0;
const newProgress = progress + Math.ceil(Math.random() * 4); const newProgress = progress + Math.ceil(Math.random() * 4);
runner.setVariable(key, newProgress); runner.setVariable(key, newProgress);
console.log(`check ${stat}(${statVal}) ${pass ? 'pass' : 'fail'}, now ${newProgress}`);
return pass ? newProgress : -newProgress; return pass ? newProgress : -newProgress;
} }
function rollStat(){ function rollStat(){
@ -94,15 +95,17 @@ export function getTtrpgFunctions(runner: IRunner){
heal(stat, 1); heal(stat, 1);
} }
return { return {
add_minutes: add_minute, commands: {
add_hours: add_hour, add_minute,
add_days: add_day, add_hour,
add_day,
damage, damage,
heal, heal,
check, fatigue,
regen
fatigue, } as Record<string, (...args: any[]) =>any>,
regen, functions: {
}; check
} as Record<string, (...args: any[]) =>any>
}
} }

View File

@ -4,6 +4,7 @@ import {compile, parseYarn, YarnRunner} from "../../yarn-spinner";
import {loadElementSrc, resolvePath} from "../utils/path"; import {loadElementSrc, resolvePath} from "../utils/path";
import {createStore} from "solid-js/store"; import {createStore} from "solid-js/store";
import {RunnerOptions} from "../../yarn-spinner/runtime/runner"; import {RunnerOptions} from "../../yarn-spinner/runtime/runner";
import {getTtrpgFunctions} from "./ttrpgRunner";
type YarnSpinnerStore = { type YarnSpinnerStore = {
dialogueHistory: (RuntimeResult | OptionsResult['options'][0])[], dialogueHistory: (RuntimeResult | OptionsResult['options'][0])[],
@ -12,7 +13,7 @@ type YarnSpinnerStore = {
runnerInstance: YarnRunner | null, runnerInstance: YarnRunner | null,
} }
export function createYarnStore(element: HTMLElement, props: RunnerOptions){ export function createYarnStore(element: HTMLElement, props: {start: string}){
const [store, setStore] = createStore<YarnSpinnerStore>({ const [store, setStore] = createStore<YarnSpinnerStore>({
dialogueHistory: [], dialogueHistory: [],
currentOptions: null, currentOptions: null,
@ -39,19 +40,11 @@ export function createYarnStore(element: HTMLElement, props: RunnerOptions){
const program = compile(ast); const program = compile(ast);
const runner = new YarnRunner(program, { const runner = new YarnRunner(program, {
functions: { startAt: props.start,
check(id: string, stat: string): number {
const statVal = runner.getVariable(stat) as number || 10;
const pass = Math.ceil(Math.random() * 20) <= statVal;
const key = `${id}_${pass ? 'pass' : 'fail'}`;
const progress = runner.getVariable(key) as number || 0;
const newProgress = progress + Math.ceil(Math.random() * 4);
runner.setVariable(key, newProgress);
return pass ? newProgress : -newProgress;
}
},
startAt: props.startAt,
}); });
const {commands, functions} = getTtrpgFunctions(runner);
runner.registerFunctions(functions);
runner.registerCommands(commands);
return runner; return runner;
} catch (error) { } catch (error) {
console.error('Failed to initialize YarnRunner:', error); console.error('Failed to initialize YarnRunner:', error);

View File

@ -499,4 +499,11 @@ export class ExpressionEvaluator {
getVariable(name: string): unknown { getVariable(name: string): unknown {
return this.variables[name]; return this.variables[name];
} }
registerFunctions(functions: Record<string, (...args: unknown[]) => unknown>){
this.functions = {
...this.functions,
...functions
}
}
} }

View File

@ -28,7 +28,6 @@ type CompiledOption = {
export class YarnRunner { export class YarnRunner {
private readonly program: IRProgram; private readonly program: IRProgram;
private readonly variables: Record<string, unknown>; private readonly variables: Record<string, unknown>;
private readonly functions: Record<string, (...args: unknown[]) => unknown>;
private readonly handleCommand?: (command: string, parsed?: ReturnType<typeof parseCommand>) => void; private readonly handleCommand?: (command: string, parsed?: ReturnType<typeof parseCommand>) => void;
private readonly commandHandler: CommandHandler; private readonly commandHandler: CommandHandler;
private readonly evaluator: ExpressionEvaluator; private readonly evaluator: ExpressionEvaluator;
@ -59,7 +58,7 @@ export class YarnRunner {
this.variables[normalizedKey] = value; this.variables[normalizedKey] = value;
} }
} }
this.functions = { let functions = {
// Default conversion helpers // Default conversion helpers
string: (v: unknown) => String(v ?? ""), string: (v: unknown) => String(v ?? ""),
number: (v: unknown) => Number(v), number: (v: unknown) => Number(v),
@ -115,12 +114,25 @@ export class YarnRunner {
} as Record<string, (...args: unknown[]) => unknown>; } as Record<string, (...args: unknown[]) => unknown>;
this.handleCommand = opts.handleCommand; this.handleCommand = opts.handleCommand;
this.onStoryEnd = opts.onStoryEnd; this.onStoryEnd = opts.onStoryEnd;
this.evaluator = new ExpressionEvaluator(this.variables, this.functions, this.program.enums); this.evaluator = new ExpressionEvaluator(this.variables, functions, this.program.enums);
this.commandHandler = opts.commandHandler ?? new CommandHandler(this.variables); this.commandHandler = opts.commandHandler ?? new CommandHandler(this.variables);
this.nodeTitle = opts.startAt; this.nodeTitle = opts.startAt;
this.step(); this.step();
} }
public registerFunctions(functions: Record<string, (...args: unknown[]) => unknown>){
this.evaluator.registerFunctions(functions);
}
public registerCommands(commands: Record<string, (args: unknown[], evaluator?: ExpressionEvaluator) => void | Promise<void>>) {
for(const key in commands){
this.commandHandler.register(key, (args, evaluator) => {
if(!evaluator) return;
commands[key].call(this, args.map(arg => evaluator.evaluateExpression(arg)));
});
}
};
/** /**
* Resolve a node title to an actual node (handling node groups). * Resolve a node title to an actual node (handling node groups).
@ -599,6 +611,8 @@ export class YarnRunner {
* Get variable value. * Get variable value.
*/ */
getVariable(name: string): unknown { getVariable(name: string): unknown {
if(this.evaluator.isSmartVariable(name))
return this.evaluator.evaluateExpression(`$${name}`);
return this.variables[name]; return this.variables[name];
} }