[SOLVED] Strange variable behaviour

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.
Message
Author
User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

[SOLVED] Strange variable behaviour

#1 Post by Milkymalk »

This is strange. I watched the variable lastspell through the console when something didn't work, and this happened:

Code: Select all

        def cast_spell(self, sp):
            global lastspell
            self.setactivecharacter(1 - self.activecharacter) 
            lastspell = sp.name
            renpy.restart_interaction()

        def setactivecharacter(self, char):
            if char != self.activecharacter:
                renpy.hide_screen('mainparty_spells')
                self.activecharacter = char
                renpy.show_screen('mainparty_spells', self)
This changes lastspell properly.

Code: Select all

        def cast_spell(self, sp)
            global lastspell
            self.setactivecharacter(1 - self.activecharacter) 
            lastspell = sp.name
            renpy.restart_interaction()

        def setactivecharacter(self, char):
            if char != self.activecharacter:
                renpy.call('switcheroo', self, char)

label switcheroo (bat, char):
    hide screen mainparty_spells
    pause 0.3
    $ bat.activecharacter = char
    show screen mainparty_spells(bat)
    return
This doesn NOT change lastspell when cast_spell is called!

I would just ditch the switcheroo label, but the screen mainparty_spells has on show and on hide transforms, and the on hide are never seen if I don't use a delay, and I can't use renpy.pause because:
File "game/script.rpy", line 316, in setactivecharacter
renpy.pause(0.3)
Exception: Cannot start an interaction in the middle of an interaction, without creating a new context.
I was surprised it worked after jumping to a new label... anyway, what is happening here?
Last edited by Milkymalk on Fri Jul 25, 2014 10:23 am, edited 1 time in total.
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Strange variable behaviour

#2 Post by Asceai »

Mind showing us how cast_spell is called?

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#3 Post by Milkymalk »

Sure:

Code: Select all

screen mainparty_spells (bat):
    zorder 5
    frame at partyspells:
        xsize 200
        ypos 100
        $ spellcurry = renpy.curry(bat.cast_spell)
        vbox:
            for spell in range(len(bat.playerparty[bat.activecharacter].spells)):
                frame:
                    xsize 0.9
                    ysize 100
                    ypadding 20
                    vbox:
                        textbutton bat.playerparty[bat.activecharacter].spells[spell].name
                        hbox:
                            for i in range(8):
                                if bat.playerparty[bat.activecharacter].spells[spell].cost[i] > 0:
                                    for j in range(bat.playerparty[bat.activecharacter].spells[spell].cost[i]):
                                        vbox xsize 29:
                                            button:
                                                background im.Scale(bat.ballpic[i],29,29)
                        hbox:
                            if bat.playerparty[bat.activecharacter].spells[spell].has_mana(bat.manapool):
                                textbutton 'Use' action spellcurry(bat.playerparty[bat.activecharacter].spells[spell])  # <------ here
                            else:
                                textbutton 'Use'
                            textbutton 'Info' action Show('spell_info', bat=bat, spell=bat.playerparty[bat.activecharacter].spells[spell])
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Strange variable behaviour

#4 Post by Asceai »

Try changing renpy.call to renpy.call_in_new_context. I don't think anything after the 'call' is running.

EDIT: Hmm, the context switch would probably screw up your transitions though, since it should hide the screen I think?

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#5 Post by Milkymalk »

Yes, already tried that. It switches to a completely different canvas if I do that.

Also, another strange thing, more of a Python behaviour.

Code: Select all

    class Test:
        def cast_spell(self, sp):
            effectbuffer = sp.effects
            while len(effectbuffer) > 0:
                currenteffect = effectbuffer.pop()
            [...stuff...]
 
I encountered a bug(?) where this actually changes the .effects of the argument given as sp and I can't wrap my head around that... this just puzzles me. Maybe it has to do with the fact that the function was also curry'd and invoked via a button.

I'm not sure yet how to call a function so that it actually changes a variable, so that it does not change a variable, and so that it changes a variable it does not even touch at all... :?: :?: :?:
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Strange variable behaviour

#6 Post by xela »

Code: Select all

effectbuffer = sp.effects * 1
will do.

You can also simply write

Code: Select all

while effectbuffer:
and the preferred python in this case would prolly be:

Code: Select all

for effect in sp.effects:
    # do stuff
I'm not sure yet how to call a function so that it actually changes a variable, so that it does not change a variable, and so that it changes a variable it does not even touch at all... :?: :?: :?:
You might as well have said "Bing Bibbly Squab", clarify or go through a Python guide/documentation that might be of use to you.
Like what we're doing? Support us at:
Image

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#7 Post by Milkymalk »

So, when I write

Code: Select all

effectbuffer = sp.effects
the two variables are equivalent and changing one changes the other, and changing a variable that is part of the function arguments changes the variable the function was called with?
If so, no need to clarify, that was ecactly the answer I was looking for :-)

