Conditional weighted random choice

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
heyhey1133
Newbie
Posts: 2
Joined: Wed Jan 08, 2020 5:32 pm
Contact:

Conditional weighted random choice

#1 Post by heyhey1133 » Sat Jan 11, 2020 11:04 am

Hey everybody!
I'm working on a "board" game, with randomly generated board, where player has to do the actions that are shown on the cell they land on, and I'd like to add the possibility to choose how often or rare should each type of the cell be present on the board. For example, I have the following "tasks" for the player to do: jump, run, sit, stand. And before the game starts player would choose for each type of the task to be shown: 1) none, 2) rarely, 3) normal or 4) often.
And each cell would be generated with a random task from the weighted list.
I'm not good with coding, so I don't really know how to do it. I've found how to make a weighted random choice here, but I have no idea how to add the possibility for the player to edit the "weight" of each type of cell right from the game.
Right now I have a very simple code for each cell:
$ cell001 = renpy.random.choice(['jump', 'run', 'sit', 'stand'])
and if I want more "jump" tasks compared to the other tasks for example, I just edit the code like this:
$ cell001 = renpy.random.choice(['jump', 'jump', 'jump', 'jump', 'run', 'sit', 'stand'])
And it becomes some kind of a simple weighted choice, jump with weight of 4 and other types with a weight of 1.
And I was thinking, maybe there is a way to make it conditional, so I don't have to use any python code and keep it simple for me.
The player would choose the jump task to appear rarely, normally, often or not shown at all, and for each option there would be following variables:
jump_none = True or False, jump_rare = True or False, jump_normal = True or False, jump_often = True or False., And for example if a player would choose jumps to be shown normally, jump_none, jump_rare and jump_normal would be == True and jump_often would be == False
And if there is a way to make renpy.random.choice conditional it would be perfect, I would add something like this for each cell:
$ cell001 = renpy.random.choice([if jump_none == True then 'jump', if jump_rare == True then 'jump', if jump_normal == True then 'jump', if jump_often == True then 'jump', 'run', 'sit', 'stand'])
so it would add or remove "jump"s to the list of outcomes to choose from making it a "weighted" random choice. But I have no idea if it's even possible to put variables into the random choice code.
I hope it makes at least a little sense. I understand that this might be a very simple task for someone who can code, but I'm not good with this stuff, so I'm trying to improvise a little :) Would appreciate any help, thank you :)

rames44
Veteran
Posts: 232
Joined: Sun May 29, 2016 4:38 pm
Contact:

Re: Conditional weighted random choice

#2 Post by rames44 » Sat Jan 11, 2020 12:53 pm

Your approach of using different arrays under different conditions is actually a very good one. One thing you could look at would be to build that array dynamically. Yes, it needs Python, but it’s not gross.

I’m mobile right now, and my iPad will not let me do a good job typing code. I’ll try to edit this post when I get home.

User avatar
Alex
Lemma-Class Veteran
Posts: 2981
Joined: Fri Dec 11, 2009 5:25 pm
Contact:

Re: Conditional weighted random choice

#3 Post by Alex » Sat Jan 11, 2020 4:16 pm

After copy/pasting the code for class NonUniformRandom from the link, try to make a 'description' of each cell that has weights of tasks for this cell, like

Code: Select all

$ cell001_description = [('jump', 0), ('run', 0), ('sit', 0), ('stand', 0)]
a list of (task, weight) elements (you can change this elements later).

So, when player choose how often task would be shown for the cell, you could change the value in description, like if player choose 'jump' to be often, set

Code: Select all

$ cell001_description[0] = ('jump', 100)
and if player choose 'run' to be rarely, set

Code: Select all

$ cell001_description[1] = ('run', 1)
(a list members has indexes (indexes are started from 0), so you could have access to the list member by its index - in this sample ('jump', 0) is the first member of cell001_description list, so it has index 0).

Then, instead of

Code: Select all

$ cell001 = renpy.random.choice(['jump', 'jump', 'jump', 'jump', 'run', 'sit', 'stand'])
you could use

Code: Select all

$ cell001 = NonUniformRandom( cell001_description )
https://docs.python.org/release/2.6/tut ... structures

rames44
Veteran
Posts: 232
Joined: Sun May 29, 2016 4:38 pm
Contact:

Re: Conditional weighted random choice

#4 Post by rames44 » Sat Jan 11, 2020 9:38 pm

@Alex 's code is a good approach.

