Page 1 of 1

Some probablities for success with OVA dice pools

Posted: Mon Oct 15, 2007 4:14 pm
by Father of Dragons
I like the die rolling method for Ricochet/OVA, but I don't necessarily find it intuitive. So, to get a feel for some of the probabilities, I wrote a brute-force C program to determine them (the source code for which can be found at the bottom of this post). I had to limit the number of dice I ran it against as it was taking forever to finish (like not finishing after running all weekend on a 2.67 GHz machine with 2G of RAM). I also ran it some against the d10 version of the dice pool that had been discussed here.

The abbreviations used for the difficulties and the numbers in the form [d6]/[d10] are as follows:

Code: Select all

Easy                      EZ  [ 2]/[ 4]
Moderate                  MO  [ 4]/[ 6]
Challenging               CG  [ 6]/[10]
Difficult                 DF  [ 8]/[14]
Very Difficult            VD  [10]/[16]
Extremely Difficult       ED  [12]/[20]
Nigh Impossible           NI  [15]/[25]
The results (in terms of chance of making a test of that difficulty) for the d6 dice pool are:

Code: Select all

#D    EZ ( 2)   MO ( 4)   CG ( 6)   DF ( 8)   VD (10)   ED (12)   NI (15)
=========================================================================
-6    23.257%    0.391%    0.000%    0.000%    0.000%    0.000%    0.000%
-5    27.908%    0.781%    0.000%    0.000%    0.000%    0.000%    0.000%
-4    33.490%    1.563%    0.002%    0.000%    0.000%    0.000%    0.000%
-3    40.188%    3.125%    0.013%    0.000%    0.000%    0.000%    0.000%
-2    48.225%    6.250%    0.077%    0.000%    0.000%    0.000%    0.000%
-1    57.870%   12.500%    0.463%    0.000%    0.000%    0.000%    0.000%
 0    69.444%   25.000%    2.778%    0.000%    0.000%    0.000%    0.000%
 1    83.333%   50.000%   16.667%    0.000%    0.000%    0.000%    0.000%
 2    97.222%   80.556%   38.889%    8.333%    5.556%    2.778%    0.000%
 3    99.537%   93.981%   60.648%   22.685%   15.278%    8.333%    0.926%
 4    99.923%   98.380%   77.855%   39.892%   27.623%   16.512%    3.318%
 5    99.987%   99.601%   89.185%   56.970%   41.088%   26.800%    7.446%
 6    99.998%   99.908%   95.413%   71.727%   54.345%   38.398%   13.351%
 7   100.000%   99.980%   98.279%   83.031%   66.382%   50.366%   20.869%
 8   100.000%   99.996%   99.416%   90.750%   76.556%   61.795%   29.663%
 9   100.000%   99.999%   99.817%   95.437%   84.581%   71.959%   39.271%
