More robust random number generator

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
Askewnotion
Newbie
Posts: 5
Joined: Mon Jul 09, 2018 5:17 pm
Projects: Never a Queen
Location: Pittsburgh, PA, USA
Contact:

More robust random number generator

#1 Post by Askewnotion »

Hi all!

I've been looking into this script as a potential someone for VN have creation, but I'm a little lost on what scripting is available since I'm a more traditional Python user, with most of my work being done with numpy.

I am looking for skills to be pulled from a beta distribution where there defining shape parameters are determined by the NPC and PC for the alpha and beta, respectively.

From what it looks like, renpy can only allow uniform distributions and I can't seem to implement numpy.

Is there some sort of document that has all of the mathematical formulas that are capable under renpy, or does someone have any guides on how to fully implement numpy?

Thanks! If I can get something running, I'll be more than willing to write a tutorial on dynamic probability structures.

User avatar
Askewnotion
Newbie
Posts: 5
Joined: Mon Jul 09, 2018 5:17 pm
Projects: Never a Queen
Location: Pittsburgh, PA, USA
Contact:

Re: More robust random number generator

#2 Post by Askewnotion »

Well, I got a little impatient and wrote some code so that computes a cdf for Beta probabilities. I'll include the code and some explanation. Hopefully this can help someone in the future. Also, if there is someway to implement LaTeX style equations in here that someone knows of, that would be awesome to include in the comments to make this a little easier to understand.

If you want to get a little bit of background on a Beta distribution, check out the wikipedia link here; https://en.wikipedia.org/wiki/Beta_distribution.

There are three functions used in this set-up;
  1. A function to pull a nCr (combinations of choose 'r' of 'n' items) value when we do some integration of the incomplete beta equation.
  2. A function to find B(a,b) in the form of factorials which were converted form their gamma functions.
  3. A function that does the integral of the incomplete beta equation.
For the function in item 1 ('ncr' in the code), it finds which is smaller of r or (n-r), then computes the left, non-truncated portion of n! and computers that over r! or (n-r)!, whichever is smaller. This is the equivalent of using the formula n!/(r!(n-r)!, though with a quicker computation for large factorials.

For the function in item 2 ('betas' in the code), it determines the formula B(a,b) = [G(a)G(b)]/G(a+b) where G is the gamma function. It uses a shortcut where G(z)=(z-1)! and then computes the factorials normally.

For the function in item 3 ('betaInt' in the code), it takes the very specific integral of the incomplete beta function given as B(x; a,b)=INT^x_0 t^(a-1)*(1-t)^(b-1)dt. This integral follows a specific pattern of having a number of x values with an index of 'a' and there will be a number of x's, incrementing by 1 each time after the first, of 'b'. For example, if a=8 and b=4. There will be an ?x^8, ?x^9, ?x^10, ?x^11, where the question marks represent the unknown coefficients. To determine the coefficients, we use Pascal's Triange, which is a derivative of our nCr function. We then divide the nCr value of each step by the index of the variable, and we are able to get a quick and dirty derivative by stepping through each variable.

Note that you have to import two libraries to get this to work;

Code: Select all

init python:
    import math
    import operator
    
    ## Finds the combinations available to choose r items from n choices
    def ncr(n, r):
        r = min(r, n-r)
        numer = reduce(operator.mul, xrange(n, n-r, -1), 1)
        denom = reduce(operator.mul, xrange(1, r+1), 1)
        return numer//denom
      
    ## Determines the values from our simplified gamma functions from B(a,b). a is our alpha shape variable and b is our beta shape variable. Note that a and b can only be passed as integers.
    def betas(a,b):
        return float(math.factorial(a-1))*math.factorial(b-1)/math.factorial(a+b-1)
        
    ## Takes the specific integral of the incomplete beta function. a is our alpha shape variable, b is our beta shape variable, x is the random probability chosen.
    def betaInt(a,b,x):
        sign=1
        total=0.0
        for i in range(a-1,a+b-1):
            if (sign % 2 == 1):
                total += float(ncr(b-1,sign-1))*(1.0/(i+1))*math.pow(x,i+1)
            else:
                total -= float(ncr(b-1,sign-1))*(1.0/(i+1))*math.pow(x,i+1)
            sign += 1
        return total/betas(a,b)
        
label start:

    $core=betaInt(4,8,0.7)

    "Around 0.995709106; [core]"

    $core=betaInt(8,4,0.7)

    "Around 0.5695623388; [core]"
You can feel free to check the values here to make sure the code is working well for you; https://www.danielsoper.com/statcalc/ca ... aspx?id=94

You may be asking yourself, "Why would I ever need this?!?" Well, the beta probability is pretty good at giving you a weighted curve of success.

Let us consider something where you want to check the opposition between two skills. Under the normal renpy random number distributions, you will get some number between 0 through 1, with no consideration to any skill. This is the uniform distribution I mentioned in the first post. If you were to map the cdf of this function, it would be a perfectly horizontal line.

Now we can take this renpy generated random number and apply our weights. Look at the example above in the code. You will notice I have x=0.7. This is assuming that 0.7 is what we got from our renpy random number generation. Let's have our skill for the person we are interacting with be our alpha shape variable 'a' and our skill for our PC be the beta shape variable 'b'. You will notice that the skill goes down when our opponent has the advantage (a>b) and our number goes up when we have the advantage (b>a).

Note that for this code to work, a>0 and b>0 and a and b must be natural numbers. The best values are between 1 and 5 as your curve will not be "smashed" too much in the middle and have "long" tails. You can go between 1 and 10, though make sure to have increments above 5 be more rare the closer they are to 10.

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: More robust random number generator

#3 Post by Remix »

Marvelous work, if a little outside my own avenues of study.
I can certainly picture a few areas where it could help balance otherwise pure random parts of a game. A lot nicer than just patching in an offset to the random result.

As you no doubt discovered, numpy is built on a C core (for fast math calculations) and thus is not easy to implement within Ren'Py; you'd have to compile the binaries within the Ren'Py core and make allowance for all dependencies and platforms you needed.

Think of Ren'Py as an abstraction layer that separates pretty straight forward script (Ren'Py screens, labels, flow and Python) from the end visual display.
Using Ren'Py we can basically ignore the gap between writing "show eileen happy" and have her displayed within a game, allowing our focus to remain wholly in the 'script' area.
Being highly targeted at artists and authors, it is designed to be easy enough for a non-technical minded person to adapt to as well as leaving enough scope for those who want to push it further.

