Skip to content

Conversation

stevesea
Copy link
Collaborator

previously, a RollResult held a simple list of Integers are the result of a roll. There were various issues with this. Among them was inability to handle explosion after combining results of different nsides. e.g. for savage worlds roll of ((1d6+1d8)-L)!

now, each die roll outcome is represented by a RolledDie object -- this carries with it the number of sides of the die, as well as metadata/scoring.

fixes #5

stevesea added 4 commits June 26, 2025 00:04
introduce new object to hold roll results.
among the fixes this will enable is exploding after a sum of heterogenous die `((1d6 + 1d8)-L)!`.

it'll also enable roll rendering in client applications
@stevesea
Copy link
Collaborator Author

example output:

❯ dart run example/main.dart -r 1234 '((1d6 + 1d8)-L)!' -o pretty -vvv
[ FINEST]   DiceRoller: roll 1d6 => [6] 
[  FINER]      StdDice: (1d6) =rollDice=> RollResult(total: 6, results: [6(d6)])
[ FINEST]   DiceRoller: roll 1d8 => [4] 
[  FINER]      StdDice: (1d8) =rollDice=> RollResult(total: 4, results: [4(d8)])
[  FINER]        AddOp: ((1d6) + (1d8)) =add=> RollResult(total: 10, results: [6(d6), 4(d8)])
[  FINER] DropHighLowOp: (((1d6) + (1d8)) -l ) =drop=> RollResult(total: 6, results: [6(d6), 4(d8⛔︎)])
[ FINEST]   DiceRoller: roll 1d6 => [1] (explode #1)
[  FINER] ExplodingDice: ((((1d6) + (1d8)) -l ) ! ) =explode=> RollResult(total: 7, results: [6(d6💣), 1(d6🔥), 4(d8⛔︎)])
[   FINE] DiceExpression: (1d6) =rollDice=> RollResult(total: 6, results: [6(d6)])
[   FINE] DiceExpression: (1d8) =rollDice=> RollResult(total: 4, results: [4(d8)])
[   FINE] DiceExpression: ((1d6) + (1d8)) =add=> RollResult(total: 10, results: [6(d6), 4(d8)])
[   FINE] DiceExpression: (((1d6) + (1d8)) -l ) =drop=> RollResult(total: 6, results: [6(d6), 4(d8⛔︎)])
[   FINE] DiceExpression: ((((1d6) + (1d8)) -l ) ! ) =explode=> RollResult(total: 7, results: [6(d6💣), 1(d6🔥), 4(d8⛔︎)])
((((1d6) + (1d8)) -l ) ! ) ===> RollSummary(total: 7, results: [6(d6💣), 1(d6🔥)], discarded: [4(d8⛔︎)])
  ((((1d6) + (1d8)) -l ) ! ) =explode=> RollResult(total: 7, results: [6(d6💣), 1(d6🔥), 4(d8⛔︎)])
      (((1d6) + (1d8)) -l ) =drop=> RollResult(total: 6, results: [6(d6), 4(d8⛔︎)])
          ((1d6) + (1d8)) =add=> RollResult(total: 10, results: [6(d6), 4(d8)])
              (1d6) =rollDice=> RollResult(total: 6, results: [6(d6)])
              (1d8) =rollDice=> RollResult(total: 4, results: [4(d8)])

note that it correctly explodes the 6, adding a 1 to the results.

@saif-ellafi
Copy link

Can't wait to test this! Thank you

stevesea added 3 commits July 2, 2025 23:43
feels like these are distinct things, so they should be put into distinct bins.
it also felt awkward to always remember when performing operations on 'results' that some of those need to be skipped due to being discarded.
Copy link

codecov bot commented Jul 3, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@stevesea stevesea force-pushed the roll-results-obj branch from 2fa23f3 to 954ad7c Compare July 5, 2025 09:15
@saif-ellafi
Copy link

Lots of activity :) So, what will be the change or perceived change after this PR?

@stevesea
Copy link
Collaborator Author

stevesea commented Jul 5, 2025

there's some new syntax:

  • sorting results -- 4d6 s (ascending) or 4d6 sd (descending).
  • comma-separated subexpressions -- (1d6!,1d8!)kh
  • support for penetrating dice

but the biggest change to the API is how the results are modeled. instead of a collection of ints, it's a collection of RolledDie objects. The RolledDie objects also changed how scoring/metadata are captured into the rolls.

an example roll:

