Rolling dice
With dice style randomness each roll (or coin toss) is independent, and runs of the same result are quite possible.
Boolean results
These code snippets give results that are booleans.
Coin toss
The simplest is a simple coin toss where heads is True
and
tails is False
and both outcomes are equally likely.
The function
renpy.random.random()
returns a floating
point value that's between zero and one. Specifically it uses a semi-open
range so the number satisfies 0 <= rnd < 1. So it can return
zero, but won't generate exactly one.
$ heads = renpy.random.random() >= 0.5As Python treats zero as
False
, and any other integer
as True
, picking a random integer that's either 0 or 1
will also do what's needed:
$ heads = renpy.random.randint(0, 1)
The function
renpy.random.randint(start, stop)
picks a
random number between start
and stop
, inclusive.
Probabilities
A fifty:fifty result from a coin toss isn't always what's needed.
You may want one in ten, 13 in 20, or some other proportion.
A simple way to do this is to use probabilities.
This simple Python function takes the probability of a True
result and returns True
or False
.
It's also safe to use with probabilities outside the normal
range of 0.0..1.0.
init python: def rndProb(pTrue=0.5): """ Generate a random boolean result with a given probability of True. :param pTrue: likelihood of a True result. Default half the time. """ return renpy.random.random() < pTrue
It can be used with a static probability value:
if rndProb(0.75): # This is executed 3/4 of the time. ...
Or with a computed probability such as with a (fictitious) player character stat check where the PC has skills from 0..20:
if rndProb(pc.dex / 20.0): # This is executed more often when the player character has a # good dex score out of 20. ...
Note: As Python is used by mathematicians there are extensive libraries for computing probabilities.
Percentages
You may prefer to work with percentages instead of probabilities. As a percentage chance is just a probability scaled by 100 this additional Python function allows you to work with percentages:
init python: def rndPercent(pctTrue=50.0): """ Generate a random boolean result with a given percentage of True. :param pctTrue: percentage of a True results. Default 50%. """ return rndProb(pctTrue / 100.0)
Integer results
Dice rolls typically generate integer results.
The standard six-sided die has faces marked one to six.
The function
renpy.random.randint(start, stop)
picks a
random number between start
and stop
, inclusive
and can be used to simulate dice rolls.
$ d6 = renpy.random.randint(1, 6)
This isn't limited to the standard polyhedral dice used for table-top games. If you need an eleven sided die you can do that too. The start and stop values can be any integer. So for example a fudge dice (-1, 0, +1) could be rolled with:
$ df = renpy.random.randint(-1, 1)
Combining rolls
It's fairly common in dice based systems to add together multiple rolls to generate values with different probabilities for each number. One die always has the same probability for each outcome. Two gives a triangle shaped distribution. With three or more it begins to approximate a gaussian distribution.
init python: def rollDice(sides=6, rolls=1, add=0): result = add for roll in range(rolls): result += renpy.random.randint(1, sides) return result
If you actually want a true gaussian distribution there's a better
way to do this than rolling lots of dice with
renpy.random.gauss(mean, sd)
.
Exploding dice, wild dice, drop lowest...
There are many weird and wonderful variations on rolling sets of dice.
While you could roll your own solution, there are Python libraries that
are available to do these things, including parsing of string-based
definitions of roll types.
You may need to modify these to use renpy.random
.
Rolls that don't increase in ones
If you need to have steps in your results, such as rolling a number from
a set of values with gaps like 30, 35, 40, ... 90, 95 then you can either
multiply the result of a renpy.random.randint
by a number:
$ stat = 5 * renpy.random.randint(6, 19)
Or you can use the
renpy.random.randrange(start, stop, step)
method. This works similar to Python's range
, so the
stop
value is not included in the possible results.
$ stat = renpy.random.randrange(30, 100, 5)
Non-numeric results
The random number functions provide a number of methods that work with sequences which need not be numbers. Here are some example techniques:
Poker dice
The faces of six-sided poker dice are marked with representations of playing cards: 9, 10, J, Q, K, A. The players try to roll the best poker-style hand (five of a kind is possible, unlike with a conventional deck of cards) with a set of five dice, re-rolling some up to twice. The game is similar in some ways to the later game Yahtzee, which uses conventional six-sided dice. The list of faces can be declared with a tuple:
define pokerDiceFaces = ('9', '10', 'J', 'Q', 'K', 'A')
There are two approaches you could use to roll one of these kinds of die. One way is simply roll an index into the tuple:
$ face = pokerDiceFaces[renpy.random.randrange(6)]
The function
renpy.random.choice(seq)
picks a random element from
a sequence. This removes the need to know how many elements are in
the sequence.
$ face = renpy.random.choice(pokerDiceFaces)
Dice poker involves rolling five dice in the initial throw.
Several dice can be rolled at once using
renpy.random.choices(seq)
to generate a list of results:
$ faces = renpy.random.choices(pokerDiceFaces, k=5)
Dice with the same symbol on two or more faces
Some dice have the same number or symbol on more than one face. Examples include:
- Average dice: 2, 3, 3, 4, 4, 5
- Sicherman dice : 1, 2, 2, 3, 3, 4 and 1, 3, 4, 5, 6, 8
- Various kinds of Intransitive dice
- HeroQuest dice : skull, skull, skull, white shield, white shield, black shield
These kinds of dice can be rolled using the function
renpy.random.choice(seq)
and repeating the face values as needed:
$ dAvg = renpy.random.choice([2, 3, 3, 4, 4, 5]) $ dHq = renpy.random.choice(['skull', 'skull', 'skull', 'white', 'white', 'black'])
Alternatively
renpy.random.choices(seq)
with different weights for
each result is another possible solution:
$ dAvg = renpy.random.choices([2, 3, 4, 5], weights=[1, 2, 2, 1])[0] $ dHq = renpy.random.choices(['skull', 'white', 'black'], weights=[3, 2, 1])[0]
Calling random labels
Picking from a list of Ren'Py labels allows random variations to be
introduced into a story. These could be trivial as random responses
from an NPC, or more impactful such as events that happen when the
player character explores a location.
The subroutine below can be used to do this using
renpy.random.choice(seq)
.
# Call one of the labels from the provided list of labels. # label callRndLabel(listLabels): $ renpy.dynamic('pick') $ pick = renpy.random.choice(listLabels) if renpy.has_label(pick): call expression pick from call_rnd_label_dyn else: dbg "In callRndLabel: label [pick] does not exist." return
To use it call it with list of labels. In the example below each event is equally likely. It uses a static list, but you could build the list dynamically based on the progress of the story.
label randomAlley: call callRndLabel(['alleyEmpty', 'alleyCat', 'alleyVomit']) return label alleyEmpty: "The alley is empty save for the normal dumpsters and wind-blown litter." return label alleyCat: "A tomcat jumps out from behind a dumpster arches its back and hisses at you." return label alleyVomit: "As you search the alley you narrowly avoid treading in a pool of vomit." return
Alternatively with Ren'Py 8 the method
renpy.random.choices(seq)
could be used to provide an optional list of relative weights for each option.
# Call one of the labels from the provided list of labels. # Optionally a list of weights can be provided, one per label. # label callRndLabelWeighted(listLabels, listWeights=None): $ renpy.dynamic('pick') $ pick = renpy.random.choices(listLabels, weights=listWeights)[0] if renpy.has_label(pick): call expression pick from call_rnd_label_weighted_dyn else: dbg "In callRndLabelWeighted: label [pick] does not exist." return
To use it call it with list of labels, and optionally a list of weights. In the example below the empty event is now twice as likely as the other two.
label randomAlleyWeighted: call callRndLabelWeighted(['alleyEmpty', 'alleyCat', 'alleyVomit'], [2,1,1]) return