Draw from a deck
Drawing random "cards" from a deck eliminates most of the issues often seen with dice-roll mechanics. With enough draws from the deck all the results will be seen eventually, and unless there are duplicate cards you won't get a run of the same results.
While the idea of cards may lead you to think of the standard 52 card deck of four suits, it is not limited to that. The objects in question need not be flat, or rectangular. Other examples you may be familiar with are:
- Family card games such as Snap! , Happy Families , Go Fish or Uno
- Bingo and Lottery systems with numbered balls
- The questions in a trivia game
- The villager roles in a game like Mafia/Werewolf and its many variants
- Tile-based games such as Dominoes , Scrabble or Carcassonne
- Random outcomes such as the Chance cards in Monopoly
- Deck building games such as Magic or Gwent
Draw pile
All deck systems need a draw pile: the collection of objects that random
picks are taken from.
For Ren'Py the most common implementation is a list of objects declared
with default
.
It needs to be a defaulted variable as it will change as objects are
taken from it.
Here the draw pile is a list of strings, but it could be some other type
such as numbers, NPCs, or Ren'Py labels.
default drawPile = ['cat', 'dog', 'ferret', 'goldfish', 'guinea pig', 'hamster']
Discard pile
Many, but not all, deck systems also have a discard pile. This is a list of used or discarded results and starts out empty. Typically once the draw pile is empty the discard pile is shuffled and becomes the new draw pile.
default discardPile = []
Random pick
This method is closer to drawing from a bag of tiles rather than a
pre-shuffled deck of cards.
It has the advantage that the randomization is carried out as needed,
and if necessary new or used "cards" can be added to the draw pile
without having to re-shuffle.
Care needs to be taken to not call renpy.random.choice
if the draw pile is empty (empty lists are False
in Python).
if not drawPile: $ pick = None else: $ pick = renpy.random.choice(drawPile) $ drawPile.remove(pick)
Used picks can be added to the discard pile by appending them to it:
$ discardPile.append(pick)
When the draw pile is empty it can be re-filled from the discard pile
by copying the entries in the discard pile back into the draw pile
with extend
and emptying the discard pile with
clear
:
if not drawPile: $ drawPile.extend(discardPile) $ discardPile.clear()
Draw and discard
These operations can be combined to draw a choice and
immediately discard it, and re-fill the draw pile as needed.
Note: When the last choice is taken from the draw pile it will
need to be re-filled, and there is a chance that the next draw will be
the same choice again.
if not drawPile: $ drawPile.extend(discardPile) $ discardPile.clear() $ pick = renpy.random.choice(drawPile) $ drawPile.remove(pick) $ discardPile.append(pick)
Shuffled deck
It is possible to instead shuffle the draw pile and take from the shuffled deck which is a closer simulation of a true deck of cards. This may be useful if your game requires the ability to peek at the next card in the draw pile. Before using the draw pile needs to be shuffled:
$ renpy.random.shuffle(drawPile)
To pick the "top" card (actually the last card in the list):
if not drawPile: $ pick = None else: $ pick = drawPile.pop()
To peek at the next card to draw the index -1
can be used:
if not drawPile: $ peek = None else: $ peek = drawPile[-1]
To add picked cards to the discard pile and automatically re-use it:
if not drawPile: $ drawPile.extend(discardPile) $ renpy.random.shuffle(drawPile) $ discardPile.clear() $ pick = drawPile.pop() $ discardPile.append(pick)
Using a deck of Ren'Py labels
One common use for deck-style randomisation is to pick events or scenes
for the player to experience.
In this case the draw pile can be a list of label names which can then
be invoked using call expression
:
default eventDrawPile = ['event1', 'event2', 'event3'] default eventDiscardPile = [] label randomEvent: $ renpy.dynamic('pick') if not eventDrawPile: $ eventDrawPile.extend(eventDiscardPile) $ eventDiscardPile.clear() $ pick = renpy.random.choice(eventDrawPile) $ eventDrawPile.remove(pick) $ eventDiscardPile.append(pick) if renpy.has_label(pick): call expression pick from random_event_dyn else: dbg "In randomEvent: label [pick] does not exist." return label event1: "Event 1" return ...
As this uses the draw from a bag style, new events can be added as
the story develops using eventDrawPile.append('event4')
or removed using eventDrawPile.remove('event1')
Sampling
The method
renpy.random.sample
provides a way of making
several deck-style draws from a sequence and returns a list of the picked
elements. This can be useful for example
for choosing a sub-set of NPCs who might be present at a location.
The example below picks three NPCs from a list:
$ npcs = ['amanda', 'brian', 'chloe', 'derek', 'emily', 'fredrick'] $ present = renpy.random.sample(npcs, 3)