2024-09-08 06:43:39 -06:00

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";
}
}
}