10   100.000%  100.000%   99.947%   97.957%   90.480%   80.410%   49.161%
11   100.000%  100.000%   99.985%   99.162%   94.512%   86.998%   58.801%
12   100.000%  100.000%   99.996%   99.683%   97.058%   91.825%   67.726%
13   100.000%  100.000%   99.999%   99.888%   98.536%   95.148%   75.588%
14   100.000%  100.000%  100.000%   99.963%   99.323%   97.292%   82.185%
Note that values of a 100% are not, actually, 100% but just very close; similarly, some of the 0% values are just too small to register with the format selected (you can tell those with a moment's thought).

The results for the d10 dice pool are:

Code: Select all

#D    EZ ( 4)   MO ( 6)   CG (10)   DF (14)   VD (16)   ED (20)   NI (25)
=========================================================================
-6     5.765%    0.391%    0.000%    0.000%    0.000%    0.000%    0.000%
-5     8.235%    0.781%    0.000%    0.000%    0.000%    0.000%    0.000%
-4    11.765%    1.563%    0.000%    0.000%    0.000%    0.000%    0.000%
-3    16.807%    3.125%    0.001%    0.000%    0.000%    0.000%    0.000%
-2    24.010%    6.250%    0.010%    0.000%    0.000%    0.000%    0.000%
-1    34.300%   12.500%    0.100%    0.000%    0.000%    0.000%    0.000%
 0    49.000%   25.000%    1.000%    0.000%    0.000%    0.000%    0.000%
 1    70.000%   50.000%   10.000%    0.000%    0.000%    0.000%    0.000%
 2    93.000%   78.000%   24.000%    4.000%    3.000%    1.000%    0.000%
 3    98.700%   91.500%   39.700%   11.400%    8.600%    3.100%    0.200%
 4    99.790%   97.130%   54.980%   21.310%   16.270%    6.360%    0.760%
 5    99.969%   99.159%   68.329%   32.703%   25.402%   10.777%    1.806%
 6    99.996%   99.786%   78.986%   44.563%   35.362%   16.278%    3.433%
 7    99.999%   99.952%   86.844%   56.006%   45.540%   22.719%    5.706%
 8   100.000%   99.990%   92.230%   66.369%   55.401%   29.902%    8.659%
 9   100.000%   99.998%   95.675%   75.239%   64.516%   37.580%   12.297%
10   100.000%  100.000%   97.736%   82.449%   72.588%   45.483%   16.593%
11   100.000%  100.000%   98.888%   88.028%   79.449%   53.341%   21.490%
As can be seen, the proposed difficulty numbers give probabilities fairly different than the d6 dice pool.

The program (for anyone interested):

Code: Select all

/***********************************************************************
*
*N  {ricochet.c} -- Calculate probabilities for Rocochet dice pools.
*
***********************************************************************/
#include <stdio.h>
#include <stdlib.h>

/* Types. */

typedef unsigned char RicoDie;
typedef int Bool;

typedef struct {
  RicoDie         *dice;
  RicoDie         dieSize;
  int             diceCount;
  int             actualDiceCount;
} RicoDicePool;

/* The following define is platform dependent.  For instance on Windows, it
   is __int64, and on some Unix platforms long int. */
#define Int64 long

/* Constants. */

#define TRUE             (-1)
#define FALSE             (0)
#define MAX_DIE_SIZE      256
#define DIFFICULTY_COUNT    7
#define MIN_DICE_POOL      -6
#define MAX_DICE_POOL      14

/* Utility macros. */

#define ACTUAL_COUNT(count) ((count > 0) ? count : (-count + 2))

/* Fixed data. */

static const char  *DifficultyLabels[] = {"Easy",
                                          "Moderate",
                                          "Challenging",
                                          "Difficult",
                                          "Very Difficult",
                                          "Extremely Difficult",
                                          "Nigh Impossible"};
static const char  *DifficultyTags[] = {"EZ","MO","CG","DF","VD","ED","NI"};
static const int   DifficultyLevels6[] = {2,4,6,8,10,12,15};
static const int   DifficultyLevels10[] = {4,6,10,14,16,20,25};

/***********************************************************************
*
*N  {CreatePool} -- Create a dice pool, all initialized to 1's
*
***********************************************************************/
RicoDicePool * CreatePool (RicoDie  size,
                           int      count)
{
  int           actualCount,d;
  RicoDicePool  *pool;

  /* Deal with dice counts < 1. */

  actualCount = ACTUAL_COUNT (count);
  
  /* Allocate structures. */

  pool = (RicoDicePool *)malloc (sizeof(RicoDicePool));
  if (!pool) return NULL;

  pool->dice = malloc (sizeof(RicoDie) * actualCount);
  if (!pool->dice) return NULL;

  /* Initialize values. */

  for (d = 0;d < actualCount;d++) pool->dice[d] = 1;
  pool->dieSize = size;
  pool->diceCount = count;
  pool->actualDiceCount = actualCount;
  
  /* Done. */

  return pool;
}

/***********************************************************************
*
*N  {FreePool} -- Free a dice pool.
*
***********************************************************************/
void FreePool (RicoDicePool  *pool)
{
  if (!pool) return;
  if (pool->dice) free (pool->dice);
  free (pool);

  return;
}

/***********************************************************************
*
*N  {IncrementPool} -- Increment a dice pool to the next value.
*
***********************************************************************/
Bool IncrementPool (RicoDicePool  *pool)
{
  int  d;

  for (d = 0;d < pool->actualDiceCount;d++) {
    if (pool->dice[d] == pool->dieSize) {
      if (d == (pool->actualDiceCount - 1)) return FALSE; /* Overflow! */
      pool->dice[d] = 1;
    } else {
      pool->dice[d]++;
      break;
    }
  }

  return TRUE;
}

/***********************************************************************
*
*N  {EvaluatePool} -- Determine the value of the supplied dice pool.
*
***********************************************************************/
int EvaluatePool (const RicoDicePool  *pool)
{
  int      d,value,rollValue;
  RicoDie  dieCount[MAX_DIE_SIZE];

  /* If there are zero or negative dice in the pool, just look for the lowest
     value. */

  if (pool->diceCount < 1) {
    value = pool->dieSize;
    for (d = 0;d < pool->actualDiceCount;d++)
      if (pool->dice[d] < value) value = pool->dice[d];
  }

  /* Positive dice a bit trickier, since we need to detect multiple instances
     of the same roll, so we'll count those first. (Multiple 1's don't count
     so we don't add them up at the end.)  */

  else {
    for (d = 1;d <= pool->dieSize;d++) dieCount[d] = 0;
    for (d = 0;d < pool->diceCount;d++) dieCount[pool->dice[d]]++;
    value = 1;
    for (d = 2;d <= pool->dieSize;d++) {
      rollValue = dieCount[d] * d;
      if (rollValue > value) value = rollValue;
    }
  }

  return value;
}

/***********************************************************************
*
*N  {PoolSuccessStats} -- Determine success percentages for a given pool size.
*
***********************************************************************/
Bool PoolSuccessStats (RicoDie    size,
                       int        count,
                       const int  *difficulties,
                       double     *successPercentages,
                       int        difficultyCount)
{
  RicoDicePool  *pool;
  int           value,d;
  Int64         *successes,totalTests;

  /* Create the pool to test. */

  pool = (RicoDicePool *) CreatePool (size,count);
  if (!pool) return FALSE;

  /* Initialize test values. */

  successes = (Int64 *)calloc (difficultyCount,sizeof(Int64));
  if (!successes) return FALSE;
  totalTests = 0;

  /* Loop through all possible values of the dice pool, and record which ones
     succeeded. */

  do {
    value = EvaluatePool (pool);
    for (d = 0;d < difficultyCount;d++)
      if (value >= difficulties[d]) successes[d]++;
    totalTests++;
  } while (IncrementPool (pool));

  FreePool (pool);

  /* Calculate percentages. */

  for (d = 0;d < difficultyCount;d++)
    successPercentages[d] = (double)successes[d] / (double)totalTests * 100.0;
  free (successes);

  return TRUE;
}

/***********************************************************************
*
*N  {Main}  --   Main function
*
***********************************************************************/
int main (int argc, char *argv[])
{
  int     poolSize,d;
  double  successPercentages[DIFFICULTY_COUNT];

  /* Print Header and terms. */

  printf ("Calculate success chances for Ricochet Dice Pools\n");
  printf ("=================================================\n");

  for (d = 0;d < DIFFICULTY_COUNT;d++)
    printf ("%-24s  %2s  [%2d]/[%2d]\n",DifficultyLabels[d],DifficultyTags[d],
            DifficultyLevels6[d],DifficultyLevels10[d]);

  printf ("\nCalculate D6's\n");
  printf ("\n#D    %2s (%2d)   %2s (%2d)   %2s (%2d)   %2s (%2d)   %2s (%2d)"
          "   %2s (%2d)   %2s (%2d)\n",
          DifficultyTags[0],DifficultyLevels6[0],
          DifficultyTags[1],DifficultyLevels6[1],
          DifficultyTags[2],DifficultyLevels6[2],
          DifficultyTags[3],DifficultyLevels6[3],
          DifficultyTags[4],DifficultyLevels6[4],
          DifficultyTags[5],DifficultyLevels6[5],
          DifficultyTags[6],DifficultyLevels6[6]);
  printf ("=================================================================="
          "=======\n");

  /* Print Calculations. */

  for (poolSize = MIN_DICE_POOL;poolSize <= MAX_DICE_POOL;poolSize++) {
    if (PoolSuccessStats (6,poolSize,DifficultyLevels6,successPercentages,
                          DIFFICULTY_COUNT)) {
      printf ("%2d   %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  "
              "%7.3f%%\n",
              poolSize,
              successPercentages[0],
              successPercentages[1],
              successPercentages[2],
              successPercentages[3],
              successPercentages[4],
              successPercentages[5],
              successPercentages[6]);
      fflush (stdout);
    }
  }

  printf ("\n\nCalculate D10's\n");
  printf ("\n#D    %2s (%2d)   %2s (%2d)   %2s (%2d)   %2s (%2d)   %2s (%2d)"
          "   %2s (%2d)   %2s (%2d)\n",
          DifficultyTags[0],DifficultyLevels10[0],
          DifficultyTags[1],DifficultyLevels10[1],
          DifficultyTags[2],DifficultyLevels10[2],
          DifficultyTags[3],DifficultyLevels10[3],
          DifficultyTags[4],DifficultyLevels10[4],
          DifficultyTags[5],DifficultyLevels10[5],
          DifficultyTags[6],DifficultyLevels10[6]);
  printf ("=================================================================="
          "=======\n");

  /* Print Calculations. */

  for (poolSize = MIN_DICE_POOL;poolSize <= MAX_DICE_POOL;poolSize++) {
    if (PoolSuccessStats (10,poolSize,DifficultyLevels10,successPercentages,
                          DIFFICULTY_COUNT)) {
      printf ("%2d   %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  %7.3f%%  "
              "%7.3f%%\n",
              poolSize,
              successPercentages[0],
              successPercentages[1],
              successPercentages[2],
              successPercentages[3],
              successPercentages[4],
              successPercentages[5],
              successPercentages[6]);
      fflush (stdout);
    }
  }
  return EXIT_SUCCESS;
}

Re: Some probablities for success with OVA dice pools

Posted: Tue Oct 16, 2007 10:55 am
by JuddG
Father of Dragons wrote:As can be seen, the proposed difficulty numbers give probabilities fairly different than the d6 dice pool.
I think I find the d6 chart a bit more to my tastes. It seems to have a far better cascade than the d10 one. Of course, I am approaching this in the aesthetic manner, not in the purely mathematical.

Plus, a d6 has always just felt better in the hand than a bunch of d10s. They build better towers, too. :lol:

On the statistical/mechanical side, you have more chances of making sets with d6 than with a d10, and that is a huge part of why the charts are so varied between them.

In the end, I see no greatly compelling objective reason to change the dice, while there are astounding subjective reasons not to. As a pedantic, I choose the path of the Sacred Cube(TM). :wink:

Posted: Wed Oct 17, 2007 10:05 am
by Clay
Thanks, FoD! Due the complicated math involved – enough to strain a modern-day PC, apparently! – I was only able to calculate a limited number of dice. I really appreciate having a better look at it.

It's actually fairly smooth. Much more so than I anticipated. I'm sure variations exist throughout, but these are mathematical nuances that would go unnoticed in actual play. I didn't pick the current mechanic for its mathematical sensibilities. :)

The sharp drops between Challenging and Difficult, and for Nigh Impossible, in general, are also appropriate. It makes a clean division between "chunks" of difficulties that make logical sense. EZ - CG is stuff you can do every day, DF - ED are stuff you'll have a hard time doing, and NI is, after all, Nigh impossible.

D10 does offer a somewhat smoother ride, but that's only because, as Judd already mentioned, there's less matching altogether. In the end OVA's mechanic is an aesthetic one. It's supposed to be fun, dynamic, and easy. I didn't pick the doubles method because I thought it'd result in perfect math. I also picked d6s because they're easy to come by and familiar. Everyone's seen a six-sided die. Naruto kiddos might be totally perplexed by something called a "d10."

Also, the d10 chart seems a bit random between 14 and 16. Why only skip two values here? It may provide a better table, but it's rather unintuitive for remembering. The current chart is just +2, +2. +2 until NI, which is +3 of the previous difficulty for a nice (and easy to remember) 15.

Anyway, as you mentioned, people have converted OVA to d10s before. (I believe the intent was an Exalted conversion). I'm sure several OVA players will really love your work here. Thanks for all the time an energy you put into it!

Random Trivia: Early, early in Ricochet's life, when it was briefly devised for a (of all things) POKEMON RPG, it was a d10 system. :)

Posted: Wed Oct 17, 2007 10:54 am
by Father of Dragons
Clay wrote:Thanks, FoD! Due the complicated math involved – enough to strain a modern-day PC, apparently! – I was only able to calculate a limited number of dice. I really appreciate having a better look at it.
I could have, in fact, almost certainly come up with a faster, more efficient way to calculate these values, but had more time available to let the program run in the background than I did to improve the program.
Clay wrote:Anyway, as you mentioned, people have converted OVA to d10s before. (I believe the intent was an Exalted conversion). I'm sure several OVA players will really love your work here. Thanks for all the time an energy you put into it!
The d10 chart is from that Exalted conversion. Personally, I'm fine with the d6's, but it didn't take a significant amount of additional effort to calculate for the d10's at the same time, and I figured if any of the people who did it were still around, they'd find that interesting.

Posted: Mon Nov 12, 2007 7:16 pm
by Jandar
One of my pals in my regular RPG group is quite a mathematician. He's an electronics engineer by training, very pedantic (kind of what I am like when it comes to spelling etc.) and very interested in tables, probabilities, formulae and so on, so I think he'll appreciate the charts by FoD as well.

I still have to persuade that guy to play OVA with me, though.
:wink:

Posted: Mon Mar 23, 2009 7:57 pm
by Tubercular Ox
Probabilities is not a substitute for probability, because some of us are impatient and forget to search on plurals. Hence I'm saying probability in this thread to help future searchers.

Probability.