Save-proof randomness

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
Kinsman
Regular
Posts: 130
Joined: Sun Jul 26, 2009 7:07 pm
Location: Fredericton, NB, Canada
Contact:

Save-proof randomness

#1 Post by Kinsman » Sun Jan 12, 2014 6:21 pm

Did you know that if you save and then load a Ren'Py game, the random generator changes internally, and you'd get different numbers than you would if you didn't save and load? This will happen even if you use renpy.random.seed() to guarantee a certain sequence.

If you don't want that to happen; for instance, if you want to keep the player from savescumming an RPG scene or minigame - then use the code below.

It changes how renpy.random behaves slightly. Your Ren'Py game will continually save the internal state of the random engine as a game variable, so that it can survive a save/load cycle.

Edit: Made an update to include the seed() function.

Code: Select all

# options.rpy is a good place for this code
init python:

    _oldrandom = renpy.random.random
    _oldseed = renpy.random.seed
   
    renpy.random.seed(0)
    _globalrandstate = renpy.random.getstate()
    
    def _newrandom():
        global _globalrandstate
       
        renpy.random.setstate(_globalrandstate)
        rv = _oldrandom()
        _globalrandstate = renpy.random.getstate()
       
        return rv
        
    def _newseed(value):
        global _globalrandstate
            
        renpy.random.setstate(_globalrandstate)
        _oldseed(value)
        _globalrandstate = renpy.random.getstate()
   
    renpy.random.random = _newrandom
    renpy.random.seed = _newseed
Last edited by Kinsman on Mon Jan 13, 2014 2:45 pm, edited 1 time in total.
Flash To Ren'Py Exporter
See the Cookbook thread

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

Re: Save-proof randomness

#2 Post by xela » Mon Jan 13, 2014 3:53 am

I couldn't get your code to work. Calling renpy.random.random() still generated new random every time I've loaded the game.

This will prevent save/load exploits that were based on random generation while still generating new sequence and therefor new gaming experience on every gamestart:

Code: Select all

label start:
    $ _seed = renpy.random.random()
    $ renpy.random.seed(_seed)

label after_load:
    $ renpy.random.seed(_seed)  
Not entirely sure that this is a cookbook material.
Like what we're doing? Support us at:
Image

Kinsman
Regular
Posts: 130
Joined: Sun Jul 26, 2009 7:07 pm
Location: Fredericton, NB, Canada
Contact:

Re: Save-proof randomness

#3 Post by Kinsman » Mon Jan 13, 2014 2:41 pm

xela wrote:I couldn't get your code to work. Calling renpy.random.random() still generated new random every time I've loaded the game.
Well, making sure you've set the seed is always important if you want predictable and repeatable random numbers.

This example script should help give you an idea of what the code is for.

Code: Select all

# The game starts here.
label start:

    "Let's test randomness."

    # Example seed
    $renpy.random.seed(0)
    
    # Ten random rolls
    $i = 1
    while i <= 10:
        $die6 = renpy.random.randint(1,6)
        "Roll #[i] was [die6]."
        $i += 1

    return
If you run this code a few times, you should get a sense of what numbers it's going to generate. But if you interrupt the run halfway through, either with a save/load cycle or using Shift+R, you'll end up with different numbers, since the state of the random generator changes with a new load.

If you then install the code above, and try it again, you'll find that the numbers remain the same when you do a save/load halfway through.

But yes, if you didn't include the seed() statement in the script, you're always going to get random numbers with each new run, since Ren'Py will use your computer's clock as a seed number when the game starts.
This will prevent save/load exploits that were based on random generation while still generating new sequence and therefor new gaming experience on every gamestart:

Code: Select all

label start:
    $ _seed = renpy.random.random()
    $ renpy.random.seed(_seed)

label after_load:
    $ renpy.random.seed(_seed)  
That code's got a problem, though - when you reload the game, the first rolls right afterwards are always going to be the same throughout the game's run, since you've gone back to the beginning of the seed's sequence.
Not entirely sure that this is a cookbook material.
I'll need to add some lines so that the seed function is protected in the same way as the random function. That way, a user doesn't have to remember to set the seed themselves.
Flash To Ren'Py Exporter
See the Cookbook thread

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

Re: Save-proof randomness

#4 Post by xela » Mon Jan 13, 2014 5:45 pm

My bad and you're right, I should have caught that...

I wanted to implement something like this in my game. Trouble is that your code or two or three variations of it that I've tried, kill after_load label (I really need that as well).

Even if after_load label looks like this:

Code: Select all

label after_load:
    $ pass
game will only load to the beginning of start label. I have no clue why :(

Maybe I'll figure it out when I have more time to tinker with this. Or just use lists of pregenerated randoms and pop from them like I've originally planned.

In any case, well done :)
Like what we're doing? Support us at:
Image

Kinsman
Regular
Posts: 130
Joined: Sun Jul 26, 2009 7:07 pm
Location: Fredericton, NB, Canada
Contact:

Re: Save-proof randomness

#5 Post by Kinsman » Mon Jan 13, 2014 6:26 pm

Make sure you put a 'return' at the end of your after_load code, so that Ren'Py knows to go back to where you saved from.
Flash To Ren'Py Exporter
See the Cookbook thread

User avatar
KimiYoriBaka
Miko-Class Veteran
Posts: 636
Joined: Thu May 14, 2009 8:15 pm
Projects: Castle of Arhannia
Contact:

Re: Save-proof randomness

#6 Post by KimiYoriBaka » Mon Jan 13, 2014 6:57 pm

if you want to keep the player from savescumming an RPG scene or minigame
while I can understand using this type of code if you want to set up a different course for your story each play-through or something, the reasons you mentioned are actually really bad. I would advise against that type of design, as it will often give the player the feeling as if the game is pre-set against them. remember that the times when a player needs to reload are generally when they're losing, and if the same thing happens again after they reload that means the numbers they thought were supposed to be random suddenly are giving far more bad results.

Kinsman
Regular
Posts: 130
Joined: Sun Jul 26, 2009 7:07 pm
Location: Fredericton, NB, Canada
Contact:

Re: Save-proof randomness

#7 Post by Kinsman » Mon Jan 13, 2014 7:47 pm

That's fair enough. When I made this code, I had some very specific reasons for it (that actually is related to story-based randomness) but I shrugged when it came to writing more general reasons why anyone would want it.

I figure anyone who's going to use this code will be someone specifically looking for it.
Flash To Ren'Py Exporter
See the Cookbook thread

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

Re: Save-proof randomness

#8 Post by xela » Tue Jan 14, 2014 3:25 pm

Kinsman wrote:Make sure you put a 'return' at the end of your after_load code, so that Ren'Py knows to go back to where you saved from.
Thanks you, works purrrfect now :)

I narrowed it down to this (since I'll be using after_load anyway):

Code: Select all

init python:

    _oldrandom = renpy.random.random
   
    def _newrandom():
        global _state
        
        rv = _oldrandom()
        _state = renpy.random.getstate()
        
        return rv
       
    renpy.random.random = _newrandom
    
label after_load:
    $ renpy.random.setstate(_state)
    return

label start:
    $ renpy.random.seed(_oldrandom())
KimiYoriBaka wrote:
actually really bad
It actually isn't. In Civ5, you are simply given a choice at game start, if you wrap the whole thing in a function with an "if" in-front of it: player has a choice. In Civ5 many consider enabling random seed a form of cheating (similar to summoning nuclear bot to stoneage). There is no general rule here, it's a preference of developer and player if given the choice...
Like what we're doing? Support us at:
Image

Post Reply

Who is online

Users browsing this forum: No registered users