14
\$\begingroup\$

I'd like to roll up the ability scores for my Dungeons and Dragons character. But I'd also like them to be balanced (and slightly better than average).

Typically, a character's stats are created by rolling four 6-sided dice, adding together the three highest results, and doing this 6 times. A 6 sided die has the numbers 1 to 6, with equal probability of landing on each. The 6 scores can be in any order, and are typically displayed out in an array. Unfortunately, this has a slight pitfall, namely that some players' stats can be much higher than other players' stats.

So for this generator, the stats should be a little more balanced, namely that at least one stat should be less than or equal to a certain threshold, at least one stat should be greater than or equal to a certain threshold, and the total should be greater than or equal to a certain threshold. However, the randomness should still be preserved. If a generated stat array does not qualify, the entire array should be rerolled.

Given 3 inputs, l, h, and t, respectively corresponding to the low threshold, the high threshold, and the total threshold, output 6 stats (in any order) that meet the criteria. All 6 stats must be included. The stats should be randomly generated, using the stat generation described above.

For example:

l   h   t   | low example       | high example
10  15  75  | 6  14 18 12 10 15 | 10 18 18 18 18 18
8   18  80  | 10 18 16 13 8  15 | 18 18 18 8  18 18
18  18  108 | 18 18 18 18 18 18 | 18 18 18 18 18 18

The code doesn't need to be fast, it just needs to get there eventually. Note that formatting doesn't much matter. Assume valid inputs.

\$\endgroup\$
8
  • 3
    \$\begingroup\$ Welcome to Code Golf! This looks like a reasonably well-specified challenge, but for future reference, we strongly recommend using the Sandbox to get feedback on challenge ideas before posting them to the main site. For example, you should specify what counts as random to make the challenge clearer \$\endgroup\$
    – pxeger
    Jul 6 at 14:44
  • \$\begingroup\$ Can any of the stats be 0? \$\endgroup\$
    – Shaggy
    Jul 6 at 14:52
  • 5
    \$\begingroup\$ @Shaggy You can't roll a 0 with dice. \$\endgroup\$
    – Adám
    Jul 6 at 14:52
  • 3
    \$\begingroup\$ All stats must be assigned. The minimum possible is 3. \$\endgroup\$
    – darthbeep
    Jul 6 at 17:25
  • 2
    \$\begingroup\$ @darthbeep That is important information which should be included in the main post. \$\endgroup\$ Jul 7 at 12:01

15 Answers 15

10
\$\begingroup\$

Japt, 28 bytes

Takes input as t, l & h.

@§Xx*Xd§V*Xd¨W}a@6Æ4ÆÒ6öÃÍÅx

Try it

@§Xx*Xd§V*Xd¨W}a@6Æ4ÆÒ6öÃÍÅx     :Implicit input of integers U=t, V=l & W=h
@                                :Left function, taking an array X as input
 §                               :  Is U less than or equal to ...
  Xx                             :  X reduced by addition after
    *                            :    Multiplying each element by ...
     Xd                          :    Any in X
       §                         :      Less than or equal to ...
        V*                       :      V multiplied by ...
          Xd                     :      Any in X
            ¨W                   :        Greater than or equal to W
              }                  :End left function
               a                 :Repeatedly run the right function and return the first result that returns true when passed through the left function
                @                :Right function
                 6Æ              :  Map the range [0,6)
                   4Æ            :    Map the range [0,4)
                     Ò           :      Negate the bitwise NOT of (i.e., increment)
                      6ö         :      Random integer in the range [0,6)
                        Ã        :    End map
                         Í       :    Sort
                          Å      :    Slice off the first element
                           x     :    Reduce by addition
