Skip to main content
GameDev.net gamedev.net
🔒 Locked

Oddly-shaped Function

Started by mbcudmore Mar 13, 2007 at 8:19 PM 7 replies 1.1k views
Original Post
mbcudmore
mbcudmore
Hello ^^ I'm having a brainlock. There's an equation I'm trying to figure out for the first gaming engine I'm experimenting with. It is related to character attributes and experience: The function getScore(int experience, int perfectScore) { ... } returns a score between zero and perfectScore based on the value of experience. The idiology is that only the lowest possible value of experience (-2147483648) returns a score of zero, whereas only the highest possible value of experience (2147483647) returns a perfect score. Those numbers cover the entire integer scope in Java. Zero experience returns half of a perfect score - meaning average. Experience can go down, and a character can get worse at a skill, or experience can go up, and a character can improve on a skill. Around the average mark (zero experience) it is very easy to gain or lose "levels", meaning a slight change in experience can affect the returned score. Once the experience gets very large or very small, however, the levels begin to advance/decline more slowly. Here is an approximation of the activity I am working for: Where H is the returned score, and A is the max/min value of experience. So, I know I've worked with graphs like this before in Math class, but I'm having dificulty finding the equation for the above graph. I want it to be as simple as possible, but still provide for an affective scoring system. So far, I have an equation that looks like this: Score = 100/(1 + 1.5^(-X)) And that gives me a pretty graph for the window X:(-10,10) Y:(-10,100) haha... However, I'm not sure how to adapt this function to the variables H and A. Any helpful hints would be much appreciated. Thank you much! :D Oh, and sorry for the long explanation... I like to be thorough.
My puddle of mud [=] a blueberry pie in the making.
Agony
Agony
If you want F(-A) and F(A) to return exactly min and max scores respectively, then a sigmoid (which is what you're trying to use) isn't the perfect solution. It never reaches its min/max values, but approaches them asymptotically. A sine wave might be more appropriate, since it actually reaches its min/max values (and doesn't cycle, if you just cut the function off appropriately, which you seem to do correctly with -2147483648 and +2147483647).

First of all, start with the standard sine function:

F1(x) = sin(x)

Then modify it so that it is in the correct range [0, H]:

F2(x) = (H/2)·(sin(x) + 1)

Then shift it so that the original -π is now -A, and +π is +A:

F3(x) = (H/2)·(sin((π·x)/(2A)) + 1)

I think that last one should get you what you want.

[edit]Fixed a tiny math mistake.[/edit]
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas nly, and not for things themselves." - John Locke o
mbcudmore
mbcudmore
Quote:
Original post by Agony
F3(x) = (H/2)·(sin((π·x)/(2A)) + 1)


Hmm, not exactly the curvature I was looking for, but it does work fairly well.

Thanks! :D




There were some rounding issues, and everything should be type-cast to double, but:

public int getScore(int minValue, int maxValue, int actualValue, int perfectScore) {   // F3(x) = (perfectScore/2)·(sin((π·x)/(2·maxValue)) + 1)   return (int)((perfectScore/2.0)*(Math.sin((actualValue*Math.PI)/(maxValue-minValue))+1.0));}


[Edited by - mbcudmore on March 13, 2007 9:58:16 PM]
My puddle of mud [=] a blueberry pie in the making.
Agony
Agony
I did think of a way to modify it somewhat to get a different curvature. The basic idea is to use this form:

F4(x) = (H/2)·(sin((π/2)·G(x)) + 1)

where G(x) is a similar shaped function, except that:

G(-1) = -1
G(0) = 0
G(1) = 1

There are different functions you can use to get this effect, and I was trying to find one with a single constant that could vary the function from a nearly straight line to a very sharply curved line. Unfortunately I couldn't find something that would work. However, you can just plug essentially the same function straight in as G(x):

G1(x) = sin((π·x)/(2·A))

This will give you a steeper slope at x=0, and F(x) will approach 0 and H more quickly from x=0. You could do this again, and get:

G2(x) = sin((π/2)·sin((π·x)/(2·A)))

Again, this will produce an even steeper slope at x=0.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas nly, and not for things themselves." - John Locke o
Gagyi
Gagyi
Looks like the derivate (i hope this is the correct word) of your function is
1-x^2=-x^+1

We integrate this and get the desired function:
-x^3/3+x

Hmm, it is 2/3 at 1 and -2/3 at -1. Correcting this looks like:
-0.5*x^3+1.5*x

This is:
1 at 1, -1 at -1, has the slope of 0 at 1 and -1.

And I'm sure its faster than sin()
-----------------------------------"After you finish the first 90% of a project, you have to finish the other 90%." - Michael Abrashstickman.hu <=my game (please tell me your opinion about it)
Stiffler
Stiffler
Hello, looking at your graph it looks like there are stable solutions at 0 and A, therefore you can write a differential equation like so

dy/dx = Y(A-Y)

Using A = 1 for simplicity to translate your -2147483648 and 2147483648 between zero and 1. After solving for Y, you obtain

y = ce^(x) / (1 + ce^(x))

This graph does not increase at the same rate at which your graph appears, so messing with the coefficents gives a pretty good result of what you want.

Final equation:

y = 2*e^(2x)/(1+2*e^(2x))

It is asymptotic though, so zero and 1 will never be given.
~Mark Schadler
alvaro
alvaro
You can use an asymptotic-style formula and then apply a simple affine transformation at the end to force the values at the extremes to be what you want.

This is C++, but it is easy to read:
#include <cmath>double const k=.000000005;double f(int x){  return 1.0/(1.0+std::exp(-k*x));}double f_scaled(int x){  static double min=f(-2147483648);  static double max=f(+2147483647);  return (f(x)-min)/(max-min);}


Change the constant k until you get your desired behavior.
JohnBolton
JohnBolton
Variations of one of these two functions might give you the results you are looking for:
    <big>y = tanh( x )</big> 
In this function, y approaches -1 and 1 as x approaches -infinity and +infinity.
    <big>y = 3x<sup>2</sup> - 2x<sup>3</sup></big> 
This gives the kind of curve you want when x is between 0 and 1 (y is also between 0 and 1).

After looking at your graph (I didn't read the details in your post), I think you should try this:
    t = X / ( 2 * A ) + 0.5f;      // X is in the range [-A,A], t is in the range [0,1]    y = 3 * t * t - 2 * t * t * t; // y is in the range [0,1]    score = H * y;                 // score is in the range [0,H] 
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Rand
Rand
Quote:
Original post by alvaro
You can use an asymptotic-style formula and then apply a simple affine transformation at the end to force the values at the extremes to be what you want.

This is C++, but it is easy to read:
*** Source Snippet Removed ***

Change the constant k until you get your desired behavior.


I would recommend just doing your maths and get your perfect score in the range 0->1 anyway. Your going to lose so much precision have a floating point in range 0->1 then scaling to max 32 bit int but as a float, then casting.

Just 'use' your perfect score in floating point 0.0->1.0. You seem to want to use complete range anyway, and floating point accuracy is very high (not the highest, but certainly high enough) at around 1.

Also you could get some really freaky bugs creap up using an int with that range. If you were debugging, how would you recognise an invalid score, if all numbers are valid? Atleast if your in the 0->1 range and it goes out, you know something has gone wrong somewhere.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.