205 lines
5.9 KiB
TypeScript
205 lines
5.9 KiB
TypeScript
/* eslint-disable react/display-name */
|
|
import { ReactNode } from "react";
|
|
import { Dice } from "../dice";
|
|
import { sum } from "../utils/sum";
|
|
import { DiceChart } from "./DiceChart";
|
|
import { TTCQueryParser } from "./TTCQueryParser";
|
|
|
|
interface StackItem {
|
|
value: any;
|
|
display: ReactNode | (() => ReactNode);
|
|
}
|
|
|
|
export class TTCQueryResolver {
|
|
private parser: TTCQueryParser | null;
|
|
private context: QueryableObject | null = null;
|
|
|
|
private stack: StackItem[] = [];
|
|
|
|
constructor(parser?: TTCQueryParser) {
|
|
this.parser = parser || null;
|
|
}
|
|
|
|
public setParser(parser: TTCQueryParser) {
|
|
this.parser = parser;
|
|
}
|
|
|
|
public setContext(obj: QueryableObject) {
|
|
this.context = obj;
|
|
}
|
|
|
|
public resolve(resolver: string, onDemand?: boolean): StackItem | undefined {
|
|
try {
|
|
const resList = resolver.split(",");
|
|
for (const res of resList) {
|
|
this.stack.push(this.parseResolver(res));
|
|
}
|
|
const last = this.stack.at(-1);
|
|
if (typeof last?.display === "function" && !onDemand) {
|
|
last.display = last.display();
|
|
}
|
|
|
|
return last;
|
|
} catch (e) {
|
|
return { value: e?.toString(), display: e?.toString() };
|
|
}
|
|
}
|
|
|
|
private parseResolver(resolver: string): StackItem {
|
|
if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver);
|
|
if (this.isQuery(resolver)) return this.runQuery(resolver);
|
|
if (Dice.isDice(resolver)) {
|
|
const value = new Dice(resolver);
|
|
const dice: StackItem = {
|
|
value,
|
|
display: () => value.toString(),
|
|
};
|
|
return dice;
|
|
}
|
|
return { value: resolver, display: resolver };
|
|
}
|
|
private isQuery(resolver: string) {
|
|
return (
|
|
resolver.startsWith("?") ||
|
|
resolver.startsWith("_") ||
|
|
/^\$\d\./.test(resolver)
|
|
);
|
|
}
|
|
private runQuery(query: string): StackItem {
|
|
if (!this.parser) throw "Parser not defined in query resolver";
|
|
if (query.startsWith("$")) {
|
|
const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || [];
|
|
if (!_) throw "Detected stack query but did not match the regex";
|
|
const stackItem = this.getFromStack(idx);
|
|
|
|
if (stackItem.value instanceof Dice) {
|
|
return {
|
|
value: query,
|
|
display: this.handleDice(stackItem.value, q),
|
|
};
|
|
}
|
|
|
|
const [res] = this.parser.search(q, stackItem.value as QueryableObject);
|
|
if (Dice.isDice(res)) {
|
|
const value = new Dice(res);
|
|
return {
|
|
value,
|
|
display: () => value.toString(),
|
|
};
|
|
}
|
|
return {
|
|
value: res,
|
|
display() {
|
|
return (
|
|
this.value.render ?? this.value ?? "Error resolving query: " + query
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
// if (query.startsWith("?") || query.startsWith("_"))
|
|
const res =
|
|
query.startsWith("_") && this.context
|
|
? this.parser.search(query.replace("_", "^"), this.context).at(0)
|
|
: this.parser.search(query.replace(/^[?_].?/, "")).at(0);
|
|
if (Dice.isDice(res)) {
|
|
const value = new Dice(res);
|
|
return {
|
|
value,
|
|
display: () => value.toString(),
|
|
};
|
|
}
|
|
return {
|
|
value: res,
|
|
display() {
|
|
return (
|
|
this.value.render ?? this.value ?? "Error resolving query: " + query
|
|
);
|
|
},
|
|
};
|
|
}
|
|
|
|
private isArithmetic(resolver: string) {
|
|
return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1;
|
|
}
|
|
private solveArithmetic(resolver: string): StackItem {
|
|
const [n1, op, n2] = resolver
|
|
.match(/(\$?\d+)([+\-*\/^])(\$?\d+)/)
|
|
?.slice(1) || ["", "+", ""];
|
|
let num1: number = Number(n1),
|
|
num2: number = Number(n2);
|
|
|
|
if (n1.startsWith("$")) {
|
|
const result = this.getFromStack(n1).value;
|
|
num1 = result instanceof Dice ? result.roll().total : Number(result);
|
|
}
|
|
if (n2.startsWith("$")) {
|
|
const result = this.getFromStack(n1).value;
|
|
num2 = result instanceof Dice ? result.roll().total : Number(result);
|
|
}
|
|
|
|
const thing: StackItem = {
|
|
value: () => {
|
|
switch (op) {
|
|
case "+":
|
|
return num1 + num2;
|
|
case "-":
|
|
return num1 - num2;
|
|
case "*":
|
|
return num1 * num2;
|
|
case "/":
|
|
return num1 / num2;
|
|
case "^":
|
|
return num1 ^ num2;
|
|
default:
|
|
throw "Arithmetic detected but no proper operator assigned";
|
|
}
|
|
},
|
|
display() {
|
|
return typeof this.value === "function" ? this.value() : this.value;
|
|
},
|
|
};
|
|
|
|
return thing;
|
|
}
|
|
|
|
public getFromStack(stackIndex: string): StackItem {
|
|
if (stackIndex === "$x") return this.stack.at(-1)!;
|
|
const i = Number(stackIndex.replace("$", ""));
|
|
const val = this.stack[i];
|
|
return val;
|
|
}
|
|
private handleDice(dice: Dice, query: string) {
|
|
const [method, n] = query.split(":");
|
|
let num = Number(n);
|
|
// if (n && n.startsWith("$")) num = this.getFromStack(n);
|
|
switch (method) {
|
|
case "roll":
|
|
return () => dice.roll.apply(dice).total;
|
|
case "rollAvg":
|
|
return () => dice.rollAvg.apply(dice);
|
|
case "rollTimes":
|
|
return () => dice.rollTimes.apply(dice, [num]);
|
|
case "rollTimesAvg":
|
|
return () => dice.rollTimesAvg.apply(dice, [num]);
|
|
case "rollLowest":
|
|
return () => dice.rollMin.apply(dice);
|
|
case "rollHighest":
|
|
return () => dice.rollMax.apply(dice);
|
|
case "rollDropHighest":
|
|
return () =>
|
|
sum(...dice.roll.apply(dice).results.toSorted().toSpliced(-1, 1));
|
|
case "rollDropLowest":
|
|
return () =>
|
|
sum(...dice.roll.apply(dice).results.toSorted().toSpliced(0, 1));
|
|
case "distribution":
|
|
return () => DiceChart({ dice: dice.getRollDistribution.apply(dice) });
|
|
case "distribution.normalized":
|
|
return () =>
|
|
DiceChart({ dice: dice.getNormalizedRollDistribution.apply(dice) });
|
|
default:
|
|
return () => "No valid method provided for dice";
|
|
}
|
|
}
|
|
}
|