0

I'm trying to get random numbers to reach a certain sum I'm using for a DND type game. Characters can have 2-6 points to use, and they fill it with modules worth 1, 2, or 3 points.
So if a character had 6 points, they could have it any sequence as long as it sums to 6. If 2 points, they could either have two 1point modules or one two point module.

For this, I'm trying a random generator to do that. To start with a random number, then subtract that number from the maximum points, and then generate a new random number under that new maximum until it reaches 0. Here's my code, and unfortunately it often generates sums one point above the limit. Any advice? Thank you.

tp = totalPoint
oneMods, twoMods, threeMods = 0, 0, 0
while tp > 0:
  if(tp >= 3):
      var = random.randint(1, 3)
      tp = tp - var
      if var == 3:
          threeMods +=1
      elif var == 2:
          twoMods += 1
      else:
          oneMods += 1
  elif(tp <=2):
      var = random.randint(1,2)
      tp = tp - var
      if var == 2:
          twoMods +=1
      else:
          oneMods += 1
  else:
      var = 1
      tp = tp -1
      oneMods +=1
await ctx.send(f'You have:\nLevel one modules: {oneMods}\nLevel 2 modules: {twoMods}\nLevel three mods: {threeMods}')
  • 1
    Your `if` and `elif` clauses cover all possibilities (assuming `tp` is an integer); the `else` clause will never be executed. – jasonharper Jul 12 '21 at 23:16
  • Change `elif(tp <=2):` to `elif(tp >=2):` That way the `else:` clause will no longer be dead code, and your sums won't exceed your limit. The way it is now, if `tp` is 1, it's executing the `elif` clause, and sometimes picking 2 which will exceed the limit. – Tom Karzes Jul 12 '21 at 23:18
  • Does this answer your question? [Is there an efficient way to generate N random integers in a range that have a given sum or average?](https://stackoverflow.com/questions/61393463/is-there-an-efficient-way-to-generate-n-random-integers-in-a-range-that-have-a-g) – Peter O. Jul 13 '21 at 00:55

1 Answers1

1

To avoid any biasing as your sum approaches the target I would approach the problem differently. Instead of randomly generating the values that comprise the sum, I would generate all the possible lists of candidate values and randomly choose one of them.

>>> import itertools
>>> modules = [1, 2, 3]
>>> points = 6
>>> candidates = [module for x in range(points+1)
                         for module in itertools.product(modules, repeat=x) 
                             if sum(module)==points]
>>> candidates
[(3, 3),
 (1, 2, 3),
 (1, 3, 2),
 (2, 1, 3),
 (2, 2, 2),
 (2, 3, 1),
 (3, 1, 2),
 (3, 2, 1),
 (1, 1, 1, 3),
 (1, 1, 2, 2),
 (1, 1, 3, 1),
 (1, 2, 1, 2),
 (1, 2, 2, 1),
 (1, 3, 1, 1),
 (2, 1, 1, 2),
 (2, 1, 2, 1),
 (2, 2, 1, 1),
 (3, 1, 1, 1),
 (1, 1, 1, 1, 2),
 (1, 1, 1, 2, 1),
 (1, 1, 2, 1, 1),
 (1, 2, 1, 1, 1),
 (2, 1, 1, 1, 1),
 (1, 1, 1, 1, 1, 1)]

>>> import random
>>> random.choice(candidates)
(1, 1, 1, 1, 2)
Woodford
  • 3,746
  • 1
  • 15
  • 29
  • For a large number of `points` this could be very inefficient, but it may be the only way to guarantee true randomness. – Mark Ransom Jul 13 '21 at 00:32
  • @MarkRansom Agreed. For large `points` values it would probably be better to pre-generate the lists once and re-use them rather than generate them every time they're needed. – Woodford Jul 13 '21 at 15:34