30 Days of Code — Day 4

Simple TTRPG Dice Roller

Jordan Fox
4 min readMay 18, 2024
Photo by Alperen Yazgı on Unsplash

Alright, Day 4 is going to be dedicated to dice rolling. If you haven’t already, check out yesterday’s progress here.

I updated the diagram to better reflect the return values of the notation interpreting function. I also made a very simple function to roll the dice using the Math.random function from Javascript.

rollDice

function rollDice(num, sides) {
let results = [];
for (let i = 0; i < num; i++) {
results.push(Math.floor(Math.random() * sides) + 1);
}
let total = results.reduce((a, b) => a + b, 0);
return { resultsArray: results, total: total };
}

This function is very basic, but it doesn’t care if the user is flipping a coin or rolling a dice with 1000 sides which is what I wanted.

An argument can be made that specifying the values to represent only possible real dice makes sense, but to me this isn’t important. If a user wants to roll a 1d1027 or 1d3, I’m not really bothered. Maybe it makes sense in the game they’re playing, maybe it doesn’t. Either way, adding a limit here doesn’t do anything but enforce my limited view/understanding of the game and locks out anything else. It’s not impacting the design or implementation of the app, so we’ll leave it alone.

processRolls

Rolling the primary part is easy. We just need to split it apart at the ‘d’ and plug it into the the rollDice function. The complication comes from the modifiers. We don’t know if the user will add a modifier at all, and if they do, we don’t know how many they’ll add.

Because our parser so excellently breaks the modifiers into parts and adds them to an array, we’re able to take the same rollDice function and apply it to the array of modifiers as we iterate through it.

// Function to process the parsed components and roll the dice
function processRolls(parsedComponents) {
let { primaryPart, inputParts } = parsedComponents;

// Process primary roll
let primaryRoll = primaryPart.match(/(\d+)d(\d+)/);
let primaryResults = rollDice(
parseInt(primaryRoll[1]),
parseInt(primaryRoll[2])
);

// Process modifiers
let parsedModifiersArray = [];
let modifierResults = {};
let modifierTotal = 0;

inputParts.forEach((modifier, index) => {
let results;
if (modifier.match(/[+\-]?\d+d\d+/)) {
let sign = modifier[0] === "-" ? -1 : 1;
let [_, num, sides] = modifier.match(/([+\-]?)(\d+)d(\d+)/).slice(1);
results = rollDice(parseInt(num), parseInt(sides));
results.total *= sign;
} else if (modifier.match(/[+\-]?\d+/)) {
let value = parseInt(modifier);
results = { resultsArray: [value], total: value };
}

parsedModifiersArray.push(modifier);
modifierResults[index] = results;
modifierTotal += results.total;
});

// Calculate final result
let finalResult = primaryResults.total + modifierTotal;

// Format the output
let output = {
primaryRoll: {
dice: primaryPart,
resultsArray: primaryResults.resultsArray,
total: primaryResults.total,
},
modifier: {
modifiers: inputParts.join(""),
parsedModifiersArray: parsedModifiersArray,
modifierResults: modifierResults,
modifierTotal: modifierTotal,
},
finalResult: finalResult,
};

return output;
}

And we output the results as an object. I like this approach because it breaks down what the user rolled on each modifier, so they won’t need to wonder if everything is correct. This could also be helpful if parts of the roll are different damage types which in D&D may be subject to resistances and vulnerabilities.

Does it Work?

The most important question: does it work? Well it seems to so far. I ran a test with a simple d20:

and with something considerably more complicated:

So for right now I think I’m happy with the way that’s working. Right now it’s just all in the App.js file, but we’ll be moving it later to run server side. In fact, I think tomorrow’s article will cover setting up the app in Vercel and making this a Vercel Function.

Thanks for reading, and please comment any feedback you have on how I’m doing so far.

--

--

Jordan Fox

Chicago with my husband. Father of one (dog). Passionate about technology. Occasionally political. Working on my mental health, my family, and my career.