Checking persistent data with a loop in a screen [Solved]

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
User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Checking persistent data with a loop in a screen [Solved]

#1 Post by Matalla »

OK, from the creator of "A couple of really silly questions already answered somewhere which I can't found" here is another one:

In a screen that register the progress of the user through the game (sort of a achievement thing), I need to check several categories and show the results. Like, there are 46 endings, and it should show "5 of 46 endings" if the player has reached 5 endings.

Truth is, I have a system that works. But even I, not a programmer at all, am upset with the method. What I do is this:

Code: Select all

$ counterfinals = 0
if persistent.ending_1:
	$ counterfinals += 1
else:
	pass
if persistent.ending_2:
	$ counterfinals += 1
else:
	pass
# etc. Until 46 (in this case, I have some categories even longer)

label _("Endings")
text _("[counterfinals] of 46 endings")
So, I'd like to do it with a loop, but can't do it with "while" in a screen, which I think I could manage (I have done similar things inside a label) and I can't get it done with a "for", either gives an error or return 0. Sort of this thing (have not checked if it's right, but you get the idea), but with the proper syntax for a screen.

Code: Select all

$ counterfinals = 0
$ endingnumber = 0
$ checkending = "persistent.ending_"
while endingnumber <= 46:
	$ endingnumber += 1
	$ checkending = checkending + str(endingnumber)
	if checkending == True:
		$ counterfinals += 1
	else:
		pass
	$ checkending = "persistent.ending_"
Last edited by Matalla on Sun Mar 24, 2019 11:06 am, edited 3 times in total.
Comunidad Ren'Py en español (Discord)
Honest Critique

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

Re: Checking persistent data with a loop in a screen

#2 Post by Alex »

You can store the info about endings as a list of True/False values, like

Code: Select all

init python:
    if not persistent.endings:
        persistent.endings = []
        for end in range (5):
            persistent.endings.append(False)

screen ends():
    $ unlocked = persistent.endings.count(True)
    $ all_ends = len(persistent.endings)
    text "Unlocked [unlocked] ending(s) out of [all_ends]." align(0.5,0.05)
    
# The game starts here.

label start:
    "..."
    show screen ends
    menu:
        "End 1":
            $ persistent.endings[0] = True
            "Ending 1 unlocked."
            jump start
        "End 2":
            $ persistent.endings[1] = True
            "Ending 2 unlocked."
            jump start
        "End 3":
            $ persistent.endings[2] = True
            "Ending 3 unlocked."
            jump start
        "End 4":
            $ persistent.endings[3] = True
            "Ending 4 unlocked."
            jump start
        "End 5":
            $ persistent.endings[4] = True
            "Ending 5 unlocked."
            jump start
https://docs.python.org/release/2.6/tut ... ight=count

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen

#3 Post by Matalla »

Thanks for your answer, Alex. I can really appreciate the cleanliness of your code, something like that is what I was looking for. However, there are just too many things I don't fully understand in it to dare to use it (I am sure it would work pasting it, but I want to understand what I put in the game for eventual modifications and stuff).

Is there another way to do that kind of check using the method I used for naming the persistent variables (persistent.whatever_1, persistent.whatever_2, etc.)?

EDIT: I have been toying with the code in a blank project and I think now I understand how it works (mostly). Enough for me to start trading punches with the code anyway. So I am going to mark it as solved, although I'd still like to know how to write the kind of loop I asked for (for other purposes).
Comunidad Ren'Py en español (Discord)
Honest Critique

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

Re: Checking persistent data with a loop in a screen (Solved)

#4 Post by rames44 »

The problem with your “while” loop approach is that you’re building an expression as a string into “checkending”, but then your “If” is just looking at the string value itself. To take the approach you’re trying to do, you’d have to force Python to actually evaluate the string as an expression.

In other words,

Code: Select all

if checkending == True
Is basically saying “is the string ‘persistent.ending_1’ equal to True”. Which, because of how Python handles truthyness, will always answer yes.
What you want is “does the string contained in checkending, when evaluated as a Python expression, equal True”
To do that, you’d need to replace ‘checkending’ with ‘eval(checkending)’
Eval() is a function that takes a string, evaluates it as a expression, and returns the result of the expression.
Also, the way your loop is constructed, the third iteration will have the ‘checkending’ variable containing the value ‘persistent.ending_123’ because you’re appending numbers to the previous result, not building ‘checkending’ from scratch.

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen (Solved)

