dice-roller / rpg-dice-roller

An advanced JS based dice roller that can roll various types of dice and modifiers, along with mathematical equations.
https://dice-roller.github.io/documentation
MIT License
236 stars 57 forks source link

Inline rolls #206

Open manlok876 opened 3 years ago

manlok876 commented 3 years ago

Feature request Provide the ability to treat roll results as dice count or number of dice sides. A similar feature is implemented in Roll20: https://wiki.roll20.net/Dice_Reference#Inline_Dice_Rolls

Current behaviour Input 4d(1d6) produces Invalid notation; Expected "%", "*", "**", "+", "-", "/", "^", [.], or [0-9] but "d" found. error. Input (1d6)d6 produces Invalid notation; Expected "%", "*", "**", "+", "-", "/", "^", or end of input but "d" found. error. etc. Inputs such as 1d4*((3d10)*(4d6/2)) produce desired results.

Desired behaviour I would like to treat dice roll results in a way similar to equations, so they would be computed and used in notation as literal numbers. Examples:

Benefits I play Warhammer 40,000. A common case of dice roll for Warhammer players (and probably in other wargames too) is to roll attacks in several steps ("To Hit" -> "To Wound" -> "Attempt saves against attacks" -> "Calculate damage" -> "Atttempt to ignore damage", etc.), using number of successes sum of rolled dice as dice pool for new rolls. A description of the rules can be found here. Obviously, just saving simple dice rolls such as 24d6 does not help much, as number of dice rolled on each step is randomized. Saving the whole chain, like (((24d6>=4)d6>=3)d6<3, or at least typing it in a single roll instead of rolling each step separately, is something I have been looking for in a dice rolling app for a long time.

Concerns

GreenImp commented 3 years ago

This is a brilliant idea, and your examples are really clear, thank you. This is something that I did try to implement a while ago, when I rewrote the whole parsing system. Unfortunately, I hit some issues that just made it very difficult to do, and so I parked the idea.

It wasn't parsing the notation that was difficult, that bit is actually quite simple, but the parsing them into die objects. Die objects need to be provided basic numbers for the quantity and sides. Die objects can't parse equations and dice rolls, and it proved incredibly difficult to make them able to do so.

You can do things like this:

(3*4)d6
4d(2+6)
(sqrt(4*2))d10

And the parser runs calculates the result to a raw number, before creating the Die object. I would like to see if I can get it working, as a built in function, but it will be a long way down the line.

There are a few ways around this though;

Storing the variables

You could roll each part individually:

// (1d6)d6
const qty = DiceRoll.roll('1d6');
const roll = DiceRoll.roll(`${qty.total}d6`);

Do it inline

You could modify the above example, to do it all inline:

// (1d6)d6
const roll = DiceRoll.roll(`${DiceRoll.roll('1d6').total}d6`);

I know neither of these are a perfect solution to what you're trying to achieve, but perhaps they're good enough for the time being?

For reference, I've just checked how Roll20 outputs a roll like this: Results:

Screenshot 2021-04-16 at 10 59 50

Hovering over calculated quantity:

Screenshot 2021-04-16 at 10 55 07
maliut commented 1 year ago

I think you could achieve it by RegExp string replacement:

const regex = /\[([^[\]]+)\]/
function parseExpression(expression, roller) {
  if (regex.test(expression)) {
    expression = expression.replace(regex, (_, notation) => {
      const singleRoll = roller.roll(notation.trim())
      return String(singleRoll.total)
    })
    return parseExpression(expression, roller)
  } else {
    return expression
  }
}

const diceRoller = new DiceRoller()
const expression = /* your expression */
const finalExpression = parseExpression(expression, diceRoller)
console.log('firstly:', diceRoller.output)
console.log('finally:', new DiceRoll(finalExpression).output)

examples:

expression: [1d6]d6
firstly: 1d6: [4] = 4
finally: 4d6: [4, 5, 1, 1] = 11
expression: d[d[d10]]
firstly: d10: [4] = 4; d4: [1] = 1
finally: d1: [1] = 1
expression: [10d6>3]d6
firstly: 10d6>3: [1, 6*, 5*, 6*, 1, 5*, 2, 6*, 1, 5*] = 6
finally: 6d6: [2, 4, 3, 1, 5, 2] = 17

You can also introduce variables to use the results of previous rolls, or any other datasource.

function parseExpression(expression, roller) {
  if (regex.test(expression)) {
    expression = expression.replace(regex, (_, notation) => {
      notation = notation.replace(/\$(\d+)/, (_, variable) => {
        return String(roller.log[Number(variable) - 1].total) // $1 will refer to roller.log[0]
      })
      const singleRoll = roller.roll(notation.trim())
      return String(singleRoll.total)
    })
    return parseExpression(expression, roller)
  } else {
    return expression
  }
}

and then:

expression: [d10]+[$1]
firstly: d10: [3] = 3; 3: 3 = 3
finally: 3+3: 3+3 = 6

A complex example: how to roll [10d6>3]d6 safely

run: sign([10d6>3])*[max(1,$1)]d6

if 10d6>3 result in 0, it becomes sign(0)*1d6 = 0 if 10d6>3 result in 5(for example), it becomes sign(5)*5d6 which is equivalent to 5d6