mirror of
https://github.com/Burn-E99/TheArtificer.git
synced 2026-01-08 05:17:54 -05:00
deno fmt
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { parseRoll } from './parser.ts';
|
||||
|
||||
export default {
|
||||
parseRoll,
|
||||
parseRoll,
|
||||
};
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { CountDetails, RollSet } from './solver.d.ts';
|
||||
|
||||
export const rollCounter = (rollSet: RollSet[]): CountDetails => {
|
||||
const countDetails = {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
};
|
||||
const countDetails = {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
};
|
||||
|
||||
rollSet.forEach((roll) => {
|
||||
countDetails.total++;
|
||||
if (roll.critHit) countDetails.successful++;
|
||||
if (roll.critFail) countDetails.failed++;
|
||||
if (roll.rerolled) countDetails.rerolled++;
|
||||
if (roll.dropped) countDetails.dropped++;
|
||||
if (roll.exploding) countDetails.exploded++;
|
||||
});
|
||||
rollSet.forEach((roll) => {
|
||||
countDetails.total++;
|
||||
if (roll.critHit) countDetails.successful++;
|
||||
if (roll.critFail) countDetails.failed++;
|
||||
if (roll.rerolled) countDetails.rerolled++;
|
||||
if (roll.dropped) countDetails.dropped++;
|
||||
if (roll.exploding) countDetails.exploded++;
|
||||
});
|
||||
|
||||
return countDetails;
|
||||
return countDetails;
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
} from '../../deps.ts';
|
||||
|
||||
import config from '../../config.ts';
|
||||
@ -15,328 +15,328 @@ import { fullSolver } from './solver.ts';
|
||||
// parseRoll(fullCmd, modifiers)
|
||||
// parseRoll handles converting fullCmd into a computer readable format for processing, and finally executes the solving
|
||||
export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => {
|
||||
const operators = ['^', '*', '/', '%', '+', '-', '(', ')'];
|
||||
const returnmsg = <SolvedRoll> {
|
||||
error: false,
|
||||
errorCode: '',
|
||||
errorMsg: '',
|
||||
line1: '',
|
||||
line2: '',
|
||||
line3: '',
|
||||
counts: {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
},
|
||||
};
|
||||
const operators = ['^', '*', '/', '%', '+', '-', '(', ')'];
|
||||
const returnmsg = <SolvedRoll> {
|
||||
error: false,
|
||||
errorCode: '',
|
||||
errorMsg: '',
|
||||
line1: '',
|
||||
line2: '',
|
||||
line3: '',
|
||||
counts: {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
},
|
||||
};
|
||||
|
||||
// Whole function lives in a try-catch to allow safe throwing of errors on purpose
|
||||
try {
|
||||
// Split the fullCmd by the command prefix to allow every roll/math op to be handled individually
|
||||
const sepRolls = fullCmd.split(config.prefix);
|
||||
// Whole function lives in a try-catch to allow safe throwing of errors on purpose
|
||||
try {
|
||||
// Split the fullCmd by the command prefix to allow every roll/math op to be handled individually
|
||||
const sepRolls = fullCmd.split(config.prefix);
|
||||
|
||||
const tempReturnData: ReturnData[] = [];
|
||||
const tempCountDetails: CountDetails[] = [{
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
}];
|
||||
const tempReturnData: ReturnData[] = [];
|
||||
const tempCountDetails: CountDetails[] = [{
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
}];
|
||||
|
||||
// Loop thru all roll/math ops
|
||||
for (const sepRoll of sepRolls) {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRoll}`);
|
||||
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
|
||||
const [tempConf, tempFormat] = sepRoll.split(config.postfix);
|
||||
// Loop thru all roll/math ops
|
||||
for (const sepRoll of sepRolls) {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRoll}`);
|
||||
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
|
||||
const [tempConf, tempFormat] = sepRoll.split(config.postfix);
|
||||
|
||||
// Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
|
||||
const mathConf: (string | number | SolvedStep)[] = <(string | number | SolvedStep)[]> tempConf.replace(/ /g, '').split(/([-+()*/%^])/g);
|
||||
// Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
|
||||
const mathConf: (string | number | SolvedStep)[] = <(string | number | SolvedStep)[]> tempConf.replace(/ /g, '').split(/([-+()*/%^])/g);
|
||||
|
||||
// Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
|
||||
let parenCnt = 0;
|
||||
mathConf.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
|
||||
if (e === '(') {
|
||||
parenCnt++;
|
||||
} else if (e === ')') {
|
||||
parenCnt--;
|
||||
}
|
||||
});
|
||||
// Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
|
||||
let parenCnt = 0;
|
||||
mathConf.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
|
||||
if (e === '(') {
|
||||
parenCnt++;
|
||||
} else if (e === ')') {
|
||||
parenCnt--;
|
||||
}
|
||||
});
|
||||
|
||||
// If the parenCnt is not 0, then we do not have balanced parens and need to error out now
|
||||
if (parenCnt !== 0) {
|
||||
throw new Error('UnbalancedParens');
|
||||
}
|
||||
// If the parenCnt is not 0, then we do not have balanced parens and need to error out now
|
||||
if (parenCnt !== 0) {
|
||||
throw new Error('UnbalancedParens');
|
||||
}
|
||||
|
||||
// Evaluate all rolls into stepSolve format and all numbers into floats
|
||||
for (let i = 0; i < mathConf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
|
||||
if (mathConf[i].toString().length === 0) {
|
||||
// If its an empty string, get it out of here
|
||||
mathConf.splice(i, 1);
|
||||
i--;
|
||||
} else if (mathConf[i] == parseFloat(mathConf[i].toString())) {
|
||||
// If its a number, parse the number out
|
||||
mathConf[i] = parseFloat(mathConf[i].toString());
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'e') {
|
||||
// If the operand is the constant e, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Math.E,
|
||||
details: '*e*',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'fart' || mathConf[i].toString().toLowerCase() === '💩') {
|
||||
mathConf[i] = {
|
||||
total: 7,
|
||||
details: '💩',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'inf' || mathConf[i].toString().toLowerCase() === 'infinity' || mathConf[i].toString().toLowerCase() === '∞') {
|
||||
// If the operand is the constant Infinity, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Infinity,
|
||||
details: '∞',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() === '𝜋') {
|
||||
// If the operand is the constant pi, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Math.PI,
|
||||
details: '𝜋',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'pie') {
|
||||
// If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them)
|
||||
mathConf[i] = {
|
||||
total: Math.PI,
|
||||
details: '𝜋',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
mathConf.splice(i + 1, 0, ...['*', {
|
||||
total: Math.E,
|
||||
details: '*e*',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
}]);
|
||||
} else if (!operators.includes(mathConf[i].toString())) {
|
||||
// If nothing else has handled it by now, try it as a roll
|
||||
const formattedRoll = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll);
|
||||
mathConf[i] = formattedRoll.solvedStep;
|
||||
tempCountDetails.push(formattedRoll.countDetails);
|
||||
}
|
||||
// Evaluate all rolls into stepSolve format and all numbers into floats
|
||||
for (let i = 0; i < mathConf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
|
||||
if (mathConf[i].toString().length === 0) {
|
||||
// If its an empty string, get it out of here
|
||||
mathConf.splice(i, 1);
|
||||
i--;
|
||||
} else if (mathConf[i] == parseFloat(mathConf[i].toString())) {
|
||||
// If its a number, parse the number out
|
||||
mathConf[i] = parseFloat(mathConf[i].toString());
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'e') {
|
||||
// If the operand is the constant e, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Math.E,
|
||||
details: '*e*',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'fart' || mathConf[i].toString().toLowerCase() === '💩') {
|
||||
mathConf[i] = {
|
||||
total: 7,
|
||||
details: '💩',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'inf' || mathConf[i].toString().toLowerCase() === 'infinity' || mathConf[i].toString().toLowerCase() === '∞') {
|
||||
// If the operand is the constant Infinity, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Infinity,
|
||||
details: '∞',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() === '𝜋') {
|
||||
// If the operand is the constant pi, create a SolvedStep for it
|
||||
mathConf[i] = {
|
||||
total: Math.PI,
|
||||
details: '𝜋',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
} else if (mathConf[i].toString().toLowerCase() === 'pie') {
|
||||
// If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them)
|
||||
mathConf[i] = {
|
||||
total: Math.PI,
|
||||
details: '𝜋',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
mathConf.splice(i + 1, 0, ...['*', {
|
||||
total: Math.E,
|
||||
details: '*e*',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
}]);
|
||||
} else if (!operators.includes(mathConf[i].toString())) {
|
||||
// If nothing else has handled it by now, try it as a roll
|
||||
const formattedRoll = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll);
|
||||
mathConf[i] = formattedRoll.solvedStep;
|
||||
tempCountDetails.push(formattedRoll.countDetails);
|
||||
}
|
||||
|
||||
if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) {
|
||||
if (typeof mathConf[i] === 'number') {
|
||||
mathConf[i] = <number> mathConf[i] * -1;
|
||||
} else {
|
||||
(<SolvedStep> mathConf[i]).total = (<SolvedStep> mathConf[i]).total * -1;
|
||||
(<SolvedStep> mathConf[i]).details = `-${(<SolvedStep> mathConf[i]).details}`;
|
||||
}
|
||||
mathConf.splice(i - 1, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) {
|
||||
if (typeof mathConf[i] === 'number') {
|
||||
mathConf[i] = <number> mathConf[i] * -1;
|
||||
} else {
|
||||
(<SolvedStep> mathConf[i]).total = (<SolvedStep> mathConf[i]).total * -1;
|
||||
(<SolvedStep> mathConf[i]).details = `-${(<SolvedStep> mathConf[i]).details}`;
|
||||
}
|
||||
mathConf.splice(i - 1, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Now that mathConf is parsed, send it into the solver
|
||||
const tempSolved = fullSolver(mathConf, false);
|
||||
// Now that mathConf is parsed, send it into the solver
|
||||
const tempSolved = fullSolver(mathConf, false);
|
||||
|
||||
// Push all of this step's solved data into the temp array
|
||||
tempReturnData.push({
|
||||
rollTotal: tempSolved.total,
|
||||
rollPostFormat: tempFormat,
|
||||
rollDetails: tempSolved.details,
|
||||
containsCrit: tempSolved.containsCrit,
|
||||
containsFail: tempSolved.containsFail,
|
||||
initConfig: tempConf,
|
||||
});
|
||||
}
|
||||
// Push all of this step's solved data into the temp array
|
||||
tempReturnData.push({
|
||||
rollTotal: tempSolved.total,
|
||||
rollPostFormat: tempFormat,
|
||||
rollDetails: tempSolved.details,
|
||||
containsCrit: tempSolved.containsCrit,
|
||||
containsFail: tempSolved.containsFail,
|
||||
initConfig: tempConf,
|
||||
});
|
||||
}
|
||||
|
||||
// Parsing/Solving done, time to format the output for Discord
|
||||
// Parsing/Solving done, time to format the output for Discord
|
||||
|
||||
// Remove any floating spaces from fullCmd
|
||||
if (fullCmd[fullCmd.length - 1] === ' ') {
|
||||
fullCmd = fullCmd.substring(0, fullCmd.length - 1);
|
||||
}
|
||||
// Remove any floating spaces from fullCmd
|
||||
if (fullCmd[fullCmd.length - 1] === ' ') {
|
||||
fullCmd = fullCmd.substring(0, fullCmd.length - 1);
|
||||
}
|
||||
|
||||
// Escape any | and ` chars in fullCmd to prevent spoilers and code blocks from acting up
|
||||
fullCmd = escapeCharacters(fullCmd, '|');
|
||||
fullCmd = fullCmd.replace(/`/g, '');
|
||||
// Escape any | and ` chars in fullCmd to prevent spoilers and code blocks from acting up
|
||||
fullCmd = escapeCharacters(fullCmd, '|');
|
||||
fullCmd = fullCmd.replace(/`/g, '');
|
||||
|
||||
let line1 = '';
|
||||
let line2 = '';
|
||||
let line3 = '';
|
||||
let line1 = '';
|
||||
let line2 = '';
|
||||
let line3 = '';
|
||||
|
||||
// If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting
|
||||
if (modifiers.maxRoll) {
|
||||
line1 = ` requested the theoretical maximum of:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Theoretical Maximum Results: ';
|
||||
} else if (modifiers.nominalRoll) {
|
||||
line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Theoretical Nominal Results: ';
|
||||
} else if (modifiers.order === 'a') {
|
||||
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
tempReturnData.sort(compareTotalRolls);
|
||||
} else if (modifiers.order === 'd') {
|
||||
line1 = ` requested the following rolls to be ordered from greatest to least:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
tempReturnData.sort(compareTotalRolls);
|
||||
tempReturnData.reverse();
|
||||
} else {
|
||||
line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
}
|
||||
// If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting
|
||||
if (modifiers.maxRoll) {
|
||||
line1 = ` requested the theoretical maximum of:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Theoretical Maximum Results: ';
|
||||
} else if (modifiers.nominalRoll) {
|
||||
line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Theoretical Nominal Results: ';
|
||||
} else if (modifiers.order === 'a') {
|
||||
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
tempReturnData.sort(compareTotalRolls);
|
||||
} else if (modifiers.order === 'd') {
|
||||
line1 = ` requested the following rolls to be ordered from greatest to least:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
tempReturnData.sort(compareTotalRolls);
|
||||
tempReturnData.reverse();
|
||||
} else {
|
||||
line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``;
|
||||
line2 = 'Results: ';
|
||||
}
|
||||
|
||||
// Fill out all of the details and results now
|
||||
tempReturnData.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
|
||||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
// Fill out all of the details and results now
|
||||
tempReturnData.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
|
||||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
|
||||
// If the roll containted a crit success or fail, set the formatting around it
|
||||
if (e.containsCrit) {
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.containsFail) {
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
// If the roll containted a crit success or fail, set the formatting around it
|
||||
if (e.containsCrit) {
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.containsFail) {
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
|
||||
// Populate line2 (the results) and line3 (the details) with their data
|
||||
if (modifiers.order === '') {
|
||||
line2 += `${preFormat}${e.rollTotal}${postFormat}${escapeCharacters(e.rollPostFormat, '|*_~`')}`;
|
||||
} else {
|
||||
// If order is on, turn rolls into csv without formatting
|
||||
line2 += `${preFormat}${e.rollTotal}${postFormat}, `;
|
||||
}
|
||||
// Populate line2 (the results) and line3 (the details) with their data
|
||||
if (modifiers.order === '') {
|
||||
line2 += `${preFormat}${e.rollTotal}${postFormat}${escapeCharacters(e.rollPostFormat, '|*_~`')}`;
|
||||
} else {
|
||||
// If order is on, turn rolls into csv without formatting
|
||||
line2 += `${preFormat}${e.rollTotal}${postFormat}, `;
|
||||
}
|
||||
|
||||
line2 = line2.replace(/\*\*\*\*/g, '** **').replace(/____/g, '__ __').replace(/~~~~/g, '~~ ~~');
|
||||
line2 = line2.replace(/\*\*\*\*/g, '** **').replace(/____/g, '__ __').replace(/~~~~/g, '~~ ~~');
|
||||
|
||||
line3 += `\`${e.initConfig}\` = ${e.rollDetails} = ${preFormat}${e.rollTotal}${postFormat}\n`;
|
||||
});
|
||||
line3 += `\`${e.initConfig}\` = ${e.rollDetails} = ${preFormat}${e.rollTotal}${postFormat}\n`;
|
||||
});
|
||||
|
||||
// If order is on, remove trailing ", "
|
||||
if (modifiers.order !== '') {
|
||||
line2 = line2.substring(0, line2.length - 2);
|
||||
}
|
||||
// If order is on, remove trailing ", "
|
||||
if (modifiers.order !== '') {
|
||||
line2 = line2.substring(0, line2.length - 2);
|
||||
}
|
||||
|
||||
// Fill in the return block
|
||||
returnmsg.line1 = line1;
|
||||
returnmsg.line2 = line2;
|
||||
returnmsg.line3 = line3;
|
||||
// Fill in the return block
|
||||
returnmsg.line1 = line1;
|
||||
returnmsg.line2 = line2;
|
||||
returnmsg.line3 = line3;
|
||||
|
||||
// Reduce counts to a single object
|
||||
returnmsg.counts = tempCountDetails.reduce((acc, cnt) => ({
|
||||
total: acc.total + cnt.total,
|
||||
successful: acc.successful + cnt.successful,
|
||||
failed: acc.failed + cnt.failed,
|
||||
rerolled: acc.rerolled + cnt.rerolled,
|
||||
dropped: acc.dropped + cnt.dropped,
|
||||
exploded: acc.exploded + cnt.exploded,
|
||||
}));
|
||||
} catch (solverError) {
|
||||
// Welp, the unthinkable happened, we hit an error
|
||||
// Reduce counts to a single object
|
||||
returnmsg.counts = tempCountDetails.reduce((acc, cnt) => ({
|
||||
total: acc.total + cnt.total,
|
||||
successful: acc.successful + cnt.successful,
|
||||
failed: acc.failed + cnt.failed,
|
||||
rerolled: acc.rerolled + cnt.rerolled,
|
||||
dropped: acc.dropped + cnt.dropped,
|
||||
exploded: acc.exploded + cnt.exploded,
|
||||
}));
|
||||
} catch (solverError) {
|
||||
// Welp, the unthinkable happened, we hit an error
|
||||
|
||||
// Split on _ for the error messages that have more info than just their name
|
||||
const [errorName, errorDetails] = solverError.message.split('_');
|
||||
// Split on _ for the error messages that have more info than just their name
|
||||
const [errorName, errorDetails] = solverError.message.split('_');
|
||||
|
||||
let errorMsg = '';
|
||||
let errorMsg = '';
|
||||
|
||||
// Translate the errorName to a specific errorMsg
|
||||
switch (errorName) {
|
||||
case 'YouNeedAD':
|
||||
errorMsg = 'Formatting Error: Missing die size and count config';
|
||||
break;
|
||||
case 'FormattingError':
|
||||
errorMsg = 'Formatting Error: Cannot use Keep and Drop at the same time, remove all but one and repeat roll';
|
||||
break;
|
||||
case 'NoMaxWithDash':
|
||||
errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct';
|
||||
break;
|
||||
case 'UnknownOperation':
|
||||
errorMsg = `Error: Unknown Operation ${errorDetails}`;
|
||||
if (errorDetails === '-') {
|
||||
errorMsg += '\nNote: Negative numbers are not supported';
|
||||
} else if (errorDetails === ' ') {
|
||||
errorMsg += `\nNote: Every roll must be closed by ${config.postfix}`;
|
||||
}
|
||||
break;
|
||||
case 'NoZerosAllowed':
|
||||
errorMsg = 'Formatting Error: ';
|
||||
switch (errorDetails) {
|
||||
case 'base':
|
||||
errorMsg += 'Die Size and Die Count';
|
||||
break;
|
||||
case 'drop':
|
||||
errorMsg += 'Drop (d or dl)';
|
||||
break;
|
||||
case 'keep':
|
||||
errorMsg += 'Keep (k or kh)';
|
||||
break;
|
||||
case 'dropHigh':
|
||||
errorMsg += 'Drop Highest (dh)';
|
||||
break;
|
||||
case 'keepLow':
|
||||
errorMsg += 'Keep Lowest (kl)';
|
||||
break;
|
||||
case 'reroll':
|
||||
errorMsg += 'Reroll (r)';
|
||||
break;
|
||||
case 'critScore':
|
||||
errorMsg += 'Crit Score (cs)';
|
||||
break;
|
||||
default:
|
||||
errorMsg += `Unhandled - ${errorDetails}`;
|
||||
break;
|
||||
}
|
||||
errorMsg += ' cannot be zero';
|
||||
break;
|
||||
case 'CritScoreMinGtrMax':
|
||||
errorMsg = 'Formatting Error: CritScore maximum cannot be greater than minimum, check formatting and flip min/max';
|
||||
break;
|
||||
case 'MaxLoopsExceeded':
|
||||
errorMsg = 'Error: Roll is too complex or reaches infinity';
|
||||
break;
|
||||
case 'UnbalancedParens':
|
||||
errorMsg = 'Formatting Error: At least one of the equations contains unbalanced parenthesis';
|
||||
break;
|
||||
case 'EMDASNotNumber':
|
||||
errorMsg = 'Error: One or more operands is not a number';
|
||||
break;
|
||||
case 'ConfWhat':
|
||||
errorMsg = 'Error: Not all values got processed, please report the command used';
|
||||
break;
|
||||
case 'OperatorWhat':
|
||||
errorMsg = 'Error: Something really broke with the Operator, try again';
|
||||
break;
|
||||
case 'OperandNaN':
|
||||
errorMsg = 'Error: One or more operands reached NaN, check input';
|
||||
break;
|
||||
case 'UndefinedStep':
|
||||
errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input';
|
||||
break;
|
||||
default:
|
||||
log(LT.ERROR, `Undangled Error: ${errorName}, ${errorDetails}`);
|
||||
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;
|
||||
break;
|
||||
}
|
||||
// Translate the errorName to a specific errorMsg
|
||||
switch (errorName) {
|
||||
case 'YouNeedAD':
|
||||
errorMsg = 'Formatting Error: Missing die size and count config';
|
||||
break;
|
||||
case 'FormattingError':
|
||||
errorMsg = 'Formatting Error: Cannot use Keep and Drop at the same time, remove all but one and repeat roll';
|
||||
break;
|
||||
case 'NoMaxWithDash':
|
||||
errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct';
|
||||
break;
|
||||
case 'UnknownOperation':
|
||||
errorMsg = `Error: Unknown Operation ${errorDetails}`;
|
||||
if (errorDetails === '-') {
|
||||
errorMsg += '\nNote: Negative numbers are not supported';
|
||||
} else if (errorDetails === ' ') {
|
||||
errorMsg += `\nNote: Every roll must be closed by ${config.postfix}`;
|
||||
}
|
||||
break;
|
||||
case 'NoZerosAllowed':
|
||||
errorMsg = 'Formatting Error: ';
|
||||
switch (errorDetails) {
|
||||
case 'base':
|
||||
errorMsg += 'Die Size and Die Count';
|
||||
break;
|
||||
case 'drop':
|
||||
errorMsg += 'Drop (d or dl)';
|
||||
break;
|
||||
case 'keep':
|
||||
errorMsg += 'Keep (k or kh)';
|
||||
break;
|
||||
case 'dropHigh':
|
||||
errorMsg += 'Drop Highest (dh)';
|
||||
break;
|
||||
case 'keepLow':
|
||||
errorMsg += 'Keep Lowest (kl)';
|
||||
break;
|
||||
case 'reroll':
|
||||
errorMsg += 'Reroll (r)';
|
||||
break;
|
||||
case 'critScore':
|
||||
errorMsg += 'Crit Score (cs)';
|
||||
break;
|
||||
default:
|
||||
errorMsg += `Unhandled - ${errorDetails}`;
|
||||
break;
|
||||
}
|
||||
errorMsg += ' cannot be zero';
|
||||
break;
|
||||
case 'CritScoreMinGtrMax':
|
||||
errorMsg = 'Formatting Error: CritScore maximum cannot be greater than minimum, check formatting and flip min/max';
|
||||
break;
|
||||
case 'MaxLoopsExceeded':
|
||||
errorMsg = 'Error: Roll is too complex or reaches infinity';
|
||||
break;
|
||||
case 'UnbalancedParens':
|
||||
errorMsg = 'Formatting Error: At least one of the equations contains unbalanced parenthesis';
|
||||
break;
|
||||
case 'EMDASNotNumber':
|
||||
errorMsg = 'Error: One or more operands is not a number';
|
||||
break;
|
||||
case 'ConfWhat':
|
||||
errorMsg = 'Error: Not all values got processed, please report the command used';
|
||||
break;
|
||||
case 'OperatorWhat':
|
||||
errorMsg = 'Error: Something really broke with the Operator, try again';
|
||||
break;
|
||||
case 'OperandNaN':
|
||||
errorMsg = 'Error: One or more operands reached NaN, check input';
|
||||
break;
|
||||
case 'UndefinedStep':
|
||||
errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input';
|
||||
break;
|
||||
default:
|
||||
log(LT.ERROR, `Undangled Error: ${errorName}, ${errorDetails}`);
|
||||
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;
|
||||
break;
|
||||
}
|
||||
|
||||
// Fill in the return block
|
||||
returnmsg.error = true;
|
||||
returnmsg.errorCode = solverError.message;
|
||||
returnmsg.errorMsg = errorMsg;
|
||||
}
|
||||
// Fill in the return block
|
||||
returnmsg.error = true;
|
||||
returnmsg.errorCode = solverError.message;
|
||||
returnmsg.errorMsg = errorMsg;
|
||||
}
|
||||
|
||||
return returnmsg;
|
||||
return returnmsg;
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
} from '../../deps.ts';
|
||||
|
||||
import { roll } from './roller.ts';
|
||||
@ -12,77 +12,77 @@ import { loggingEnabled } from './rollUtils.ts';
|
||||
// formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep
|
||||
// formatRoll handles creating and formatting the completed rolls into the SolvedStep format
|
||||
export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: boolean): RollFormat => {
|
||||
let tempTotal = 0;
|
||||
let tempDetails = '[';
|
||||
let tempCrit = false;
|
||||
let tempFail = false;
|
||||
let tempTotal = 0;
|
||||
let tempDetails = '[';
|
||||
let tempCrit = false;
|
||||
let tempFail = false;
|
||||
|
||||
// Generate the roll, passing flags thru
|
||||
const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll);
|
||||
// Generate the roll, passing flags thru
|
||||
const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll);
|
||||
|
||||
// Loop thru all parts of the roll to document everything that was done to create the total roll
|
||||
tempRollSet.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
|
||||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
// Loop thru all parts of the roll to document everything that was done to create the total roll
|
||||
tempRollSet.forEach((e) => {
|
||||
loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
|
||||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
|
||||
if (!e.dropped && !e.rerolled) {
|
||||
// If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail
|
||||
switch (e.type) {
|
||||
case 'ova':
|
||||
case 'roll20':
|
||||
case 'fate':
|
||||
tempTotal += e.roll;
|
||||
break;
|
||||
case 'cwod':
|
||||
tempTotal += e.critHit ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
if (e.critHit) {
|
||||
tempCrit = true;
|
||||
}
|
||||
if (e.critFail) {
|
||||
tempFail = true;
|
||||
}
|
||||
}
|
||||
// If the roll was a crit hit or fail, or dropped/rerolled, add the formatting needed
|
||||
if (e.critHit) {
|
||||
// Bold for crit success
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.critFail) {
|
||||
// Underline for crit fail
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
if (e.dropped || e.rerolled) {
|
||||
// Strikethrough for dropped/rerolled rolls
|
||||
preFormat = `~~${preFormat}`;
|
||||
postFormat = `${postFormat}~~`;
|
||||
}
|
||||
if (e.exploding) {
|
||||
// Add ! to indicate the roll came from an explosion
|
||||
postFormat = `!${postFormat}`;
|
||||
}
|
||||
if (!e.dropped && !e.rerolled) {
|
||||
// If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail
|
||||
switch (e.type) {
|
||||
case 'ova':
|
||||
case 'roll20':
|
||||
case 'fate':
|
||||
tempTotal += e.roll;
|
||||
break;
|
||||
case 'cwod':
|
||||
tempTotal += e.critHit ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
if (e.critHit) {
|
||||
tempCrit = true;
|
||||
}
|
||||
if (e.critFail) {
|
||||
tempFail = true;
|
||||
}
|
||||
}
|
||||
// If the roll was a crit hit or fail, or dropped/rerolled, add the formatting needed
|
||||
if (e.critHit) {
|
||||
// Bold for crit success
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.critFail) {
|
||||
// Underline for crit fail
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
if (e.dropped || e.rerolled) {
|
||||
// Strikethrough for dropped/rerolled rolls
|
||||
preFormat = `~~${preFormat}`;
|
||||
postFormat = `${postFormat}~~`;
|
||||
}
|
||||
if (e.exploding) {
|
||||
// Add ! to indicate the roll came from an explosion
|
||||
postFormat = `!${postFormat}`;
|
||||
}
|
||||
|
||||
// Finally add this to the roll's details
|
||||
tempDetails += `${preFormat}${e.roll}${postFormat} + `;
|
||||
});
|
||||
// After the looping is done, remove the extra " + " from the details and cap it with the closing ]
|
||||
tempDetails = tempDetails.substring(0, tempDetails.length - 3);
|
||||
if (tempRollSet[0]?.type === 'cwod') {
|
||||
tempDetails += `, ${tempRollSet.filter((e) => e.critHit).length} Successes, ${tempRollSet.filter((e) => e.critFail).length} Fails`;
|
||||
}
|
||||
tempDetails += ']';
|
||||
// Finally add this to the roll's details
|
||||
tempDetails += `${preFormat}${e.roll}${postFormat} + `;
|
||||
});
|
||||
// After the looping is done, remove the extra " + " from the details and cap it with the closing ]
|
||||
tempDetails = tempDetails.substring(0, tempDetails.length - 3);
|
||||
if (tempRollSet[0]?.type === 'cwod') {
|
||||
tempDetails += `, ${tempRollSet.filter((e) => e.critHit).length} Successes, ${tempRollSet.filter((e) => e.critFail).length} Fails`;
|
||||
}
|
||||
tempDetails += ']';
|
||||
|
||||
return {
|
||||
solvedStep: {
|
||||
total: tempTotal,
|
||||
details: tempDetails,
|
||||
containsCrit: tempCrit,
|
||||
containsFail: tempFail,
|
||||
},
|
||||
countDetails: rollCounter(tempRollSet),
|
||||
};
|
||||
return {
|
||||
solvedStep: {
|
||||
total: tempTotal,
|
||||
details: tempDetails,
|
||||
containsCrit: tempCrit,
|
||||
containsFail: tempFail,
|
||||
},
|
||||
countDetails: rollCounter(tempRollSet),
|
||||
};
|
||||
};
|
||||
|
||||
@ -43,12 +43,12 @@ const handleRollWorker = async (rq: QueuedRoll) => {
|
||||
(
|
||||
await generateRollEmbed(
|
||||
rq.dd.message.authorId,
|
||||
<SolvedRoll>{
|
||||
<SolvedRoll> {
|
||||
error: true,
|
||||
errorCode: 'TooComplex',
|
||||
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
|
||||
},
|
||||
<RollModifiers>{}
|
||||
<RollModifiers> {},
|
||||
)
|
||||
).embed,
|
||||
],
|
||||
@ -170,14 +170,14 @@ const handleRollWorker = async (rq: QueuedRoll) => {
|
||||
JSON.stringify(
|
||||
rq.modifiers.count
|
||||
? {
|
||||
counts: countEmbed,
|
||||
details: pubEmbedDetails,
|
||||
}
|
||||
counts: countEmbed,
|
||||
details: pubEmbedDetails,
|
||||
}
|
||||
: {
|
||||
details: pubEmbedDetails,
|
||||
}
|
||||
)
|
||||
)
|
||||
details: pubEmbedDetails,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -218,7 +218,7 @@ The results for this roll will replace this message when it is done.`,
|
||||
setInterval(async () => {
|
||||
log(
|
||||
LT.LOG,
|
||||
`Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`
|
||||
`Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`,
|
||||
);
|
||||
if (rollQueue.length && currentWorkers < config.limits.maxWorkers) {
|
||||
const temp = rollQueue.shift();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
} from '../../deps.ts';
|
||||
|
||||
import { ReturnData, RollSet } from './solver.d.ts';
|
||||
@ -11,70 +11,70 @@ export const loggingEnabled = false;
|
||||
// genRoll(size) returns number
|
||||
// genRoll rolls a die of size size and returns the result
|
||||
export const genRoll = (size: number, maximiseRoll: boolean, nominalRoll: boolean): number => {
|
||||
if (maximiseRoll) {
|
||||
return size;
|
||||
} else {
|
||||
// Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
|
||||
return nominalRoll ? ((size / 2) + 0.5) : Math.floor((Math.random() * size) + 1);
|
||||
}
|
||||
if (maximiseRoll) {
|
||||
return size;
|
||||
} else {
|
||||
// Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
|
||||
return nominalRoll ? ((size / 2) + 0.5) : Math.floor((Math.random() * size) + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// genFateRoll returns -1|0|1
|
||||
// genFateRoll turns a d6 into a fate die, with sides: -1, -1, 0, 0, 1, 1
|
||||
export const genFateRoll = (maximiseRoll: boolean, nominalRoll: boolean): number => {
|
||||
if (nominalRoll) {
|
||||
return 0;
|
||||
} else {
|
||||
const sides = [-1, -1, 0, 0, 1, 1];
|
||||
return sides[genRoll(6, maximiseRoll, nominalRoll) - 1];
|
||||
}
|
||||
if (nominalRoll) {
|
||||
return 0;
|
||||
} else {
|
||||
const sides = [-1, -1, 0, 0, 1, 1];
|
||||
return sides[genRoll(6, maximiseRoll, nominalRoll) - 1];
|
||||
}
|
||||
};
|
||||
|
||||
// compareRolls(a, b) returns -1|0|1
|
||||
// compareRolls is used to order an array of RollSets by RollSet.roll
|
||||
export const compareRolls = (a: RollSet, b: RollSet): number => {
|
||||
if (a.roll < b.roll) {
|
||||
return -1;
|
||||
}
|
||||
if (a.roll > b.roll) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
if (a.roll < b.roll) {
|
||||
return -1;
|
||||
}
|
||||
if (a.roll > b.roll) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// compareTotalRolls(a, b) returns -1|0|1
|
||||
// compareTotalRolls is used to order an array of RollSets by RollSet.roll
|
||||
export const compareTotalRolls = (a: ReturnData, b: ReturnData): number => {
|
||||
if (a.rollTotal < b.rollTotal) {
|
||||
return -1;
|
||||
}
|
||||
if (a.rollTotal > b.rollTotal) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
if (a.rollTotal < b.rollTotal) {
|
||||
return -1;
|
||||
}
|
||||
if (a.rollTotal > b.rollTotal) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// compareRolls(a, b) returns -1|0|1
|
||||
// compareRolls is used to order an array of RollSets by RollSet.origidx
|
||||
export const compareOrigidx = (a: RollSet, b: RollSet): number => {
|
||||
if (a.origidx < b.origidx) {
|
||||
return -1;
|
||||
}
|
||||
if (a.origidx > b.origidx) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
if (a.origidx < b.origidx) {
|
||||
return -1;
|
||||
}
|
||||
if (a.origidx > b.origidx) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
// escapeCharacters(str, esc) returns str
|
||||
// escapeCharacters escapes all characters listed in esc
|
||||
export const escapeCharacters = (str: string, esc: string): string => {
|
||||
// Loop thru each esc char one at a time
|
||||
for (const e of esc) {
|
||||
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
|
||||
// Create a new regex to look for that char that needs replaced and escape it
|
||||
const temprgx = new RegExp(`[${e}]`, 'g');
|
||||
str = str.replace(temprgx, `\\${e}`);
|
||||
}
|
||||
return str;
|
||||
// Loop thru each esc char one at a time
|
||||
for (const e of esc) {
|
||||
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
|
||||
// Create a new regex to look for that char that needs replaced and escape it
|
||||
const temprgx = new RegExp(`[${e}]`, 'g');
|
||||
str = str.replace(temprgx, `\\${e}`);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
@ -5,23 +5,23 @@ self.postMessage('ready');
|
||||
|
||||
// Handle the roll
|
||||
self.onmessage = async (e: any) => {
|
||||
const payload = e.data;
|
||||
const returnmsg = parseRoll(payload.rollCmd, payload.modifiers) || {
|
||||
error: true,
|
||||
errorCode: 'EmptyMessage',
|
||||
errorMsg: 'Error: Empty message',
|
||||
line1: '',
|
||||
line2: '',
|
||||
line3: '',
|
||||
counts: {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
},
|
||||
};
|
||||
self.postMessage(returnmsg);
|
||||
self.close();
|
||||
const payload = e.data;
|
||||
const returnmsg = parseRoll(payload.rollCmd, payload.modifiers) || {
|
||||
error: true,
|
||||
errorCode: 'EmptyMessage',
|
||||
errorMsg: 'Error: Empty message',
|
||||
line1: '',
|
||||
line2: '',
|
||||
line3: '',
|
||||
counts: {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
rerolled: 0,
|
||||
dropped: 0,
|
||||
exploded: 0,
|
||||
},
|
||||
};
|
||||
self.postMessage(returnmsg);
|
||||
self.close();
|
||||
};
|
||||
|
||||
1150
src/solver/roller.ts
1150
src/solver/roller.ts
File diff suppressed because it is too large
Load Diff
142
src/solver/solver.d.ts
vendored
142
src/solver/solver.d.ts
vendored
@ -4,99 +4,99 @@ export type RollType = '' | 'roll20' | 'fate' | 'cwod' | 'ova';
|
||||
|
||||
// RollSet is used to preserve all information about a calculated roll
|
||||
export type RollSet = {
|
||||
type: RollType;
|
||||
origidx: number;
|
||||
roll: number;
|
||||
dropped: boolean;
|
||||
rerolled: boolean;
|
||||
exploding: boolean;
|
||||
critHit: boolean;
|
||||
critFail: boolean;
|
||||
type: RollType;
|
||||
origidx: number;
|
||||
roll: number;
|
||||
dropped: boolean;
|
||||
rerolled: boolean;
|
||||
exploding: boolean;
|
||||
critHit: boolean;
|
||||
critFail: boolean;
|
||||
};
|
||||
|
||||
// SolvedStep is used to preserve information while math is being performed on the roll
|
||||
export type SolvedStep = {
|
||||
total: number;
|
||||
details: string;
|
||||
containsCrit: boolean;
|
||||
containsFail: boolean;
|
||||
total: number;
|
||||
details: string;
|
||||
containsCrit: boolean;
|
||||
containsFail: boolean;
|
||||
};
|
||||
|
||||
// ReturnData is the temporary internal type used before getting turned into SolvedRoll
|
||||
export type ReturnData = {
|
||||
rollTotal: number;
|
||||
rollPostFormat: string;
|
||||
rollDetails: string;
|
||||
containsCrit: boolean;
|
||||
containsFail: boolean;
|
||||
initConfig: string;
|
||||
rollTotal: number;
|
||||
rollPostFormat: string;
|
||||
rollDetails: string;
|
||||
containsCrit: boolean;
|
||||
containsFail: boolean;
|
||||
initConfig: string;
|
||||
};
|
||||
|
||||
// CountDetails is the object holding the count data for creating the Count Embed
|
||||
export type CountDetails = {
|
||||
total: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
rerolled: number;
|
||||
dropped: number;
|
||||
exploded: number;
|
||||
total: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
rerolled: number;
|
||||
dropped: number;
|
||||
exploded: number;
|
||||
};
|
||||
|
||||
// RollFormat is the return structure for the rollFormatter
|
||||
export type RollFormat = {
|
||||
solvedStep: SolvedStep;
|
||||
countDetails: CountDetails;
|
||||
solvedStep: SolvedStep;
|
||||
countDetails: CountDetails;
|
||||
};
|
||||
|
||||
// SolvedRoll is the complete solved and formatted roll, or the error said roll created
|
||||
export type SolvedRoll = {
|
||||
error: boolean;
|
||||
errorMsg: string;
|
||||
errorCode: string;
|
||||
line1: string;
|
||||
line2: string;
|
||||
line3: string;
|
||||
counts: CountDetails;
|
||||
error: boolean;
|
||||
errorMsg: string;
|
||||
errorCode: string;
|
||||
line1: string;
|
||||
line2: string;
|
||||
line3: string;
|
||||
counts: CountDetails;
|
||||
};
|
||||
|
||||
// RollConf is used by the roll20 setup
|
||||
export type RollConf = {
|
||||
dieCount: number;
|
||||
dieSize: number;
|
||||
drop: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
keep: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
dropHigh: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
keepLow: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
reroll: {
|
||||
on: boolean;
|
||||
once: boolean;
|
||||
nums: number[];
|
||||
};
|
||||
critScore: {
|
||||
on: boolean;
|
||||
range: number[];
|
||||
};
|
||||
critFail: {
|
||||
on: boolean;
|
||||
range: number[];
|
||||
};
|
||||
exploding: {
|
||||
on: boolean;
|
||||
once: boolean;
|
||||
compounding: boolean;
|
||||
penetrating: boolean;
|
||||
nums: number[];
|
||||
};
|
||||
dieCount: number;
|
||||
dieSize: number;
|
||||
drop: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
keep: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
dropHigh: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
keepLow: {
|
||||
on: boolean;
|
||||
count: number;
|
||||
};
|
||||
reroll: {
|
||||
on: boolean;
|
||||
once: boolean;
|
||||
nums: number[];
|
||||
};
|
||||
critScore: {
|
||||
on: boolean;
|
||||
range: number[];
|
||||
};
|
||||
critFail: {
|
||||
on: boolean;
|
||||
range: number[];
|
||||
};
|
||||
exploding: {
|
||||
on: boolean;
|
||||
once: boolean;
|
||||
compounding: boolean;
|
||||
penetrating: boolean;
|
||||
nums: number[];
|
||||
};
|
||||
};
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
log,
|
||||
// Log4Deno deps
|
||||
LT,
|
||||
} from '../../deps.ts';
|
||||
|
||||
import { SolvedStep } from './solver.d.ts';
|
||||
@ -16,188 +16,188 @@ import { loggingEnabled } from './rollUtils.ts';
|
||||
// fullSolver(conf, wrapDetails) returns one condensed SolvedStep
|
||||
// fullSolver is a function that recursively solves the full roll and math
|
||||
export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean): SolvedStep => {
|
||||
// Initialize PEMDAS
|
||||
const signs = ['^', '*', '/', '%', '+', '-'];
|
||||
const stepSolve = {
|
||||
total: 0,
|
||||
details: '',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
// Initialize PEMDAS
|
||||
const signs = ['^', '*', '/', '%', '+', '-'];
|
||||
const stepSolve = {
|
||||
total: 0,
|
||||
details: '',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
|
||||
// If entering with a single number, note it now
|
||||
let singleNum = false;
|
||||
if (conf.length === 1) {
|
||||
singleNum = true;
|
||||
}
|
||||
// If entering with a single number, note it now
|
||||
let singleNum = false;
|
||||
if (conf.length === 1) {
|
||||
singleNum = true;
|
||||
}
|
||||
|
||||
// Evaluate all parenthesis
|
||||
while (conf.indexOf('(') > -1) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
|
||||
// Get first open parenthesis
|
||||
const openParen = conf.indexOf('(');
|
||||
let closeParen = -1;
|
||||
let nextParen = 0;
|
||||
// Evaluate all parenthesis
|
||||
while (conf.indexOf('(') > -1) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
|
||||
// Get first open parenthesis
|
||||
const openParen = conf.indexOf('(');
|
||||
let closeParen = -1;
|
||||
let nextParen = 0;
|
||||
|
||||
// Using nextParen to count the opening/closing parens, find the matching paren to openParen above
|
||||
for (let i = openParen; i < conf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`);
|
||||
// If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one
|
||||
if (conf[i] === '(') {
|
||||
nextParen++;
|
||||
} else if (conf[i] === ')') {
|
||||
nextParen--;
|
||||
}
|
||||
// Using nextParen to count the opening/closing parens, find the matching paren to openParen above
|
||||
for (let i = openParen; i < conf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`);
|
||||
// If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one
|
||||
if (conf[i] === '(') {
|
||||
nextParen++;
|
||||
} else if (conf[i] === ')') {
|
||||
nextParen--;
|
||||
}
|
||||
|
||||
// When nextParen reaches 0 again, we will have found the matching closing parenthesis and can safely exit the for loop
|
||||
if (nextParen === 0) {
|
||||
closeParen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// When nextParen reaches 0 again, we will have found the matching closing parenthesis and can safely exit the for loop
|
||||
if (nextParen === 0) {
|
||||
closeParen = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we did find the correct closing paren, if not, error out now
|
||||
if (closeParen === -1 || closeParen < openParen) {
|
||||
throw new Error('UnbalancedParens');
|
||||
}
|
||||
// Make sure we did find the correct closing paren, if not, error out now
|
||||
if (closeParen === -1 || closeParen < openParen) {
|
||||
throw new Error('UnbalancedParens');
|
||||
}
|
||||
|
||||
// Call the solver on the items between openParen and closeParen (excluding the parens)
|
||||
const parenSolve = fullSolver(conf.slice(openParen + 1, closeParen), true);
|
||||
// Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent
|
||||
conf.splice(openParen, closeParen - openParen + 1, parenSolve);
|
||||
// Call the solver on the items between openParen and closeParen (excluding the parens)
|
||||
const parenSolve = fullSolver(conf.slice(openParen + 1, closeParen), true);
|
||||
// Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent
|
||||
conf.splice(openParen, closeParen - openParen + 1, parenSolve);
|
||||
|
||||
// Determing if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8)
|
||||
// insertedMult flags if there was a multiplication sign inserted before the parens
|
||||
let insertedMult = false;
|
||||
// Check if a number was directly before openParen and slip in the "*" if needed
|
||||
if (((openParen - 1) > -1) && (signs.indexOf(conf[openParen - 1].toString()) === -1)) {
|
||||
insertedMult = true;
|
||||
conf.splice(openParen, 0, '*');
|
||||
}
|
||||
// Check if a number is directly after closeParen and slip in the "*" if needed
|
||||
if (!insertedMult && (((openParen + 1) < conf.length) && (signs.indexOf(conf[openParen + 1].toString()) === -1))) {
|
||||
conf.splice(openParen + 1, 0, '*');
|
||||
} else if (insertedMult && (((openParen + 2) < conf.length) && (signs.indexOf(conf[openParen + 2].toString()) === -1))) {
|
||||
// insertedMult is utilized here to let us account for an additional item being inserted into the array (the "*" from before openParn)
|
||||
conf.splice(openParen + 2, 0, '*');
|
||||
}
|
||||
}
|
||||
// Determing if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8)
|
||||
// insertedMult flags if there was a multiplication sign inserted before the parens
|
||||
let insertedMult = false;
|
||||
// Check if a number was directly before openParen and slip in the "*" if needed
|
||||
if (((openParen - 1) > -1) && (signs.indexOf(conf[openParen - 1].toString()) === -1)) {
|
||||
insertedMult = true;
|
||||
conf.splice(openParen, 0, '*');
|
||||
}
|
||||
// Check if a number is directly after closeParen and slip in the "*" if needed
|
||||
if (!insertedMult && (((openParen + 1) < conf.length) && (signs.indexOf(conf[openParen + 1].toString()) === -1))) {
|
||||
conf.splice(openParen + 1, 0, '*');
|
||||
} else if (insertedMult && (((openParen + 2) < conf.length) && (signs.indexOf(conf[openParen + 2].toString()) === -1))) {
|
||||
// insertedMult is utilized here to let us account for an additional item being inserted into the array (the "*" from before openParn)
|
||||
conf.splice(openParen + 2, 0, '*');
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest)
|
||||
const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']];
|
||||
allCurOps.forEach((curOps) => {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
|
||||
// Iterate thru all operators/operands in the conf
|
||||
for (let i = 0; i < conf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`);
|
||||
// Check if the current index is in the active teir of operators
|
||||
if (curOps.indexOf(conf[i].toString()) > -1) {
|
||||
// Grab the operands from before and after the operator
|
||||
const operand1 = conf[i - 1];
|
||||
const operand2 = conf[i + 1];
|
||||
// Init temp math to NaN to catch bad parsing
|
||||
let oper1 = NaN;
|
||||
let oper2 = NaN;
|
||||
const subStepSolve = {
|
||||
total: NaN,
|
||||
details: '',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
// Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest)
|
||||
const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']];
|
||||
allCurOps.forEach((curOps) => {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
|
||||
// Iterate thru all operators/operands in the conf
|
||||
for (let i = 0; i < conf.length; i++) {
|
||||
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`);
|
||||
// Check if the current index is in the active teir of operators
|
||||
if (curOps.indexOf(conf[i].toString()) > -1) {
|
||||
// Grab the operands from before and after the operator
|
||||
const operand1 = conf[i - 1];
|
||||
const operand2 = conf[i + 1];
|
||||
// Init temp math to NaN to catch bad parsing
|
||||
let oper1 = NaN;
|
||||
let oper2 = NaN;
|
||||
const subStepSolve = {
|
||||
total: NaN,
|
||||
details: '',
|
||||
containsCrit: false,
|
||||
containsFail: false,
|
||||
};
|
||||
|
||||
// If operand1 is a SolvedStep, populate our subStepSolve with its details and crit/fail flags
|
||||
if (typeof operand1 === 'object') {
|
||||
oper1 = operand1.total;
|
||||
subStepSolve.details = `${operand1.details}\\${conf[i]}`;
|
||||
subStepSolve.containsCrit = operand1.containsCrit;
|
||||
subStepSolve.containsFail = operand1.containsFail;
|
||||
} else {
|
||||
// else parse it as a number and add it to the subStep details
|
||||
if (operand1 || operand1 == 0) {
|
||||
oper1 = parseFloat(operand1.toString());
|
||||
subStepSolve.details = `${oper1.toString()}\\${conf[i]}`;
|
||||
}
|
||||
}
|
||||
// If operand1 is a SolvedStep, populate our subStepSolve with its details and crit/fail flags
|
||||
if (typeof operand1 === 'object') {
|
||||
oper1 = operand1.total;
|
||||
subStepSolve.details = `${operand1.details}\\${conf[i]}`;
|
||||
subStepSolve.containsCrit = operand1.containsCrit;
|
||||
subStepSolve.containsFail = operand1.containsFail;
|
||||
} else {
|
||||
// else parse it as a number and add it to the subStep details
|
||||
if (operand1 || operand1 == 0) {
|
||||
oper1 = parseFloat(operand1.toString());
|
||||
subStepSolve.details = `${oper1.toString()}\\${conf[i]}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If operand2 is a SolvedStep, populate our subStepSolve with its details without overriding what operand1 filled in
|
||||
if (typeof operand2 === 'object') {
|
||||
oper2 = operand2.total;
|
||||
subStepSolve.details += operand2.details;
|
||||
subStepSolve.containsCrit = subStepSolve.containsCrit || operand2.containsCrit;
|
||||
subStepSolve.containsFail = subStepSolve.containsFail || operand2.containsFail;
|
||||
} else {
|
||||
// else parse it as a number and add it to the subStep details
|
||||
oper2 = parseFloat(operand2.toString());
|
||||
subStepSolve.details += oper2;
|
||||
}
|
||||
// If operand2 is a SolvedStep, populate our subStepSolve with its details without overriding what operand1 filled in
|
||||
if (typeof operand2 === 'object') {
|
||||
oper2 = operand2.total;
|
||||
subStepSolve.details += operand2.details;
|
||||
subStepSolve.containsCrit = subStepSolve.containsCrit || operand2.containsCrit;
|
||||
subStepSolve.containsFail = subStepSolve.containsFail || operand2.containsFail;
|
||||
} else {
|
||||
// else parse it as a number and add it to the subStep details
|
||||
oper2 = parseFloat(operand2.toString());
|
||||
subStepSolve.details += oper2;
|
||||
}
|
||||
|
||||
// Make sure neither operand is NaN before continuing
|
||||
if (isNaN(oper1) || isNaN(oper2)) {
|
||||
throw new Error('OperandNaN');
|
||||
}
|
||||
// Make sure neither operand is NaN before continuing
|
||||
if (isNaN(oper1) || isNaN(oper2)) {
|
||||
throw new Error('OperandNaN');
|
||||
}
|
||||
|
||||
// Verify a second time that both are numbers before doing math, throwing an error if necessary
|
||||
if ((typeof oper1 === 'number') && (typeof oper2 === 'number')) {
|
||||
// Finally do the operator on the operands, throw an error if the operator is not found
|
||||
switch (conf[i]) {
|
||||
case '^':
|
||||
subStepSolve.total = Math.pow(oper1, oper2);
|
||||
break;
|
||||
case '*':
|
||||
subStepSolve.total = oper1 * oper2;
|
||||
break;
|
||||
case '/':
|
||||
subStepSolve.total = oper1 / oper2;
|
||||
break;
|
||||
case '%':
|
||||
subStepSolve.total = oper1 % oper2;
|
||||
break;
|
||||
case '+':
|
||||
subStepSolve.total = oper1 + oper2;
|
||||
break;
|
||||
case '-':
|
||||
subStepSolve.total = oper1 - oper2;
|
||||
break;
|
||||
default:
|
||||
throw new Error('OperatorWhat');
|
||||
}
|
||||
} else {
|
||||
throw new Error('EMDASNotNumber');
|
||||
}
|
||||
// Verify a second time that both are numbers before doing math, throwing an error if necessary
|
||||
if ((typeof oper1 === 'number') && (typeof oper2 === 'number')) {
|
||||
// Finally do the operator on the operands, throw an error if the operator is not found
|
||||
switch (conf[i]) {
|
||||
case '^':
|
||||
subStepSolve.total = Math.pow(oper1, oper2);
|
||||
break;
|
||||
case '*':
|
||||
subStepSolve.total = oper1 * oper2;
|
||||
break;
|
||||
case '/':
|
||||
subStepSolve.total = oper1 / oper2;
|
||||
break;
|
||||
case '%':
|
||||
subStepSolve.total = oper1 % oper2;
|
||||
break;
|
||||
case '+':
|
||||
subStepSolve.total = oper1 + oper2;
|
||||
break;
|
||||
case '-':
|
||||
subStepSolve.total = oper1 - oper2;
|
||||
break;
|
||||
default:
|
||||
throw new Error('OperatorWhat');
|
||||
}
|
||||
} else {
|
||||
throw new Error('EMDASNotNumber');
|
||||
}
|
||||
|
||||
// Replace the two operands and their operator with our subStepSolve
|
||||
conf.splice(i - 1, 3, subStepSolve);
|
||||
// Because we are messing around with the array we are iterating thru, we need to back up one idx to make sure every operator gets processed
|
||||
i--;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Replace the two operands and their operator with our subStepSolve
|
||||
conf.splice(i - 1, 3, subStepSolve);
|
||||
// Because we are messing around with the array we are iterating thru, we need to back up one idx to make sure every operator gets processed
|
||||
i--;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If we somehow have more than one item left in conf at this point, something broke, throw an error
|
||||
if (conf.length > 1) {
|
||||
loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`);
|
||||
throw new Error('ConfWhat');
|
||||
} else if (singleNum && (typeof (conf[0]) === 'number')) {
|
||||
// If we are only left with a number, populate the stepSolve with it
|
||||
stepSolve.total = conf[0];
|
||||
stepSolve.details = conf[0].toString();
|
||||
} else {
|
||||
// Else fully populate the stepSolve with what was computed
|
||||
stepSolve.total = (<SolvedStep> conf[0]).total;
|
||||
stepSolve.details = (<SolvedStep> conf[0]).details;
|
||||
stepSolve.containsCrit = (<SolvedStep> conf[0]).containsCrit;
|
||||
stepSolve.containsFail = (<SolvedStep> conf[0]).containsFail;
|
||||
}
|
||||
// If we somehow have more than one item left in conf at this point, something broke, throw an error
|
||||
if (conf.length > 1) {
|
||||
loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`);
|
||||
throw new Error('ConfWhat');
|
||||
} else if (singleNum && (typeof (conf[0]) === 'number')) {
|
||||
// If we are only left with a number, populate the stepSolve with it
|
||||
stepSolve.total = conf[0];
|
||||
stepSolve.details = conf[0].toString();
|
||||
} else {
|
||||
// Else fully populate the stepSolve with what was computed
|
||||
stepSolve.total = (<SolvedStep> conf[0]).total;
|
||||
stepSolve.details = (<SolvedStep> conf[0]).details;
|
||||
stepSolve.containsCrit = (<SolvedStep> conf[0]).containsCrit;
|
||||
stepSolve.containsFail = (<SolvedStep> conf[0]).containsFail;
|
||||
}
|
||||
|
||||
// If this was a nested call, add on parens around the details to show what math we've done
|
||||
if (wrapDetails) {
|
||||
stepSolve.details = `(${stepSolve.details})`;
|
||||
}
|
||||
// If this was a nested call, add on parens around the details to show what math we've done
|
||||
if (wrapDetails) {
|
||||
stepSolve.details = `(${stepSolve.details})`;
|
||||
}
|
||||
|
||||
// If our total has reached undefined for some reason, error out now
|
||||
if (stepSolve.total === undefined) {
|
||||
throw new Error('UndefinedStep');
|
||||
}
|
||||
// If our total has reached undefined for some reason, error out now
|
||||
if (stepSolve.total === undefined) {
|
||||
throw new Error('UndefinedStep');
|
||||
}
|
||||
|
||||
return stepSolve;
|
||||
return stepSolve;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user