#5 Post by Matalla »

rames44 wrote: Sat Mar 23, 2019 2:23 pm The problem with your “while” loop approach is that you’re building an expression as a string into “checkending”, but then your “If” is just looking at the string value itself. To take the approach you’re trying to do, you’d have to force Python to actually evaluate the string as an expression.

In other words,

Code: Select all

if checkending == True
Is basically saying “is the string ‘persistent.ending_1’ equal to True”. Which, because of how Python handles truthyness, will always answer yes.
What you want is “does the string contained in checkending, when evaluated as a Python expression, equal True”
To do that, you’d need to replace ‘checkending’ with ‘eval(checkending)’
Eval() is a function that takes a string, evaluates it as a expression, and returns the result of the expression.
Also, the way your loop is constructed, the third iteration will have the ‘checkending’ variable containing the value ‘persistent.ending_123’ because you’re appending numbers to the previous result, not building ‘checkending’ from scratch.
Thanks for the info, I didn't know the eval thing, should be useful in the future. As for the other thing you mention, I don't think it's a problem as the last line in the loop reset the variable to "persistent.ending_". I have done similar things and they worked (they didn't involve that kind of comparisons, so the eval part wasn't a problem). Anyway, the problem is a "while" loop can't be used inside a screen, but in a label I think it should work.
Comunidad Ren'Py en español (Discord)
Honest Critique

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen

#6 Post by Matalla »

I'm sorry to reopen this thing, but I have encountered a problem.

I implemented the system and all seems to work, I checked the several things that the game tracks and everything worked fine. But...

I have in the preferences screen a button to delete the persistent data, this is the code:

Code: Select all

imagebutton auto _("gui/button/bt_reset_%s.png") focus_mask True action Confirm(_("Are you sure?"), persistent._clear, Hide ('confirm')) alt _("Reset to default")
When I delete de data everything seems fine, but if I return to the progress screen, all hell breaks lose:

Code: Select all

I'm sorry, but an uncaught exception occurred.

While running game code:
  File "game/progress.rpy", line 45, in execute
    screen progress():
  File "game/progress.rpy", line 45, in execute
    screen progress():
  File "game/progress.rpy", line 51, in execute
    use game_menu(_("Progreso"), scroll="viewport"):
  File "game/screens.rpy", line 434, in execute
    screen game_menu(title, scroll=None, yinitial=0.0):
  File "game/screens.rpy", line 434, in execute
    screen game_menu(title, scroll=None, yinitial=0.0):
  File "game/screens.rpy", line 443, in execute
    frame:
  File "game/screens.rpy", line 446, in execute
    hbox:
  File "game/screens.rpy", line 452, in execute
    frame:
  File "game/screens.rpy", line 455, in execute
    if scroll == "viewport":
  File "game/screens.rpy", line 457, in execute
    viewport:
  File "game/screens.rpy", line 466, in execute
    vbox:
  File "game/screens.rpy", line 467, in execute
    transclude
  File "game/progress.rpy", line 51, in execute
    use game_menu(_("Progreso"), scroll="viewport"):
  File "game/progress.rpy", line 53, in execute
    hbox:
  File "game/progress.rpy", line 56, in execute
    vbox:
  File "game/progress.rpy", line 248, in execute
    $ counterendings = persistent.endings.count(True)
  File "game/progress.rpy", line 248, in <module>
    $ counterendings = persistent.endings.count(True)
AttributeError: 'NoneType' object has no attribute 'count'

-- Full Traceback ------------------------------------------------------------