\$\endgroup\$
10
  • 5
    \$\begingroup\$ @Downvoter, please leave a comment. \$\endgroup\$
    – Shaggy
    Jul 6 at 16:05
  • 1
    \$\begingroup\$ It's not really about your personal perception, the question was unclear and this answer was posted. The quality of the answer is not determined by what you thought. \$\endgroup\$
    – Grain Ghost
    Jul 6 at 17:33
  • 6
    \$\begingroup\$ @WheatWizard, clarity is a matter of perception. \$\endgroup\$
    – Shaggy
    Jul 6 at 17:43
  • 5
    \$\begingroup\$ @WheatWizard, to be honest, I find your last comment quite petty, especially coming from a mod who's been 'round this site long enough to know how it works. I also don't appreciate the unfounded accusations as to the timing of me posting it; you're making assumptions as to my intent based on your own perception. \$\endgroup\$
    – Shaggy
    Jul 6 at 17:48
  • 1
    \$\begingroup\$ I don't know what you find petty about this. I am just answering your request as to why I downvoted. I don't like answers to questions that are not clear. That is it. It's not about you. I'm not sure why I should be providing my reason for downvoting if you plan on attacking me for it. \$\endgroup\$
    – Grain Ghost
    Jul 6 at 17:56
5
\$\begingroup\$

APL (Dyalog Unicode), 40 bytes

Full program. Prompts for total, then high, then low.

{(+/-⌊/)?6 4⍴6}⍣((∨/⎕≥⊣)∧(∨/⎕≤⊣)∧⎕≤1⊥⊣)⍬

Try it online!

{} apply the following lambda to the empty list dummy argument:

6 4⍴6 cyclically reshape 6 into a 6-row, 4-column matrix