I hope you continue to explore Ren'Py even without numpy there to help and I look forward to further snippets you offer to the community.
Frameworks & Scriptlets:

User avatar
Askewnotion
Newbie
Posts: 5
Joined: Mon Jul 09, 2018 5:17 pm
Projects: Never a Queen
Location: Pittsburgh, PA, USA
Contact:

Re: More robust random number generator

#4 Post by Askewnotion »

Thanks for the input, Remix!

I'm used to running my Python script on Anaconda, so I can usually go wild with my libraries. This is a new change of pace, and a welcome challenge. I think I'm just going to keep posting on this thread with various hacks to make different types of distributions for people to use. Maybe this can get sticky'ed or something once I get enough content in here.

I figure I will go with something a little easier for most people to grasp (compared to the drudgery that is the Beta distribution and all the ins-and-outs), and that is the triangular distribution.

Most people are familiar with hearing about the bell curve. It's most likely to hit in the middle and less likely to hit on the ends, with these ends theoretically going to negative and positive infinity. You can think of the triangular distribution as a very simplified bell curve that has distinct end points. The simple measurements of the triangular distribution and the fact it has fixed ends makes it perfect for some ren'py random number transformations!

How the triangular distribution works is you get to determine where the highest part of the triangle sits, i.e. the most likely result. Lets say, for example, you want most of the results to be centered around 70%, but there still be a chance to get values from 0% to 100%. You would define the middle point as 0.7 and you are ready to rock! Note that, in statistics, this mid point is where you are defining something called the mode. The mode is, if you took an infinite number of samples of this distribution, this is the number that would appear the most if you were to count them.

This is probably the easiest code to implement as it is basically a piecewise function with no integral necessary. Note that your mid point must be between 0 and 1, but not equal to either (0<mid<1). Feel free to copy the code below for your use;

Code: Select all

init python:
    import math
    
    #Calculate the triangular CDF. 'mid' is the midpoint you choose and 'x' can be generated from a random number generator
    def tri(mid,x):
        if (x <= mid):
            triprob = math.pow(x,2)/mid
        else:
            triprob = 1 - math.pow(1-x,2)/(1-mid)
        return triprob
        
label start:
    $core = tri(0.08, 0.322)

    "Around 0.5; [core]"
If you want to try out some calculations for yourself, a good calculator can be found here; https://calculator.vhex.net/calculator/ ... stribution
Make sure to make l=0 and r=1 when you do this to simulate the code, and have m be some number between 0 and 1.

User avatar
Askewnotion
Newbie
Posts: 5
Joined: Mon Jul 09, 2018 5:17 pm
Projects: Never a Queen
Location: Pittsburgh, PA, USA
Contact:

Re: More robust random number generator

#5 Post by Askewnotion »