Full traceback:
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in script
    python hide:
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\ast.py", line 896, in execute
    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\python.py", line 1929, in py_exec_bytecode
    exec bytecode in globals, locals
  File "renpy/common/_layout/screen_main_menu.rpym", line 28, in <module>
    python hide:
  File "renpy/common/_layout/screen_main_menu.rpym", line 35, in _execute_python_hide
    ui.interact()
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\ui.py", line 289, in interact
    rv = renpy.game.interface.interact(roll_forward=roll_forward, **kwargs)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 2690, in interact
    repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, **kwargs)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 3074, in interact_core
    root_widget.visit_all(lambda i : i.per_interact())
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 534, in visit_all
    d.visit_all(callback, seen)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 534, in visit_all
    d.visit_all(callback, seen)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 534, in visit_all
    d.visit_all(callback, seen)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 534, in visit_all
    d.visit_all(callback, seen)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\screen.py", line 424, in visit_all
    callback(self)
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\core.py", line 3074, in <lambda>
    root_widget.visit_all(lambda i : i.per_interact())
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\screen.py", line 434, in per_interact
    self.update()
  File "C:\PortableApps\renpy-7.0.0-sdk\renpy\display\screen.py", line 619, in update
    self.screen.function(**self.scope)
  File "game/progress.rpy", line 45, in execute
    screen progress():
  File "game/progress.rpy", line 45, in execute
    screen progress():
  File "game/progress.rpy", line 51, in execute
    use game_menu(_("Progreso"), scroll="viewport"):
  File "game/screens.rpy", line 434, in execute
    screen game_menu(title, scroll=None, yinitial=0.0):
  File "game/screens.rpy", line 434, in execute
    screen game_menu(title, scroll=None, yinitial=0.0):
  File "game/screens.rpy", line 443, in execute
    frame:
  File "game/screens.rpy", line 446, in execute
    hbox:
  File "game/screens.rpy", line 452, in execute
    frame:
  File "game/screens.rpy", line 455, in execute
    if scroll == "viewport":
  File "game/screens.rpy", line 457, in execute
    viewport:
  File "game/screens.rpy", line 466, in execute
    vbox:
  File "game/screens.rpy", line 467, in execute
    transclude
  File "game/progress.rpy", line 51, in execute
    use game_menu(_("Progreso"), scroll="viewport"):
  File "game/progress.rpy", line 53, in execute
    hbox:
  File "game/progress.rpy", line 56, in execute
    vbox:
  File "game/progress.rpy", line 248, in execute
    $ counterendings = persistent.endings.count(True)
  File "game/progress.rpy", line 248, in <module>
    $ counterendings = persistent.endings.count(True)
AttributeError: 'NoneType' object has no attribute 'count'

Windows-8-6.2.9200
Ren'Py 7.2.1.457
Las Tres Gracias 1.0
Sat Mar 23 23:00:10 2019
Any idea about how to fix this?
Comunidad Ren'Py en español (Discord)
Honest Critique

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

Re: Checking persistent data with a loop in a screen

#7 Post by Alex »

Well, since you've deleted persistent data persistent.endings no longer existed...
So you need to set it to default persistent.endings=[False, False, etc.] somehow.
Like, I don't know, make a label with for loop to append 46 False values to a list, and call it when deleting persistent.
https://www.renpy.org/doc/html/screen_actions.html#Call

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen

#8 Post by Matalla »

Alex wrote: Sat Mar 23, 2019 7:40 pm Well, since you've deleted persistent data persistent.endings no longer existed...
So you need to set it to default persistent.endings=[False, False, etc.] somehow.
Like, I don't know, make a label with for loop to append 46 False values to a list, and call it when deleting persistent.
https://www.renpy.org/doc/html/screen_actions.html#Call
And should I have to do that with every aspect the game keep tracks of? Like a loop for making a list for 46 endings, another one for 48 dreams, another for 18 movies, etc.? And call all of them when I reset the persistent data?

I checked a simpler solution in the blank project with your code and it seems to work. I moved the content of the init python block to a python block inside the screen (also the variables counters). It keeps the count correctly and gives no errors when deleting persistent data. It looks like this:

Code: Select all

vbox:
	python:
		if not persistent.endings:
			persistent.endings = []
			for end in range (46):
				persistent.endings.append(False)
            
	$ counterendings = persistent.endings.count(True)
	$ all_endings = len(persistent.endings)
                
	label "Endings"
	text "[counterendings] of [all_endings] endings"

Are there any unexpected side effects with this method I should be aware of?
Comunidad Ren'Py en español (Discord)
Honest Critique

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

Re: Checking persistent data with a loop in a screen

#9 Post by Alex »

Matalla wrote: Sat Mar 23, 2019 8:42 pm ...
The issue is that you delete persistent variables completely (not reset them to default values).
If you really need to do so (set all the texts as unreaded, all the images as unseen etc.) then try to add an action that sets the default values.

Code: Select all

init python:
    def default_persistent_vars():
        global persistent
        # set all the desirable persistent vars the way like that
        if not persistent.endings:
            persistent.endings = []
            for end in range (5):
                persistent.endings.append(False)
                
    # check if we need to set values for the first time
    if not persistent.endings:
        default_persistent_vars()