One of the things you have to take into account is the relative weighting. For example - you have three tasks, but what if the player chooses "rarely" for all three? Does that make them equally weighted? That doesn't feel "rare" to me. So, to start off with, rating them individually might not be the best idea. Perhaps, instead, they should choose "most frequent," "next most frequent," "least frequent" out of the three tasks. ("least frequent" would automatically be the one that they didn't choose in the first two.) If you take that approach, then there are really only six possible combinations.

But, that being said, you can use Python to build up a "weighted array" in a manner similar to this:

Code: Select all

init python:
    def get_action():
        choices = []
        if jump_often:
            choices.extend(["jump", "jump", "jump", "jump"])
        if jump_normal:
            choices.extend(["jump", "jump", "jump"])
        if jump_rare:
            choices.extend(["jump"])

        # Do the same for the other options

        return renpy.random.choice(choices)
And then

Code: Select all

    $ cell001 = get_action()
The core idea is that inside each of the "if" statements, you dynamically add as many of the options as are appropriate to build the final array that you'll pass to renpy.random.choice(). Wrapping it inside a function (inside an "init python" block) makes it neat and clean in the main part of your code.

heyhey1133
Newbie
Posts: 2
Joined: Wed Jan 08, 2020 5:32 pm
Contact:

Re: Conditional weighted random choice

#5 Post by heyhey1133 » Fri Apr 10, 2020 4:05 am

Alex wrote:
Sat Jan 11, 2020 4:16 pm
After copy/pasting the code ...
rames44 wrote:
Sat Jan 11, 2020 9:38 pm
@Alex 's code is a good approach...
Thank you so much, @Alex @rames44! Thanks to your help, it's working really good and it's really easy to edit the code whenever I need it.
Sorry it took so long for me to reply, got completely distracted from the project and forgot about it for a while :)
I've ended up doing it like this:

Code: Select all

init python:
    class NonUniformRandom(object):
        def __init__(self, list_of_values_and_probabilities):
            """
            expects a list of [ (value, probability), (value, probability),...]
            """
            self.the_list = list_of_values_and_probabilities
            self.the_sum = sum([ v[1] for v in list_of_values_and_probabilities])
        def pick(self):
            """
            return a random value taking into account the probabilities
            """
            import random
            r = random.uniform(0, self.the_sum)
            s = 0.0
            for k, w in self.the_list:
                s += w
                if r < s: return k
            return k

label start:

    $ cell_description = [('jump', 50), ('run', 50), ('stand', 50)]
    call screen settings

label jump_very_often:
    $ cell_description[0] = ('jump', 100)
    call screen settings
label jump_often:
    $ cell_description[0] = ('jump', 75)
    call screen settings
label jump_normal:
    $ cell_description[0] = ('jump', 50)
    call screen settings
label jump_rare:
    $ cell_description[0] = ('jump', 25)
    call screen settings
label jump_none:
    $ cell_description[0] = ('jump', 0)
    call screen settings

# Same for "run" and "stand", only changed [0] to [1] and [2]

label start_game:
    hide screen settings
    $ cell001 = NonUniformRandom( cell_description ).pick()
    $ cell002 = NonUniformRandom( cell_description ).pick()
    $ cell003 = NonUniformRandom( cell_description ).pick()
    $ cell004 = NonUniformRandom( cell_description ).pick()
    $ cell005 = NonUniformRandom( cell_description ).pick()
    $ cell006 = NonUniformRandom( cell_description ).pick()
    $ cell007 = NonUniformRandom( cell_description ).pick()
    $ cell008 = NonUniformRandom( cell_description ).pick()
    $ cell009 = NonUniformRandom( cell_description ).pick()
    # ... as many cells as I need
	
    show screen cells
# the rest of the code for playing the game here

screen settings:
    button:
        xalign 0.5 yalign 0.965
        action Jump("start_game")
        text _("start_game") style "button"

    imagebutton:
        focus_mask True
        idle "images/bt idle.png"
        hover "images/bt hover.png"
        action Jump("jump_very_often")
        anchor (0.5, 0.5)
        xpos 300
        ypos 250
    # same for often, normal, rare and none options but in different positions and jumping to their own labels for each task category


screen cells:
    # cell001
    if cell001 == 'jump':
        add "icon jump.png" anchor (0.5, 0.5) xpos 295 ypos 853
    if cell001 == 'run':
        add "icon run.png" anchor (0.5, 0.5) xpos 295 ypos 853
    if cell001 == 'stand':
        add "icon stand.png" anchor (0.5, 0.5) xpos 295 ypos 853
# same for other cells, but different positions




Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot], span4ev