tabletop-commander/lib/ttcQuery/TTCResolvers.ts

149 lines
4.3 KiB
TypeScript

/* eslint-disable react/display-name */
import { Dice } from "../dice";
import { sum } from "../utils/sum";
import { DiceChart } from "./DiceChart";
import { TTCQueryParser } from "./TTCQueryParser";
export class TTCQueryResolver {
private parser: TTCQueryParser | null;
private context: QueryableObject | null = null;
private stack: any[] = [];
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) {
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 === "function" && !onDemand) return last();
return last;
} catch (e) {
return e?.toString();
}
}
private parseResolver(resolver: string) {
if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver);
if (this.isQuery(resolver)) return this.runQuery(resolver);
return resolver;
}
private isQuery(resolver: string) {
return (
resolver.startsWith("?") ||
resolver.startsWith("_") ||
/^\$\d\./.test(resolver)
);
}
private runQuery(query: string) {
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 (
typeof stackItem === "string" &&
Dice.isDice(
stackItem.replace(/\$\d+/g, (e) =>
this.getFromStack<number>(e).toString()
)
)
) {
return this.handleDice(
stackItem.replace(/\$\d+/g, (e) =>
this.getFromStack<number>(e).toString()
),
q
);
}
return this.parser.search(q, stackItem as QueryableObject);
}
// if (query.startsWith("?") || query.startsWith("_"))
return query.startsWith("_") && this.context
? this.parser.search(query.replace("_", "^"), this.context).at(0)
: this.parser.search(query.replace(/^[?_].?/, "")).at(0);
}
private handleDice(dice: string, query: string) {
const d = new Dice(dice);
const [method, n] = query.split(":");
let num = Number(n);
// if (n && n.startsWith("$")) num = this.getFromStack(n);
switch (method) {
case "roll":
return () => d.roll().total;
case "rollAvg":
return () => d.rollAvg();
case "rollTimes":
return () => d.rollTimes(num);
case "rollTimesAvg":
return () => d.rollTimesAvg(num);
case "rollLowest":
return () => d.rollMin();
case "rollHighest":
return () => d.rollMax();
case "rollDropHighest":
return () => sum(...d.roll().results.toSorted().toSpliced(-1, 1));
case "rollDropLowest":
return () => sum(...d.roll().results.toSorted().toSpliced(0, 1));
case "distribution":
return () => DiceChart({ dice: d.getRollDistribution() });
case "distribution.normalized":
return () => DiceChart({ dice: d.getNormalizedRollDistribution() });
default:
return "No valid method provided for dice";
}
}
private isArithmetic(resolver: string) {
return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1;
}
private solveArithmetic(resolver: string) {
const [n1, op, n2] = resolver
.match(/(\$?\d+)([+\-*\/^])(\$?\d+)/)
?.slice(1) || ["", "+", ""];
let num1 = Number(n1),
num2 = Number(n2);
if (n1.startsWith("$")) num1 = this.getFromStack<number>(n1);
if (n2.startsWith("$")) num2 = this.getFromStack<number>(n2);
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";
}
}
public getFromStack<T>(stackIndex: string): T {
const i = Number(stackIndex.replace("$", ""));
const val = this.stack[i] as T;
return val;
}
}