screen ends():
    textbutton "Clear" action Confirm(_("Are you sure?"), [persistent._clear, Function(default_persistent_vars)], Hide ('confirm')) align(0.95, 0.05)
    
    $ unlocked = persistent.endings.count(True)
    $ all_ends = len(persistent.endings)
    text "Unlocked [unlocked] ending(s) out of [all_ends]." align(0.5,0.05)

https://www.renpy.org/doc/html/screen_a ... l#Function

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen

#10 Post by Matalla »

Alex wrote: Sun Mar 24, 2019 9:30 am The issue is that you delete persistent variables completely (not reset them to default values).
If you really need to do so (set all the texts as unreaded, all the images as unseen etc.) then try to add an action that sets the default values.

Code: Select all

init python:
    def default_persistent_vars():
        global persistent
        # set all the desirable persistent vars the way like that
        if not persistent.endings:
            persistent.endings = []
            for end in range (5):
                persistent.endings.append(False)
                
    # check if we need to set values for the first time
    if not persistent.endings:
        default_persistent_vars()

screen ends():
    textbutton "Clear" action Confirm(_("Are you sure?"), [persistent._clear, Function(default_persistent_vars)], Hide ('confirm')) align(0.95, 0.05)
    
    $ unlocked = persistent.endings.count(True)
    $ all_ends = len(persistent.endings)
    text "Unlocked [unlocked] ending(s) out of [all_ends]." align(0.5,0.05)

https://www.renpy.org/doc/html/screen_a ... l#Function
Thanks Alex, but with the modification I mentioned in the previous post it works wonderfully, there's no need to add an action as the code regenerates the lists when entering the screen if they are empty.

I have done every evil thing I can imagine with the persistent data and it seems bulletproof, while when it was still in an init block caused some other problems, like if I edited the code, adding or removing elementes from a category, gave errors, forcing me to delete manually the persistent data anyway. I am sure there's a reason to put that code in an init block, but for the use I do of this, works better the other way.

Oh, and I believe the code for reseting the seen text and images is persistent._clear(progress=True), with the code I posted it just delete the persistent variables. I'll change to that for the release, but right now, it's better to keep it this way for testing.
Comunidad Ren'Py en español (Discord)
Honest Critique

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

Re: Checking persistent data with a loop in a screen

#11 Post by Alex »

Matalla wrote: Sun Mar 24, 2019 11:03 am ...I have done every evil thing I can imagine with the persistent data and it seems bulletproof, while when it was still in an init block caused some other problems, like if I edited the code, adding or removing elementes from a category, gave errors, forcing me to delete manually the persistent data anyway. I am sure there's a reason to put that code in an init block, but for the use I do of this, works better the other way. ...
Mmm, this might work fine if you show this screen right after deleting persistent data, but if you delete persistent and won't show this screen to set value for persistent.endings list, you'll get error while trying to set one of items in nonexistent list to True (when player has reached the ending)...

User avatar
Matalla
Veteran
Posts: 202
Joined: Wed Mar 06, 2019 6:22 pm
Completed: Max Power and the Egyptian Beetle Case, The Candidate, The Last Hope, El cajón del viejo escritorio, Clementina y la luna roja, Caught in Orbit, Dirty Business Ep 0, Medianoche de nuevo, The Lost Smile
itch: matalla-interactive
Location: Spain
Contact:

Re: Checking persistent data with a loop in a screen

#12 Post by Matalla »

Alex wrote: Sun Mar 24, 2019 2:07 pm
Matalla wrote: Sun Mar 24, 2019 11:03 am ...I have done every evil thing I can imagine with the persistent data and it seems bulletproof, while when it was still in an init block caused some other problems, like if I edited the code, adding or removing elementes from a category, gave errors, forcing me to delete manually the persistent data anyway. I am sure there's a reason to put that code in an init block, but for the use I do of this, works better the other way. ...
Mmm, this might work fine if you show this screen right after deleting persistent data, but if you delete persistent and won't show this screen to set value for persistent.endings list, you'll get error while trying to set one of items in nonexistent list to True (when player has reached the ending)...
Nope, I checked it because I also thought it could be a problem (I was prepared to make the delete persistent button force to go to the progress screen) but it works. I don't know how it works because I still don't fully understand the code, but it does.
Comunidad Ren'Py en español (Discord)
Honest Critique

Post Reply

Who is online

Users browsing this forum: Google [Bot]