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 type {TextResult, RuntimeResult, OptionsResult} from '../yarn-spinner/runtime/results';
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();
let historyContainer: HTMLDivElement | undefined;

View File

@ -38,7 +38,7 @@ export function getTtrpgFunctions(runner: IRunner){
}
/* stat related */
function ensure_stat(stat: string){
if(!runner.getVariable(stat)){
if(runner.getVariable(stat) === undefined){
const statBase = `${stat}_base`;
const statMod = `${stat}_mod`;
const statDamage = `${stat}_damage`;
@ -74,6 +74,7 @@ export function getTtrpgFunctions(runner: IRunner){
const progress = runner.getVariable(key) as number || 0;
const newProgress = progress + Math.ceil(Math.random() * 4);
runner.setVariable(key, newProgress);
console.log(`check ${stat}(${statVal}) ${pass ? 'pass' : 'fail'}, now ${newProgress}`);
return pass ? newProgress : -newProgress;
}
function rollStat(){
@ -94,15 +95,17 @@ export function getTtrpgFunctions(runner: IRunner){
heal(stat, 1);
}
return {
add_minutes: add_minute,
add_hours: add_hour,
add_days: add_day,
commands: {
add_minute,
add_hour,
add_day,
damage,
heal,
check,
fatigue,
regen,
};
regen
} as Record<string, (...args: any[]) =>any>,
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 {createStore} from "solid-js/store";
import {RunnerOptions} from "../../yarn-spinner/runtime/runner";
import {getTtrpgFunctions} from "./ttrpgRunner";
type YarnSpinnerStore = {
dialogueHistory: (RuntimeResult | OptionsResult['options'][0])[],
@ -12,7 +13,7 @@ type YarnSpinnerStore = {
runnerInstance: YarnRunner | null,
}
export function createYarnStore(element: HTMLElement, props: RunnerOptions){
export function createYarnStore(element: HTMLElement, props: {start: string}){
const [store, setStore] = createStore<YarnSpinnerStore>({
dialogueHistory: [],
currentOptions: null,
@ -39,19 +40,11 @@ export function createYarnStore(element: HTMLElement, props: RunnerOptions){
const program = compile(ast);
const runner = new YarnRunner(program, {
functions: {
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,
startAt: props.start,
});
const {commands, functions} = getTtrpgFunctions(runner);
runner.registerFunctions(functions);
runner.registerCommands(commands);
return runner;
} catch (error) {
console.error('Failed to initialize YarnRunner:', error);

View File

@ -499,4 +499,11 @@ export class ExpressionEvaluator {
getVariable(name: string): unknown {
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 {
private readonly program: IRProgram;
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 commandHandler: CommandHandler;
private readonly evaluator: ExpressionEvaluator;
@ -59,7 +58,7 @@ export class YarnRunner {
this.variables[normalizedKey] = value;
}
}
this.functions = {
let functions = {
// Default conversion helpers
string: (v: unknown) => String(v ?? ""),
number: (v: unknown) => Number(v),
@ -115,13 +114,26 @@ export class YarnRunner {
} as Record<string, (...args: unknown[]) => unknown>;
this.handleCommand = opts.handleCommand;
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.nodeTitle = opts.startAt;
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).
*/
@ -599,6 +611,8 @@ export class YarnRunner {
* Get variable value.
*/
getVariable(name: string): unknown {
if(this.evaluator.isSmartVariable(name))
return this.evaluator.evaluateExpression(`$${name}`);
return this.variables[name];
}