712086ddca
Показ анимации кубиков игрокам, закрытие оверлеев с окнами, ручной ввод количества, меньший радиус кисти, блок спонсоров в «О программе»; плюс черновик game systems / character sheet. Co-authored-by: Cursor <cursoragent@cursor.com>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import type { DerivedDefinition } from './types';
|
|
import { buildFormulaContext, evaluateFormula } from './formula';
|
|
|
|
export class CycleError extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = 'CycleError';
|
|
}
|
|
}
|
|
|
|
/** Kahn topological sort of derived definitions by dependsOn keys (derived.* or attr.* etc.). */
|
|
export function sortDerived(defs: DerivedDefinition[]): DerivedDefinition[] {
|
|
const byKey = new Map(defs.map((d) => [d.key, d]));
|
|
const indeg = new Map<string, number>();
|
|
const adj = new Map<string, string[]>();
|
|
|
|
for (const d of defs) {
|
|
indeg.set(d.key, 0);
|
|
adj.set(d.key, []);
|
|
}
|
|
|
|
for (const d of defs) {
|
|
for (const dep of d.dependsOn) {
|
|
const depKey = dep.startsWith('derived.') ? dep.slice('derived.'.length) : null;
|
|
if (depKey && byKey.has(depKey)) {
|
|
adj.get(depKey)!.push(d.key);
|
|
indeg.set(d.key, (indeg.get(d.key) ?? 0) + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
const queue = [...indeg.entries()].filter(([, n]) => n === 0).map(([k]) => k);
|
|
const ordered: DerivedDefinition[] = [];
|
|
while (queue.length) {
|
|
const k = queue.shift()!;
|
|
const def = byKey.get(k);
|
|
if (def) ordered.push(def);
|
|
for (const next of adj.get(k) ?? []) {
|
|
const n = (indeg.get(next) ?? 0) - 1;
|
|
indeg.set(next, n);
|
|
if (n === 0) queue.push(next);
|
|
}
|
|
}
|
|
|
|
if (ordered.length !== defs.length) {
|
|
throw new CycleError('Derived definitions contain a cycle');
|
|
}
|
|
return ordered;
|
|
}
|
|
|
|
export function computeDerived(
|
|
defs: DerivedDefinition[],
|
|
values: {
|
|
attributes?: Record<string, number | string | boolean>;
|
|
skills?: Record<string, number | string | boolean>;
|
|
resources?: Record<string, number>;
|
|
},
|
|
): Record<string, number> {
|
|
const ordered = sortDerived(defs);
|
|
const derived: Record<string, number> = {};
|
|
for (const d of ordered) {
|
|
const ctx = buildFormulaContext({ ...values, derived });
|
|
derived[d.key] = evaluateFormula(d.formula, ctx, d.round ?? 'none');
|
|
}
|
|
return derived;
|
|
}
|