Ren'Py How to code multiple variables (13 total) that are true (SOLVED)

GrayTShirt

Well-Known Member
Game Developer
Nov 27, 2019
1,204
6,289
I have 13 scenes that are default = False. After each scene is viewed, I have them set up as $ scene = True.
There is a message that should come up in my scene selection map that will say "All scenes viewed and repeatable".
How can I group these variables so that when all 13 scenes are viewed, the message will appear?

I have hover messages that appear for different scenes:
(This is just a snippet of my "myscreens.rpy" script)

Code:
hotspot (201, 620, 181, 128):

            if ch4home1complete:

                action Jump("hilda")

            else:

                action NullAction()

            hovered Function(set_castle_map_hint_text, "hilda")

            unhovered Function(clear_castle_map_hint_text)


frame:

        style "mapHintFrame"

        xalign 0.5

        yalign 0.97

        xsize 800

        ysize 120

        hbox:

            xalign 0.5

            yalign 0.5


            text castle_map_hint_text



init python:


    castle_map_hint_text = ""


    def set_castle_map_hint_text(option):


        global ch4home1complete

        global castle_map_hint_text


        if option == "hilda":

            if not ch4home1complete:

                castle_map_hint_text = "View the 'Home' scene to unlock"

            else:

                castle_map_hint_text = ""


        elif option == "fionastories":

            castle_map_hint_text = "Fiona tells enchanting stories to her sister"


        elif option == "bryn":

            if not queenmainstory1complete:

                castle_map_hint_text = "View 'Tunrida's Room 1' scene to unlock"


  def clear_castle_map_hint_text():

          global castle_map_hint_text

          castle_map_hint_text = ""
Thank you for the help
 

gojira667

Member
Sep 9, 2019
325
320
How can I group these variables so that when all 13 scenes are viewed, the message will appear?
These are all normal boolean vars? showif/if:
Code:
    if ch4home1complete and queenmainstory1complete and otherscenevars: # and ... and ... and ...
        pass
 

MidnightArrow

Active Member
Aug 22, 2021
500
451


Ren'py stores that internally, you don't need a boolean for it. Though the Ren'py documentation is often subpar so I don't know if there's any gotchas in using it.

The most efficient way to check would be like this:

Code:
init python:

    def all_conditions_met():
    
        if not renpy.seen_label("label_1"):
            return False
    
        if not renpy.seen_label("label_2"):
            return False
        
        if not renpy.seen_label("label_3"):
            return False
            
        [...]
        
        return True
If even one of the thirteen conditions isn't met, the function returns false. It's much better than chaining them into one giant AND conditional on the same (very crowded) line.
 
  • Like
Reactions: dannybighuz

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,503
8,038
The best way is to have an array of bool, then use a for loop.
All that if if if if if if if if if if if if looks like a meme straight out of yanderedev
 

MidnightArrow

Active Member
Aug 22, 2021
500
451
The best way is to have an array of bool, then use a for loop.
All that if if if if if if if if if if if if looks like a meme straight out of yanderedev
Using an array of booleans would get in the way of Ren'py's save/load system since they're not individually "default"ed.

Also using if/else statements to check conditional logic is Programming 101, so I'm not sure what Yanderedev has to do with anything.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,503
8,038
Using an array of booleans would get in the way of Ren'py's save/load system since they're not individually "default"ed.

Also using if/else statements to check conditional logic is Programming 101, so I'm not sure what Yanderedev has to do with anything.
Save/Load supports array, I have used them in the past.
It is okay if you do an if/else statement to check for something, but if you have a huge list of what is essentially a copy/paste, then you are doing it wrong.

Using an array, then a few lines for a for loop is the correct way of doing it.
 
  • Like
Reactions: Nagozo

MidnightArrow

Active Member
Aug 22, 2021
500
451
Save/Load supports array, I have used them in the past.
It is okay if you do an if/else statement to check for something, but if you have a huge list of what is essentially a copy/paste, then you are doing it wrong.
Save/load does support arrays but the docs recommend defaulting variables to ensure they're always saved, and Python doesn't allow you to hold references to primitive datatypes so you can't have an array of pointers either.

Plus if you store them inside an array they won't show up inside the variable viewer, and if you want to access them consistently you'll need to define thirteen enum indices too, or else you have a bunch of magic numbers in your code. In this case it's safer and more human-readable to unroll the loop and call the conditionals in sequential order, since there's no point optimizing for speed on a single script called only once when a screen is loaded. "Correct" depends on the situation.

Also if OP uses renpy.seen_label() then they'll have to create a pointless array just to hold the thirteen label names to loop through.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,503
8,038
Save/load does support arrays but the docs recommend defaulting variables to ensure they're always saved, and Python doesn't allow you to hold references to primitive datatypes so you can't have an array of pointers either.