Thanks, I'm not really good with Python syntax. for ... in confuses me a little so I sort of stay away from it ^^;
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Strange variable behaviour

#8 Post by xela »

Milkymalk wrote:So, when I write

Code: Select all

effectbuffer = sp.effects
the two variables are equivalent and changing one changes the other, and changing a variable that is part of the function arguments changes the variable the function was called with?
If so, no need to clarify, that was ecactly the answer I was looking for :-)
Yes and no, you got the behavior right but not the process. In a nutshell:

a = range(10) # A list of integers from 0 to 9 is created and assigned to label "a" (NOTHING to do with Ren'Py labels). In RenPy it will be on "global" or "store" namespace unless specified otherwise.
b = a # Another label "b" is assigned to a range list object we created earlier.
a = list() # a new list object was created and assigned label a. b is still aligned to previous list.

func(b) # will take whatever is assigned to b (the range list in our case) and pass that as argument.

so if inside a function you have:

func_variable = b # you will simply assign yet another label to that range list we created with the first line of code.
===========================
func_variable will be unsigned from the list the moment function routine is finished (because all functions and methods have their own namespaces).

When you delete the last reference (label) to the object, it will be destroyed and removed from the program by automatic garbage collector.

I suppose the most important thing to understand that when you assign b to a, nothing new is being created, you just add a new reference/label to already existing object.
Like what we're doing? Support us at:
Image

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#9 Post by Milkymalk »

Thank you, that clears it up! I guess I will have to stop thinking of "a = b" as "the contents of b are copied to a", but instead as "a is another name for what b is a name for".

But another question:

Why does this change the sp outside the function:

Code: Select all

    class Test:
        def cast_spell(self, sp):
            effectbuffer = sp.effects
            while len(effectbuffer) > 0:
                currenteffect = effectbuffer.pop()
            [...stuff...]
 
and this does not change amount outside the function?

Code: Select all

        def change_mana(self, target, change_to, amount, position):
            if position == 1:
                step = 1
            elif position == 2:
                step = -1
            deletemana = [ ]
            for i in range(15):
                deletemana.append(False)                    
            for i in range(15):
                if amount > 0 and (self.balls_color[7-(7-i)*step] == target or target == -1) and not self.ballselected[7-(7-i)*step]:
                    if change_to == -3:
                        deletemana[7-(7-i)*step] = True
                        amount -= 1
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Strange variable behaviour

#10 Post by Asceai »

Mutable types have reference semantics, immutable types have value semantics. sp is mutable, amount is immutable

a = b copies b if b is immutable, but creates a reference to b if b is mutable (well, creates a reference to whatever b is a reference to, because mutable variables are always references. So if you do 'b = c' later, a will still be whatever b was before, even though b is pointing at c now.)

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#11 Post by Milkymalk »

That explains a lot, though I find it confusing that variables behave in different ways.

Back to my initial problem:
This is strange. I watched the variable lastspell through the console when something didn't work, and this happened:

Code: Select all

        def cast_spell(self, sp):
            global lastspell
            self.setactivecharacter(1 - self.activecharacter)
            lastspell = sp.name
            renpy.restart_interaction()

        def setactivecharacter(self, char):
            if char != self.activecharacter:
                renpy.hide_screen('mainparty_spells')
                self.activecharacter = char
                renpy.show_screen('mainparty_spells', self)
This changes lastspell properly.

Code: Select all

        def cast_spell(self, sp)
            global lastspell
            self.setactivecharacter(1 - self.activecharacter)
            lastspell = sp.name
            renpy.restart_interaction()

        def setactivecharacter(self, char):
            if char != self.activecharacter:
                renpy.call('switcheroo', self, char)

label switcheroo (bat, char):
    hide screen mainparty_spells
    pause 0.3
    $ bat.activecharacter = char
    show screen mainparty_spells(bat)
    return
This doesn NOT change lastspell when cast_spell is called!
I've come to a point where this is really annoying. I will go into detail a little more.

TL;DR:
I'm doing all this while in a call for an empty screen so I have a pseudo-loop that waits for something, so I can't call anything else without the "switcheroo" trick, but it messes up variables I change during switcheroo. call_in_new_context on the other hand messes up the visuals.

Long explanation:
I'm making a battle engine aka minigame that involves status screens (no problem) and called screens that wait for a specific input from the player (problem, see below).
To keep the script from advancing until the combat is over, I use:

Code: Select all

screen battleround:
    zorder 1

label combatstart:
    while True:                          # <-- because "switcheroo" makes "battleround" close
        call screen battleround
I have to do it this way because

Code: Select all

label combatstart:
    while True:                      
        jump combatstart