? roll those dice

 () apply the following tacit function to that:

  +/-⌊/ the sum minus the minimum (lit. plus-reduction minus smallest-reduction

⍣() keep applying that function to its results until the following holds true:

 the results

1⊥ evaluated as unary (i.e. summed) 

⎕≤ is the input (total) less than or equal to that?

()∧ and:

   the results

  ⎕≤ is the input (high) less than or equal to those?

  ∨/ are any true? (lit. OR-reduction)

()∧ and:

   the results

  ⎕≥ is the input (low) greater than or equal to those?

  ∨/ are any true? (lit. OR-reduction)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ ((∨/⎕>⊣)∧(∨/⎕<⊣)∧⎕≤1⊥⊣) has a pleasing almost-symmetry. I'd like to see an explanation in case there is more to borrow for my J answer. \$\endgroup\$
    – Jonah
    Jul 7 at 7:03
  • 2
    \$\begingroup\$ @Jonah Explanation added (and cutoffs fixed). \$\endgroup\$
    – Adám
    Jul 7 at 7:14
5
\$\begingroup\$

Troll, 135 bytes

Right tool for rolling dice, but not necessarily for golfing.

function s(x)=w:=x pick1;x:=x--w;'w<>x&call s(x)
function f(l,h,t)=call s(repeat x:=6#sum largest3 4d6until h<=max x&l>=min x&t<=sum x)

Try it online! (Click on Make random rolls. and the result will appear a bit further down.)

The first function generates a shuffled string from a collection of numbers. This is necessary as collections in Troll are generally unordered and printed in ascending order.

slightly ungolfed:

function shuffle(x) =
  w := x pick 1;
  x := x -- w;                  \ --  multiset difference
  'w <> (x & call shuffle(x))   \ <>  vertically concat strings

function f(l, h, t) =
  call s(
    repeat
      x := 6 # sum largest 3 4d6
    until
      h<=max x & l>=min x & t<=sum x
  )

call f(10, 15, 75)
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 138 bytes

(l,h,t)=>eval(F="while(F||S-3<t)[F=3,S=3,...A=[3,3,3,3]].map(n=>(A.map(m=_=>n+=m<(v=Math.random()*6|0)?v:m=v),S+=n-=m,F&=n>l|2*(n<h),n))")

Try it online!

As pointed out by @Shaggy, a recursive version is shorter (134 bytes). But it will fail when the odds are low ... whereas the above version will just take forever to find a solution. :-)

Commented

The main function initializes F to a truthy value and invokes an eval() ...

(l, h, t) => eval(F = "...")

... whose content is:

while(F || S - 3 < t)     // while F is truthy or S - 3 is less than t:
  [ F = 3,                //   start with F = 3 (boundary flags)
    S = 3,                //   start with S = 3 (sum of all die + 3)
    ... A = [3, 3, 3, 3]  //   append 4 more 3's
  ]                       //
  .map(n => (             //   for each entry, starting with n = 3:
    A.map(m =             //     repeat 4 times, starting with m non-numeric:
      _ =>                //
      n += m < (          //       update n:
        v = Math.random() //         pick a dice value v in [0 .. 5]
            * 6 | 0       //
      ) ? v               //         and add it to n
        : m = v           //         update m to min(m, v)
    ),                    //     end of inner map()
    S += n -= m,          //     subtract m from n and add the result to S
    F &= n > l |          //     clear the bit #0 of F if n <= l
         2 * (n < h),     //     clear the bit #1 of F if n >= h
    n                     //     yield n
  ))                      //   end of outer map()
                          // implicit end of while()
\$\endgroup\$
3
  • \$\begingroup\$ Looks like recursion works out a little shorter \$\endgroup\$
    – Shaggy
    Jul 7 at 11:32
  • \$\begingroup\$ You might also be able to replace F||S-3<t with F|S-3<t. \$\endgroup\$
    – Shaggy
    Jul 7 at 11:34
  • 2
    \$\begingroup\$ @Shaggy That was my initial approach, but it is doomed to stack overflow when the odds are low, like with the 3rd test case (which has basically zero chance to complete on TIO with the current version either ... but still ^^). \$\endgroup\$
    – Arnauld
    Jul 7 at 11:47
4
\$\begingroup\$

R 4.1, 112 bytes

Using the new shorthand notation of defining functions (\(x) expr):

r=\(l,h,t){x=l;while(sum(x)<t|min(x)>l|max(x)<h){x=matrix(sample(6,24,r=T),nr=6);x=rowSums(x)-apply(x,1,min)};x}

Explanation

Generate 6x4 matrix (nrow=6) with random number 1-6:

x=matrix(sample(6,24,r=T),nr=6)

Calculate the sum over the rows and subtract the row-minimum to get the sum of the largest three values:

x=rowSums(x)-apply(x,1,min)

And repeat as long criteria not met:

while(sum(x)<t|min(x)>l|max(x)<h)

Initialise x to a value that violates criteria (could also have chosen 0)

x = l

Edit: added explanation

\$\endgroup\$
3
\$\begingroup\$

Factor + dice math.unicode combinators.short-circuit.smart, 153 bytes

[ [ 6 [ 4 [ ROLL: 1d6 ] replicate natural-sort rest Σ ] replicate 3dup { [ Σ <= nip ] [ nip minmax '[ _ _ between? ] ∀ ] } && ] [ drop ] until 2nip ]

Try it online!

It's a quotation (anonymous function) that takes a low-high pair (like { 10 15 }) and a sum target from the data stack as input and leaves a sequence of 6 stat values on the data stack as output.

Explanation:

  • [ ... 3dup ... ] [ drop ] until 2nip Keep making stats (and discarding them) until one of them is valid.
  • 6 [ ... ] replicate Create a sequence with 6 values given by a quotation.
  • 4 [ ROLL: 1d6 ] replicate Roll 1d6 4 times and collect the results in a sequence.
  • natural-sort rest Σ Sort, remove the first (lowest) value, and sum.
  • { [ Σ <= nip ] [ nip minmax '[ _ _ between? ] ∀ ] } && Check if the sum is >= the target sum, and check whether both elements in the low-high pair are between the minimum and maximum value (inclusive) in the stats.
\$\endgroup\$
1
  • \$\begingroup\$ Oof! And I thought Japt was bad for white space! \$\endgroup\$
    – Shaggy
    Jul 6 at 23:03
3
\$\begingroup\$

Charcoal, 36 bytes

W∨›Iζ↨υ¹∨›Iη⌈υ‹Iθ⌊υ≔EE⁶E⁴⊕‽⁶⁻Σκ⌊κυIυ

Try it online! Link is to verbose version of code. Explanation:

W∨›Iζ↨υ¹∨›Iη⌈υ‹Iθ⌊υ

Repeat until the initially empty predefined list fails to satisfy any of the conditions that its total is less than the third input, its highest member is less than the second input or its lowest member is greater than the third input. The total is checked first because using base 1 instead of Sum allows the check to run on the empty list.

≔EE⁶E⁴⊕‽⁶⁻Σκ⌊κυ

Generate 6 sets of four dice, subtract the minimum of each set from their sums, and replace the predefined list with them.

Iυ

Output the valid list.

\$\endgroup\$
3
\$\begingroup\$

Python 3, 139 137 bytes

Recursive function which returns a tuple of integers.

from random import*
f=lambda l,h,t,*k:k*(t<=sum(k)>max(k)>=h>=l>=min(k))or f(l,h,t,*eval('sum(sorted(map(randint,[1]*4,[6]*4))[1:]),'*6))

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Ruby, 91 bytes

->l,h,t{1while(w=(k=*1..6).map{(k*4).sample(4).sort[1,3].sum};w.min>l||w.max<h||w.sum<t);w}

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ 89 bytes with redo instead of while. \$\endgroup\$
    – Dingus
    Jul 8 at 2:23
1
\$\begingroup\$

Python 3.8 (pre-release), 198 bytes

from random import*
def g(l,h,t,r=range):
 while sum(k:=list(sum(sorted(randint(1,6)for _ in r(4))[1:])for _ in r(6)))<t or(n:=list(map(sum,zip(*((i<=l,i>=h)for i in k)))))[0]*n[1]!=1:pass
 return k

Not fast but gets the job done. It is quite possible that the last test won't finish as it needs perfect rolls.

On TIO the additional output is a check that the sum is greater or equal than the t and the count of numbers less than or equal to l and greater or equal to h.

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Python 3, 138 bytes

from random import*
def f(l,h,t):
 while l<min(s:=[3+sum(d:=choices(range(6),k=4))-min(d)for _ in[0]*6])or h>max(s)or t>sum(s):0
 return s
\$\endgroup\$
1
\$\begingroup\$

Java (JDK), 175 bytes

(l,h,t)->{int r[]=new int[6],i,j,m,d,s,x=0,T=0;for(;x<3|T<t;)for(x=T=i=0;i<6;T+=r[i++]=s-=m,x|=s<h?s>l?0:1:2)for(m=7,s=j=0;j++<4;s+=d+=Math.random()*6,m=d<m?d:m)d=1;return r;}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Ly, 104 bytes

n`n,n,>1[p6[>&p<6[>16?<,]p>arppp&+s>l<p<,]p>>&s<&l<<&s>>>>&p&l<a0Isp>rlGfp<s>flLfp<&+sp>rlLfp**sp<<<l!]>

Try it online!

The specific steps in this code are pretty straightforward, the only trick is keeping the data organized on the stacks.

Here's how the stacks are used:

s0: low-high-total values (adjusted)
s1: loop control numbers (three nested loops share one stack)
s2: dice rolls and temp storage of stats
s3: stats accumulator and workspace
s4: low-high-test work area

Also, the input parameter are incremented/decremented to make the comparisons to the generated stats easier. The operators available in Ly are less than < and greater then > and the criteria allow the stat to equal the threshold. Adjusting the threshold was shorter than adding logic to check "greater than or equal", etc...

# Step 0: Read in low, high, sum thresholds, setup for comps
n`n,n,>
n`      # Read line one, low thresh, increment
  n,    # Read line two, high thresh, decrement
    n,  # Read line three, sum of stats, decrement

# Step 1: Loop until a set of stats meets all criteria
1[p  ...  l!] # The iteration check is pulled from the backup cell

# Step 2: Generate a set of 6 stats
6[>&p<6[>16?<,]p>arppp&+s>l<p<,]p
6[                            ,]p # Loop once for each stat

# Step 2.1: Generate 6 random dice rolle
>&p<6[>16?<,]p
>&p<           # Clear the stack to hold the rolls
    6[     ,]p # Loop 6 times
      >16?<    # Push random 1-6 on the dice roll stack

# Step 2.2: Compute the stat from the 6 dice rolls
>arppp&+s>l<p<
>a              # Switch to dice stack, sort entries
  rppp          # Reverse stack, pop 3 entries (lowest rolls)
      &+s       # Sum the remaining 3 (highest) and stash
         >l     # Switch stacks and load sum (stat)
           <p   # Switch back to dice stack and clear it
             <  # Switch back to the loop iterator stack

# Step 3: Check to see if stats meet criteria
>>&s<&l<<&s>>>>&p&l<a0Isp>rlGfp<s>flLfp<&+sp>rlLfp**sp<<<

# Step 3.1: Save a "ready to print" copy of the stats
>>&s<&1
>>&s     # Switch to the stats stack, stash on the backup cell
    <&1  # Switch to an empty stack and restore from backup

# Step 3.2: Init test stack, copy in low/high/sum thresholds
<<&s>>>>&p&l
<<&s         # Switch to rules stack, copy to backup cell
    >>>>&p&l # Switch to test stack, clear, load from backup

# Step 3.3: Check minimum stat against threshold
<a0Isp>rlGfp
<a           # Switch to stats stack, sort entries
  0Is        # Pull smallest entry to top, save in backup
     p       # Clean-up stack (pop duplicate)
      >r     # Switch to test stack, reverse entries
        lG   # Load min stat, do "min-stat>min-thresh" comp
          fp # Stack cleanup, delete min-thresh

# Step 3.4: Check maximum stat against threshold
<s>flLfp
<s        # Save largest stats to backup cell
  >f      # Switch to test stack, flip max-thresh to top
    lL    # Load max stat, do "max-stat<max-thresh" comp
      fp  # Stack cleanup, delete max-thresh

# Step 3.5: Check sum of stats against threshold
<&+sp>rlLfp
<&+s        # Sum stats, stash in back cell
    p>      # Cleanup stack, switch to test stack
      r     # Reverse stack, puts sum-thresh on top
       lL   # Load sum, do "sum-stat<sum-thresh" comp
         fp # Stack cleanup, delete sum-thresh

# Step 3.6: Consolidate test results
**sp<<<
**sp     # Multiple all three comps, stash the result
    <<<  # Switch to the iterator stack

#Step 4: Print the result
>  # Switch to stack with copy "ready to print" of stats
\$\endgroup\$
1
\$\begingroup\$

05AB1E, 35 34 bytes

[6Lε₄ε6LΩ}{¦O}©O¹@i®δ.SεN‹NQà}P#]®

Can probably be shorter, but this will do for now.

First input is t, second input is a pair [l,h].

Try it online or try it online with added debug lines.

Explanation:

[               # Start an infinite loop:
 6L             #  Push list [1,2,3,4,5,6]
   ε            #  Map each value to:
    ₄           #   Push 1000
     ε          #   Map each digit to:
      6L        #    Push list [1,2,3,4,5,6]
        Ω       #    Pop and leave a random item from this list
     }{         #   After the inner map: sort the four values from lowest to highest
       ¦        #   Remove the first/lowest
        O       #   And sum the remaining three together
   }©           #  After the outer map: save this list of six values in variable `®` (without popping)
     O          #  Sum the list
      ¹@i       #  If the sum is larger than or equal to the first input:
         ®      #   Push the list from variable `®` again
          δ     #   Apply double-vectorized with the (implicit) second input-pair:
           .S   #    Compare: -1 if a<b; 0 if a==b; 1 if a>b
         ε      #   Map this list of lists to:
          N‹    #    Check for each value if it's smaller than the 0-based map-index
            NQ  #    Then check if the result (0 or 1) is equal to the 0-based map-index
              à #    Check if any is truthy by leaving just the maximum
         }P     #   After the map: check if both are truthy by taking the product
           #    #   If this is truthy: stop the infinite loop
]               # Close the if-statement and infinite loop
 ®              # Push list `®`
                # (after which it is output implicitly as result)
\$\endgroup\$
0
\$\begingroup\$

Vyxal, 29 bytes

÷ʀ℅$19r℅":∑⁰$-:£4ḭw3ẋf:∑¥$-Wf

Try it Online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.