Also if OP uses renpy.seen_label() then they'll have to create a pointless array just to hold the thirteen label names to loop through.
I had in mind the bool thing OP mentioned.
seen_label() could be fine, but in my project I had a scene split in many labels... It is not always 1 label = 1 scene.
If he uses bools, you will simply need to remember to increase a number (maybe a variable called "sceneNumber") and the code will do the rest.

I also generally avoid strings when possible, so I find bools to be an overall cleaner solution.


there's no point optimizing for speed on a single script called only once when a screen is loaded. "Correct" depends on the situation.
It is not about speed, there's no gain in that.

It is about doing the least amount of things, so rather than having to write extra if, you can increase a simple number and be done with it.
If you have a separate folder, and each scene is contained in a file, you wouldn't even need to increase that number manually, and simple read the amount of files in that folder, that'd be the best scenario because you could literally forget about it and the system would work indefinitely.

The other reason is that it looks fine as long as it is 10 scenes or even 20, but if your game reaches 100 scenes or more, you'll end up with a mountain of if to scroll down. It is far from readable.
 

crabsinthekitchen

Well-Known Member
Apr 28, 2020
1,565
9,079


Ren'py stores that internally, you don't need a boolean for it. Though the Ren'py documentation is often subpar so I don't know if there's any gotchas in using it.

The most efficient way to check would be like this:

Code:
init python:

    def all_conditions_met():

        if not renpy.seen_label("label_1"):
            return False

        if not renpy.seen_label("label_2"):
            return False
    
        if not renpy.seen_label("label_3"):
            return False
        
        [...]
    
        return True
If even one of the thirteen conditions isn't met, the function returns false. It's much better than chaining them into one giant AND conditional on the same (very crowded) line.
seen_labelworks kinda like persistent, in a sense that if you finish the game and start a new one, seen_label will return True for all labels you've already seen on the previous run. so it might be exactly what OP wants or it might not

Save/load does support arrays but the docs recommend defaulting variables to ensure they're always saved, and Python doesn't allow you to hold references to primitive datatypes so you can't have an array of pointers either.
Thankfully Python arrays don't have a fixed size so you can do this
Code:
default seen_scenes = []
define max_scenes = 13

label scene1:
    $ seen_scenes.append("scene1")
    if len(seen_scenes) == max_scenes:
        show message_screen

Plus if you store them inside an array they won't show up inside the variable viewer, and if you want to access them consistently you'll need to define thirteen enum indices too, or else you have a bunch of magic numbers in your code. In this case it's safer and more human-readable to unroll the loop and call the conditionals in sequential order, since there's no point optimizing for speed on a single script called only once when a screen is loaded. "Correct" depends on the situation.
Don't know what I'm doing different but I can see arrays inside the variable viewer just fine
arrays.png
If I need to know what's exactly in the array, I can always use console
 
  • Like
Reactions: Winterfire

MidnightArrow

Active Member
Aug 22, 2021
500
451
I had in mind the bool thing OP mentioned.
seen_label() could be fine, but in my project I had a scene split in many labels... It is not always 1 label = 1 scene.
If he uses bools, you will simply need to remember to increase a number (maybe a variable called "sceneNumber") and the code will do the rest.

I also generally avoid strings when possible, so I find bools to be an overall cleaner solution.



It is not about speed, there's no gain in that.

It is about doing the least amount of things, so rather than having to write extra if, you can increase a simple number and be done with it.
If you have a separate folder, and each scene is contained in a file, you wouldn't even need to increase that number manually, and simple read the amount of files in that folder, that'd be the best scenario because you could literally forget about it and the system would work indefinitely.

The other reason is that it looks fine as long as it is 10 scenes or even 20, but if your game reaches 100 scenes or more, you'll end up with a mountain of if to scroll down. It is far from readable.
In hindsight, I don't think the OP gave us enough info to go on.

I've been assuming they want the flag to trigger only on thirteen specific predetermined scenes, but it sounds like you assume they want the flag to trigger if the player has seen every scene in the whole game as defined by the script files?

For my solution, it doesn't matter how many scenes their game has. Only if those specific scenes have been seen. Writing it out helps to make the logic human-readable since you can see which scenes you're testing for by name. If you tried to do that with an array you'd need an enum const to keep track of which scene you're testing for, which isn't really saving you a whole lot of writing since you need to both define it and type it out inside the array braces for every single use.

But I guess it depends on what OP is actually trying to do.

Don't know what I'm doing different but I can see arrays inside the variable viewer just fine
If I need to know what's exactly in the array, I can always use console
Having to use the console to examine the contents of an array isn't nearly as convenient as being able to see the individual variables at a glance.
 

Winterfire