hangs the game. This unfortunately means that I can't call anything else (create a new interaction) without call_in_new_context, which would screw up my screen, and that even includes renpy.pause because apparently that creates a new interaction. Through trial-and-error, I found out that if I call a label from within a function, I CAN use pause and screen calls, BUT any variables I change during the label call remain unchanged, probably because this label call somehow creates a new (pseudo?)context.

So I either need a way to keep my script from advancing without resorting to calling an empty screen (HIGHLY preferred), or another way to make a call from within the call that doesn't affect variable interaction.

EDIT:

Code: Select all

        def cast_spell(self, sp):
            effectbuffer = sp.effects
            for i in range(len(effectbuffer)):
                currenteffect = effectbuffer[i]
                if currenteffect[0] == 0:   
                    self.directdamagespell(currenteffect[1], currenteffect[2], currenteffect[3])
                if currenteffect[0] == 4:     
                    self.change_mana(currenteffect[1], currenteffect[2], currenteffect[3], currenteffect[4])
            self.kill_selectedballs()   
            self.update_pool(-1, 0)
            self.setactivecharacter(1 - self.activecharacter) 
            renpy.restart_interaction()
            
        def directdamagespell(self, type, amount, target):
            if target == 1:                                                                
                renpy.call('gettarget', self, 0)
                global aa
                aa = _return
                selectedtarget = _return
                selectedtarget.takehit(type, amount)
                renpy.restart_interaction()

label gettarget(bat, party):
    call screen selectcharacter(bat, 0)
    return _return
Usually, when called from the script without any tricks, the screen selectcharacter works fine; watched variables changed their value properly so the problem is not there.
But not only did aa (a control variable for bugtesting) not change value, but anything after "elf.directdamagespell(currenteffect[1], currenteffect[2], currenteffect[3])" in cast_spell(self, sp) was not executed; the whole

Code: Select all

            self.kill_selectedballs()   
            self.update_pool(-1, 0)
            self.setactivecharacter(1 - self.activecharacter) 
            renpy.restart_interaction()
that works fine if "currenteffect[0] == 4" is ignored. It probably has also to do with calling the label because I don't call a label in self.change_mana.

Tell me if I should take this to a new thread because it's more of a "how do I do this" instead of a "why does this happen".
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Strange variable behaviour

#12 Post by xela »

Before I read the following post:
Asceai wrote:a = b copies b if b is immutable, but creates a reference to b if b is mutable
I don't buy this (at all), in python a = b will never copy anything, mutability is irrelevant.
Like what we're doing? Support us at:
Image

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Strange variable behaviour

#13 Post by xela »

Milkymalk wrote:though I find it confusing that variables behave in different ways.
They don't, he's just trying to confuse you :twisted:
Milkymalk wrote:Back to my initial problem:
Why is swicherloo a label? You have a really weird setup there making it's logic hard to follow.

You can try something like:

Code: Select all

label battle:
    python:
        while 1:
            result = ui.interact()
            if result[0] == "any_string":
                # Do something.

screen battle:
    textbutton "Meow":
        action Return(["any_string"])
Or just do the same in Python class/function. You can obviously just/call labels in similar way but that's likely to be more confusing. I couldn't understand the main problem from your post other than asking about how to loop stuff properly.
Like what we're doing? Support us at:
Image

Asceai
Eileen-Class Veteran
Posts: 1258
Joined: Fri Sep 21, 2007 7:13 am
Projects: a battle engine
Contact:

Re: Strange variable behaviour

#14 Post by Asceai »

xela wrote:Before I read the following post:
Asceai wrote:a = b copies b if b is immutable, but creates a reference to b if b is mutable
I don't buy this (at all), in python a = b will never copy anything, mutability is irrelevant.
It's a simplified explanation. I had a more detailed explanation that went into this in the edit window but then I lost the tab >_> Still, the concept of value semantics vs reference semantics is a good model for explaining it to people who aren't familiar with python's arcaneness, since those concepts are popular in other languages.

User avatar
Milkymalk
Miko-Class Veteran
Posts: 760
Joined: Wed Nov 23, 2011 5:30 pm
Completed: Don't Look (AGS game)
Projects: KANPEKI! ★Perfect Play★
Organization: Crappy White Wings
Location: Germany
Contact:

Re: Strange variable behaviour

#15 Post by Milkymalk »

As long as it works when I use it like that, I'm fine with simplyfied explanations :-)

@Xela:
I think ui.interact is exactly what I needed, I will try to change my engine to processing all clicks through that in a main loop and only show the screens instead of calling them. This will be a lot of work, so I won't be back to this thread for a few days I think. I will let you know how it went.
Crappy White Wings (currently quite inactive)
Working on: KANPEKI!
(On Hold: New Eden, Imperial Sea, Pure Light)

Post Reply

Who is online

Users browsing this forum: Majestic-12 [Bot]