[RPG] Is it possible to model this die with anydice

anydice

In anydice, I'd like to create a die, so that a roll would be the sum of 4 random values from the following sequence:

{1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7}

So that each number in the sequence is used at maximum once. (That is 3+5+5+7 result is possible where as 5+5+7+7 is not because there are not enough 7s for that) Is this possible?

This is a cards mechanics, not dice mechanics. I'm trying to get a roll by getting a player to draw several card out of pool of cards that I put together. While Anydice is mostly for dice mechanics not for cards drawing mechanics Anydice works well for probability analysis in general, and for sharing the visual spread of probabilities with less technically apt people.

Best Answer

I came up with two solutions, which why inherently different, give the same result. This instills confidence in me that they are correct.

Solution 1

This solution is the one I started working on before I read Sandwich's answer. You can try it here. The idea is to use Buckles algorithm for mapping an lexicographical index (from a die roll) to a selection from my sequence. Let's look at the code:

set "maximum function depth" to 100
function: factorial A:n {
  RESULT: 1
  loop N over {1..A} { RESULT: RESULT*N }
  result: RESULT
}

function: N:n choose K:n {
  result: [factorial N]/[factorial K]/[factorial N-K]
}

function: buckles inner CURRENT:n N:n K:n LIMIT:n INDEX:n {
  NEWLIMIT: [N-CURRENT choose K] + LIMIT
  if NEWLIMIT >= INDEX { result: {CURRENT,LIMIT}}
  else { result: [buckles inner CURRENT+1 N K NEWLIMIT INDEX]}
}

function: N:n buckles K:n index INDEX:n {
  RESULT: {}
  CURRENT: 0
  LIMIT: 0
  loop I over {1..K-1}{
    TUPLE: [buckles inner CURRENT+1 N K-I LIMIT INDEX]
    CURRENT: 1@TUPLE
    LIMIT: 2@TUPLE
    RESULT:{RESULT,CURRENT}
  }
  result: {RESULT,CURRENT+INDEX-LIMIT} 
}

function: select SELECTOR:s from SEQUENCE:s {
  RESULT: {}
  loop I over SELECTOR { RESULT: {RESULT,I@SEQUENCE} }  
  result: RESULT  
}

function: draw K:n items from SEQUENCE:s at lexicographical index INDEX:n {
  result: [select [#SEQUENCE buckles K index INDEX] from SEQUENCE]
}

function: draw from SEQUENCE:s K:n times {
  INDEX: d[#SEQUENCE choose K]
  result: [draw K items from SEQUENCE at lexicographical index INDEX]
}

SEQUENCE: {1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7}
output [draw from SEQUENCE 4 times] named "mydie"

The factorial and choose functions are self explanatory. The first calculates 1*2*3....*n, and the second one gives the total number of different ways to get k item out of n, in my case 4 out of 11. buckles and buckles inner implement the Buckles algorithm itself. If we pass N=11, P=4 and X=1 to the buckles function we will receive {1,2,3,4}. If we pass N=11, P=4 and X=2, we will receive {1,2,3,5} and so on an so forth until we go all the way to N=11, P=4 and X=330 to get {8,9,10,11}. Note that 330 here is [11 choose 4].

select is a helper function, that takes a selector, a sequence and returns the resulting sequence. For example for selector {1,6,7,11} and sequence {1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7} the result will be {1,3,3,7}.

draw items function selects K items from the given sequence at a given lexicographical index. For example with K = 4 at index 1 and sequence {1, 3, 5 , 9, 11} the result will be {1, 3 , 5 , 9}.

The second draw function allow you specifying a sequence and the number of items to draw from it. That will be the die.

Solution 2

This one is completely inspired by sandwich's answer. You try it here. And here is the code:

function: remove N:n from SEQUENCE:s {
  RESULT:{}
  REMOVED: 0
  loop C over SEQUENCE {
    if (REMOVED = 0) & (C=N) { REMOVED: 1 } else { RESULT: {RESULT,C} }
  }
  result: RESULT
}

function: draw from SEQUENCE:s with X:n and RESULT:s C:n more times{
  if C = 0 {
   result: RESULT
  } else {
    SEQUENCE: [remove X from SEQUENCE]
    result: [draw from SEQUENCE with dSEQUENCE and {RESULT,X} C-1 more times]
  }
}

function: draw from SEQUENCE:s N:n times {
  result: [draw from SEQUENCE with dSEQUENCE and {} N more times]
}

SEQUENCE:{1, 1, 1, 1, 1, 3, 3, 3, 5, 5, 7}
output [draw from SEQUENCE 4 times] named "mydie"

I was not sure how to remove a value from an existing sequence, so I had to write my own function for that. It's remove. The two draw functions implement sandwich's idea of rolling a die and then removing the result from the die sequence. There are two of them, because one of them is recursive and the other bootstraps the first one.

I can tell you that the first solution took me about 3 hours, and the second one only a few minutes. Well done sandwich!