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.5

As 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)

Ren'Py 8 only:

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:

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'])

Ren'Py 8 only:

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

Ren'Py 8 only:

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