Forum Fanatic
Respected User
Game Developer
Sep 27, 2018
5,503
8,038
I've been assuming they want the flag to trigger only on thirteen specific predetermined scenes, but it sounds like you assume they want the flag to trigger if the player has seen every scene in the whole game as defined by the script files?
I am assuming it is just the current amount of scenes, and it may very well go on the hundreds by the time his game is completed.

He also specified he wants to check every scene, and not just the last one... I assume his game is not linear, otherwise it would be as easy as checking whether the last scene has been viewed or not.
 

Bip

Active Member
Donor
May 4, 2017
737
2,131
Not worse than having a bunch of "if and and and", maybe something like:

Python:
    python:
        allDone = True

        # using your boolean: Choose this one OR the one below
        # Here, scene1, scene2... are your boolean
        for tmpCheck in [ scene1, scene2, scene3, scene4, scene5 (...) ]:
            allDone = allDone and tmpCheck

        # using Ren'Py stuff: Choose this one OR the one above
        # Here, scene1, scene2... are your scene labels
        for tmpCheck in [ "scene1", "scene2", "scene3", "scene4", "scene5" (...) ]:
            allDone = allDone and renpy.seen_label ( tmpCheck )

    if allDone:
        "You've seen everything"
    else:
        "There's more to see"
I could not test with the Ren'Py stuff, but I don't see why there would be some trouble.... Except for the Murphy's law, of course !
 
  • Like
Reactions: 79flavors

79flavors

Well-Known Member
Respected User
Jun 14, 2018
1,608
2,256
Not worse than having a bunch of "if and and and", maybe something like:

This is probably better than what you had planned.

A variation on this same idea would be to have a of the scenes you want to keep track of. Initialized with a default statement.
As you complete each scene, remove the relevant scene name from the list.
If the list is empty, then that's your indication that all the scenes have been completed.

You could also use it as a way of picking the next scene. If the scene name is the same as the label, you could just jump to the label matching a random element from the list.

Additionally, it adds flexibility. In so much as you could add scenes in the future without breaking too much.

Something like:

Python:
default scenes_todo_list = [ "scene1_start", "scene2_start", "scene3_start" ]
default scenes_completed = []
default allDone = False

init python:

    def finish_scene(scene_id):

        if scene_id in scenes_todo_list:
            scenes_todo_list.remove(scene_id)
            scenes_completed.append(scene_id)

            if len(scenes_todo_list) == 0:
                store.allDone = True
                renpy.notify("All scenes completed")

# only needed to add new scenes after the initial release of the game.
label after_load():

    python:
        if ("scene4_start" not in scenes_todo_list) and ("scene4_start" not in scenes_completed):
            scenes_todo_list.add("scene4_start")
            store.allDone = False

label start:

    scene black


label primary_loop:

    menu:
        "Scene 1" if "scene1_start" in scenes_todo_list:
            jump scene1_start

        "Scene 2" if "scene2_start" in scenes_todo_list:
            jump scene2_start

        "Scene 3" if "scene3_start" in scenes_todo_list:
            jump scene3_start

        "Repeat Scene 1" if allDone:
            jump scene1_start

        "Repeat Scene 2" if allDone:
            jump scene2_start

        "Repeat Scene 3" if allDone:
            jump scene3_start

        "End":
            jump the_end


label scene1_start:

    scene black
    centered "Scene 1"

    $ finish_scene("scene1_start")

    jump primary_loop


label scene2_start:

    scene black
    centered "Scene 2"

    $ finish_scene("scene2_start")

    jump primary_loop


label scene3_start:

    scene black
    centered "Scene 3"

    $ finish_scene("scene3_start")

    jump primary_loop


label the_end:

    return

The after_load(): code is there to add a new todo scene into a game already in progress. It wouldn't be there from day 1, but it could be added later to introduce new scenes.

I couldn't see a need for the scenes_completed list, but I included it just in case I wanted it in the future.

I'm not sure the allDone variable is needed. But it does no harm either.

There are definitely ways to improve this code, especially in the area of the label primary_loop:.
The obvious one that comes to mind is jump expression {labelname}, where {labelname} is a variable.
Another would be to use values like "scene1" instead of "scene1_start" and concatenate the "_start" part of the label name on afterward.
 

Alcahest

Engaged Member
Donor
Game Developer
Jul 28, 2017
3,485
4,330
Not worse than having a bunch of "if and and and", maybe something like:

Python:
    python:
        allDone = True

        # using your boolean: Choose this one OR the one below
        # Here, scene1, scene2... are your boolean
        for tmpCheck in [ scene1, scene2, scene3, scene4, scene5 (...) ]:
            allDone = allDone and tmpCheck

        # using Ren'Py stuff: Choose this one OR the one above
        # Here, scene1, scene2... are your scene labels
        for tmpCheck in [ "scene1", "scene2", "scene3", "scene4", "scene5" (...) ]:
            allDone = allDone and renpy.seen_label ( tmpCheck )

    if allDone:
        "You've seen everything"
    else:
        "There's more to see"
