As already mentioned in comments and answers, there's no need to worry about performance in this case. You'd probably have to work hard for it to be slow :)
Like rolfl I'd rather work on the structure in order to "unnest" some the code, and split the logic for parsing the string and rolling dice into separate functions.
Sidenote: "Dice" is the plural form ("two dice"), "die" is the singular form ("one die"). So rollDices
is grammatically incorrect, and should just be rollDice
.
Also, while I've never played D&D, I do know that there are some rules about dice rolls that you might want to take into account. For instance, the question of what dice actually exist. Your current code would gladly roll a d3 die, even though that's not actually a thing. (Edit: aaaaaand I was wrong. According to the comments a d3 is a thing. But the code below will support a d3 just fine regardless; you can just add it if you need it.)
I'd try to make a slightly more generic model of some dice, and then worry about parsing string input. You can then use said dice in whatever way you need later.
For instance, in a sorta DSL-style you could do:
function makeDie(sides) {
var die = function () {
return 1 + Math.random() * sides | 0;
};
die.times = function (count) {
var rolls = [];
for(var i = 0 ; i < count ; i++) {
rolls.push(this());
}
return rolls;
};
return die;
}
var dice = {
d4: makeDie(4),
d6: makeDie(6),
d8: makeDie(8),
d10: makeDie(10),
d12: makeDie(12),
d20: makeDie(20)
};
which you can use like so:
dice.d6(); // => random d6 roll
dice.d8.times(3) // => array of 3 random d8 rolls
You can make any die you want, e.g makeDie(3)
or makeDie(17)
if necessary.
As for parsing a string expression, I'd use String.replace
with a callback to "map" the parts of the string to individual rolls, and then use reduce
as rolfl explained well:
function roll(expression) {
var rolls = [];
expression.toLowerCase().replace(/(\d+)(d\d+)?/g, function (_, count, die) {
if(die) {
rolls = rolls.concat(dice[die].times(+count));
} else {
rolls.push(+count);
}
});
return rolls.reduce(function (sum, roll) {
return sum + roll;
});
}
I'm using the +variable
trick in lieu of parseInt
, but parseInt
might be a good idea. The regular expression will match a number, and an optional following by a "d" and another number. E.g. it'll match "4"
and "2d6"
.
There's a gotcha, though, since the above doesn't check the input too heavily. You could pass a string like "2d6 foobar! 3"
, and it'd roll 2 d6 and add 3, even though the input isn't really correct. However, it'll throw an exception if you try to roll a die that doesn't exist.
There's also the question of negative values. As far as I know, the modifier can be negative? Not sure. But point is that that's not handled by the above. It kinda can be, though, by rewriting the regular expression to /(-?\d+)(d\d+)?/g
which allow you to write "2d6 -5"
, but it's very whitespace sensitive. Still, in terms of the overall approach, I think it's a good track.
Continuing the DSL style, you can add the roll
function to the dice
object:
var dice = {
d4: makeDie(4),
d6: makeDie(6),
d8: makeDie(8),
d10: makeDie(10),
d12: makeDie(12),
d20: makeDie(20),
roll: function (expression) {
var self = this, rolls = [];
expression.toLowerCase().replace(/(\d+)(d\d+)?/g, function (_, count, die) {
if(die) {
rolls = rolls.concat(self[die].times(+count));
} else {
rolls.push(+count);
}
});
return rolls.reduce(function (sum, roll) {
return sum + roll;
});
}
};
And you've got a dice bag, you can use like so:
dice.d6(); // => random d6 roll
dice.d8.times(3) // => array of 3 random d8 rolls
dice.roll("2d6 + 2d10 + 2"); // => sum of die rolls plus modifier