❯ dart run example/main.dart -r1234 -o pretty '(4d6)! #cs #cf'
((((4d6) ! ) #cs ) #cf ) ===> RollSummary(total: 17, results: [1(d6❌), 6(d6💣✅), 3(d6🔥), 2(d6), 5(d6)], critSuccessCount: 1, critFailureCount: 1)
  ((((4d6) ! ) #cs ) #cf ) =count=> RollResult(total: 17, results: [1(d6❌), 6(d6💣✅), 3(d6🔥), 2(d6), 5(d6)])
      (((4d6) ! ) #cs ) =count=> RollResult(total: 17, results: [6(d6💣✅), 3(d6🔥), 2(d6), 1(d6), 5(d6)])
          ((4d6) ! ) =explode=> RollResult(total: 17, results: [6(d6💣), 3(d6🔥), 2(d6), 1(d6), 5(d6)])
              (4d6) =rollDice=> RollResult(total: 14, results: [6(d6), 2(d6), 1(d6), 5(d6)])

or, the same result as JSON:

{
  "expression": "((((4d6) ! ) #cs ) #cf )",
  "total": 17,
  "critSuccessCount": 1,
  "critFailureCount": 1,
  "results": [
    {
      "result": 1,
      "nsides": 6,
      "dieType": "polyhedral",
      "critFailure": true
    },
    {
      "result": 6,
      "nsides": 6,
      "dieType": "polyhedral",
      "critSuccess": true,
      "exploded": true
    },
    {
      "result": 3,
      "nsides": 6,
      "dieType": "polyhedral",
      "explosion": true
    },
    {
      "result": 2,
      "nsides": 6,
      "dieType": "polyhedral"
    },
    {
      "result": 5,
      "nsides": 6,
      "dieType": "polyhedral"
    }
  ],
  "detailedResults": {
    "expression": "((((4d6) ! ) #cs ) #cf )",
    "opType": "count",
    "results": [
      {
        "result": 1,
        "nsides": 6,
        "dieType": "polyhedral",
        "critFailure": true
      },
      {
        "result": 6,
        "nsides": 6,
        "dieType": "polyhedral",
        "critSuccess": true,
        "exploded": true
      },
      {
        "result": 3,
        "nsides": 6,
        "dieType": "polyhedral",
        "explosion": true
      },
      {
        "result": 2,
        "nsides": 6,
        "dieType": "polyhedral"
      },
      {
        "result": 5,
        "nsides": 6,
        "dieType": "polyhedral"
      }
    ],
    "left": {
      "expression": "(((4d6) ! ) #cs )",
      "opType": "count",
      "results": [
        {
          "result": 6,
          "nsides": 6,
          "dieType": "polyhedral",
          "critSuccess": true,
          "exploded": true
        },
        {
          "result": 3,
          "nsides": 6,
          "dieType": "polyhedral",
          "explosion": true
        },
        {
          "result": 2,
          "nsides": 6,
          "dieType": "polyhedral"
        },
        {
          "result": 1,
          "nsides": 6,
          "dieType": "polyhedral"
        },
        {
          "result": 5,
          "nsides": 6,
          "dieType": "polyhedral"
        }
      ],
      "left": {
        "expression": "((4d6) ! )",
        "opType": "explode",
        "results": [
          {
            "result": 6,
            "nsides": 6,
            "dieType": "polyhedral",
            "exploded": true
          },
          {
            "result": 3,
            "nsides": 6,
            "dieType": "polyhedral",
            "explosion": true
          },
          {
            "result": 2,
            "nsides": 6,
            "dieType": "polyhedral"
          },
          {
            "result": 1,
            "nsides": 6,
            "dieType": "polyhedral"
          },
          {
            "result": 5,
            "nsides": 6,
            "dieType": "polyhedral"
          }
        ],
        "left": {
          "expression": "(4d6)",
          "opType": "rollDice",
          "results": [
            {
              "result": 6,
              "nsides": 6,
              "dieType": "polyhedral"
            },
            {
              "result": 2,
              "nsides": 6,
              "dieType": "polyhedral"
            },
            {
              "result": 1,
              "nsides": 6,
              "dieType": "polyhedral"
            },
            {
              "result": 5,
              "nsides": 6,
              "dieType": "polyhedral"
            }
          ],
          "total": 14
        },
        "total": 17
      },
      "total": 17,
      "critSuccessCount": 1
    },
    "total": 17,
    "critSuccessCount": 1,
    "critFailureCount": 1
  }
}

Each RolledDie should have enough information that you can render the die and result and metadata nicely.

    {
      "result": 6,
      "nsides": 6,
      "dieType": "polyhedral",
      "critSuccess": true,
      "exploded": true
    },
    
      {
        "result": 3,
        "nsides": 6,
        "dieType": "polyhedral",
        "explosion": true
      },

from that you could draw two 6-sided die, one showing a 6 and another a 3.
the 6 is a critical success. The six exploded (exploded: true)
the 3 was rolled as a result of exploding die (explosion: true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Savage Worlds compatible expression
2 participants