I could not test with the Ren'Py stuff, but I don't see why there would be some trouble.... Except for the Murphy's law, of course !
This thread should be renamed to a thousand ways to do the same thing differently. :)

That said, you're not adding any benefit when you complicate things with adding an array when you could just write a oneliner like in the first answer. Unless you lift out the array to be used elsewhere also. for tmpCheck in myArray

But if you do use an array, there is no need to loop through the whole array every time.
Python:
        for tmpCheck in [ scene1, scene2, scene3, scene4, scene5 (...) ]:
            if not tmpCheck:
                allDone = False
                break
 

GrayTShirt

Well-Known Member
Game Developer
Nov 27, 2019
1,204
6,289
So much to learn! I definitely bit off more than I could chew.
Time to learn all the things!
Thank you for the help!
-GTS
 

GrayTShirt

Well-Known Member
Game Developer
Nov 27, 2019
1,204
6,289
Well...
Turns out I was able to do it after all. Thank you for everything!
You don't have permission to view the spoiler content. Log in or register now.

Once I had all my "default scene = False" (x13) set up, I did this:
Python:
if abaddonstory1complete and ch4home1complete and queenmainstory1complete and arenaviewed and hildaviewed and brynviewed and mauveviewed and fionaviewed and tunridaviewed and skyeviewed and greerlyallviewed and bodilviewed:
            castle_map_hint_text = "All scenes viewed and repeatable"
        else:
            castle_map_hint_text = ""
The message only shows up when all of the 13 scenes are viewed, otherwise it's blank. The smaller scene icons have messages like "View Home 1 to unlock" or similar if their individual view condition isn't met.

Thank you again! Editing the title to solved.
-GTS
 

Nagozo

Member
Sep 30, 2017
125
246
This thread should be renamed to a thousand ways to do the same thing differently. :)

That said, you're not adding any benefit when you complicate things with adding an array when you could just write a oneliner like in the first answer. Unless you lift out the array to be used elsewhere also. for tmpCheck in myArray

But if you do use an array, there is no need to loop through the whole array every time.
Just out of spite I want to add one more one-liner to the list of options, using Python's reduce() (from functools):

Python:
alldone = reduce(lambda a, b: a and b, [scene1, scene2, scene3, ...]) # Assuming all these are booleans
Which I think looks slightly more elegant still than using a for loop lol
Edit: A second after posting this I realised there's also just the all() function which is arguably the easiest way to solve this if you have a list of booleans.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228


Ren'py stores that internally, you don't need a boolean for it.

Though the Ren'py documentation is often subpar so I don't know if there's any gotchas in using it.
You mean, a gotchas like in "Returns true if the named label has executed at least once on the current user's system".

This is a persistent value. Once the player have seen a label, it will be marked as "seen", even if it was two years ago when playing the game in a totally different way.

Here it can do it, since it's a gallery/achievement system. But if he also need the boolean for other reasons, renpy.seen_label() would be irrelevant and misleading.
 

MidnightArrow

Active Member
Aug 22, 2021
500
451
You mean, a gotchas like in "Returns true if the named label has executed at least once on the current user's system".

This is a persistent value. Once the player have seen a label, it will be marked as "seen", even if it was two years ago when playing the game in a totally different way.

Here it can do it, since it's a gallery/achievement system. But if he also need the boolean for other reasons, renpy.seen_label() would be irrelevant and misleading.
That is the kind of shit that belongs in the documentation, yes.

Then again I can't think of anything more irrelevant and misleading than the Ren'py documentation, which is full of weird turns-of-phrases like "screens must not have side effects" (meaning don't change variables outside the screen) or "this will return from the interaction" (meaning it will close the screen containing a creator-defined displayable) that aren't explained anywhere, except maybe a patron-exclusive blog post that isn't reachable from the search bar. So when I see something like "executed at least once on the current user's system" I assume it's just more obtuse jargon PyTom made up like we're supposed to know what it means, since I expect the documentation to tell us important things like "This is a persistent variable."
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,971
16,228
Then again I can't think of anything more irrelevant and misleading than the Ren'py documentation,
It's wrote by a programmer, and for programmers. Globally it's understandable.


[...] except maybe a patron-exclusive blog post that isn't reachable from the search bar.
Every Patreon article are available for anyone after a month.


So when I see something like "executed at least once on the current user's system" I assume it's just more obtuse jargon PyTom made up like we're supposed to know what it means, since I expect the documentation to tell us important things like "This is a persistent variable."
It's what it mean, and it's not as obscure as you make it seem.
"User's system", it's a software, so it refer to your computer, or more precisely your Operation System. "current" because there's no magic, but some users will not see it as obvious. And "at least once" without additional information mean that it could have happened anytime in the past.
It's not necessarily the best phrasing, but it's just English and there's no jargon.