If you are reading this far down, congratulations! You are now officially a math nerd; welcome to the ranks!

You may also be saying to yourself, "I really like the idea of that Beta distribution, but it is way too restrictive! I want to use decimal numbers and put in whatever I want. You're not my father! You can't tell me what to do!"

Well, hold on there champ, I may have just the solution for you. It's called the Kumaraswamy distribution. Try saying that out loud five times fast...

This is like the Beta distribution above where we are passing a random number from 0-1. Additionally, it uses weights like the Beta distribution for alpha (a) and beta (b) that define our shape parameters. The values won't be exactly the same as the Beta distribution. You will find that the tails are not quite as long for the same alpha and beta passed into a Beta function. In other words, while B(4,8,0.7) got us a value of around 0.996, the same value for our Kumaraswamy distribution gets us around 0.889.

So, what are the advantages? Well, the big one is that this works very well with decimals. Lets say you have stats that range from 1-100. That is pretty big for any shape parameter. But, what happens if you divide them by 100? You now have weights ranging from 0.1-10, which fit every nicely with this distribution. Take note that values less than 1 can be considered a "penalty." That is, if alpha is 0.5 and beta is 0.5, for example, you will not have a straight, even odds across probabilities. You will instead have "high" values for very low numbers and "low" values for very high numbers of x. The graphs at the bottom of this link demonstrate this idea well; https://www.itl.nist.gov/div898/softwar ... kumcdf.htm

Take note that both a and b should be greater than zero. Otherwise, at zero you will get 0 returned every time and anything negative will give you some funky numbers. Funky numbers is a very technical math term.

The code for this can be found below. Feel free to implement it as needed;

Code: Select all

init python:
    import math
    
    #a is the lefthand shape parameter, b is the righthand shape parameter, and x is a random variable from 0-1
    def kuma(a,b,x):
        return (1 - math.pow(1- math.pow(x,a),b))
    
label start:
    $core=kuma(4,8,0.7)

    "Around 0.889; [core]"

User avatar
Askewnotion
Newbie
Posts: 5
Joined: Mon Jul 09, 2018 5:17 pm
Projects: Never a Queen
Location: Pittsburgh, PA, USA
Contact:

Re: More robust random number generator

#6 Post by Askewnotion »

Quick set of code before before I head out to a conference this next week. I tried to post this earlier but the internet at my current location is spotty at best and kicked my upload a few times.

The next set is a 2-for-1 in getting you distributions that you want weighed heavily in the extremes. For example, if you want it very likely to have output on the low end or high end, these are the distributions for you. The resultant in the CDF will be that most values, except for very high or very low ones, will sit toward the middle of distribution (around 0.5).

The first one is called the U-quadratic distribution. It looks just like it sounds; a U. The second one is the Arcsine distribution. It is also U-shaped, but has an actual chance for the midpoint to occur, as opposed to the U-distribution. This causes the the U-quadratic distribution to be more "centralized" than the Arcsine distribution. You can look at both graphs though these three links; The code is pretty simple, so feel free to use it as you see fit;

Code: Select all

init python:
    import math
    
    #U-quadratic distribution at random variable x
    def Uquad(x):
        return 4*(math.pow(x-0.5,3)+math.pow(0.5,3))
        
    #Arcsine distribution at random variable x
    def asine(x):
        return 2/math.pi*math.asin(math.pow(x,0.5))
        
label start:

    $coreA=Uquad(0.25)
    $coreB=Uquad(0.75)

    "The left quarter is 0.4375 ([coreA]) and the right quarter is 0.5625 ([coreB])"

    $coreA=asine(0.25)
    $coreB=asine(0.75)

    "The left quarter is 1/3 ([coreA]) and the right quarter is 2/3 ([coreB])"        
As you can see, for the U-quadratic distribution, values between 0.25 and 0.75 get transformed to 0.4375 and 0.5625. This normalizes a fair bit toward the center of 0.5.

The Arcsine distribution also normalizes to the center, having values between 0.25 and 0.75 get transformed to 1/3 (0.333...) and 2/3 (0.666...). It is nowhere as extreme as the U-quadratic, but still fills the same role.

It is important to note that both of these distributions are symmetrical, so unlike some of the previous methods, we give no weights to "skill levels" or the like.

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: More robust random number generator

#7 Post by Remix »

I would suggest either adding these to The Cookbook or asking Taleweaver or other admin to move this thread over there.

The functions clearly have their uses, especially in any game that needs more than just simple randomness and I feel they might get lost in the obscurity of old threads over in this forum area.
Frameworks & Scriptlets:

Post Reply

Who is online

Users browsing this forum: No registered users