0

I'm creating a game in which someone opens a chest and the chest will give them a random prize. The maximum I can give out is 85,000,000 in 10,000 chests which is 8,500 average however I want some to make it so some chests will be below this value and above and to be able to set a min lose of 2,500 and max win 250,000 but still get the total value of 85,000,000.

I'm really struggling to come up with an algorithm for this using my C# knowledge.

Prajwal
  • 3,403
  • 3
  • 20
  • 43
Aidan
  • 197
  • 1
  • 2
  • 16
  • Well do you know how to do it in pseudo-code? Forget the C# part first - work out an algorithm to start with... the implementation is likely to be the easy part. – Jon Skeet Jan 06 '17 at 10:37
  • Can you precalculate how much to put in each chest, or do you need to calculate this as the chest is opened? – Lasse V. Karlsen Jan 06 '17 at 10:52
  • @LasseV.Karlsen Yes I wish to calculate 10,000 chests in advance with a total value of 85,000,000 however I do not want the value in each chest to go below the minimum or a maximum. – Aidan Jan 06 '17 at 11:02
  • What about simply setting each chest to 8500 first, then pick half at random and pair each from that half with a random chest from the other half, allowing duplicates when picking from the other half. Then reduce the first chest by a random amount within the tolerance and raise the second chest by the same amount. – Lasse V. Karlsen Jan 06 '17 at 11:04
  • @LasseV.Karlsen - The problem is that the min lose and max win are different. – Graffito Jan 06 '17 at 11:05
  • @Graffito - why is that a problem – PaulF Jan 06 '17 at 11:10
  • Yes, but as I said, allow picking the same chest multiple times from the other half, which means that if you reduce 2 chests from the first half, they both increase the same chest from the second half. – Lasse V. Karlsen Jan 06 '17 at 11:11
  • To get the max prize, 1 chest would need to be the recipient of at least 40 other chests. – PaulF Jan 06 '17 at 11:17
  • What do you mean by "min lose"? Can the player lose money on opening a chest (=negative amount in chest)? Or did you mean "min *win*"? – Hans Kesting Jan 06 '17 at 14:31

5 Answers5

1

pseudocode algoritm:

  • use an array of chests
    • index of array is chest number; length of array is amount of chests
    • value in array is amount in chest at that index
    • initial value is total amount divided by number of chests
  • now repeat a number of times (say: 10 times the number of chests)
    • get two random chests
    • work out the maximum amount you can transfer from chest 1 to chest 2, so that 1 doesn't get below the minimum and 2 doesn't get above the maximum
    • get a random value below that maximum and transfer it

Now try and implement this in C#.

Hans Kesting
  • 34,565
  • 7
  • 74
  • 97
  • It may need repeating more than 10 times max chests to get a reasonable chance of the maximum prize as any one chest would need the maximum amount available from at least 40 other chests starting from the average amount. – PaulF Jan 06 '17 at 11:20
  • @PaulF - yeah, that is a major problem with this solution. – Hans Kesting Jan 07 '17 at 13:49
1

Here goes some OOP. You have Player class. Which stores some info - amount of gold he has, chests left to open, and total amount of gold in chests he will find.

public class Player
{
    private int gold = 0;
    private int goldLeftInChests = 85000000;
    private int chestsToOpen = 10000;
    private Random random = new Random();

    public void OpenChest()
    {
        if (chestsToOpen == 0)
            return; // or whatever you want after 10000 chests.

        int goldInChest = CalculateGoldInNextChest();
        goldLeftInChests -= goldInChest;
        chestsToOpen--;
        gold += goldInChest;
    }

    private int CalculateGoldInNextChest()
    {
        if (chestsToOpen == 1)
            return goldLeftInChests;

        var average = goldLeftInChests / chestsToOpen;
        return random.Next(average);
    }
}

When next chest is opened, gold in chest is calculated and player data ajusted - we add some gold to player and reduce total amount of gold in chests, and chests left to open.

Calculating gold in a chest is very simple. We get average amount left and calculate number between 1 and average. First time this value will always be below 8500. But next time average will be little bit bigger. So player will have chance to find more than 8500. If he will be unlucky again, average will grow. Or it will be reduced if palyer gets lot of gold.


UPDATE: As @Hans pointed, I didn't count min and max restrictions for gold in chests. Also there is a problem in @Hans solution - you should move gold between 10000 chests lot of time to get some chests close to 250000 value. And you have to fill and keep all 10000 values. Next problem I thought about was random numbers distribution in .NET. Values have equal probability on all interval we are using. So if we are generating value from 2500 to 250000, chance that we'll get value around 8500 (average) is like 12000 (8500±6000) vs 235500 (250000-12000-2500). That means generating default random numbers from given range will give you lot of big numbers in the begining, and then you will stick near lowest boundary (2500). So you need random numbers with different distribution - Gaussian variables. We still want to have 8500 gold with highest probablity, and 250000 with lowest probability. Something like that:

enter image description here

And last part - calculation. We need to update only one method :)

private int CalculateGoldInNextChest()
{
    const int mean = 8500;
    var goldPerChestRange = new Range(2500, 250000);
    var averageRange = new Range(mean - 2500, mean + 2500);

    if (chestsToOpen == 1)
        return goldLeftInChests;

    do
    {
        int goldInChest = (int)random.NextGaussian(mu: mean, sigma: 50000);
        int averageLeft = (goldLeftInChests - goldInChest) / (chestsToOpen - 1);

        if (goldPerChestRange.Contains(goldInChest) && averageRange.Contains(averageLeft))
            return goldInChest;
    }
    while (true);
}

Note: I used range to make code more readable. Running tests several times produces nice top values more than 200000.

Community
  • 1
  • 1
Sergey Berezovskiy
  • 215,927
  • 33
  • 392
  • 421
0

This should be a good starting point. Each chest gets filled randomly with the limits adapting to make sure the remaining chests can also get valid values.

        Random rand = new Random();
        int[] chests = new int[numOffChests];
        int remaining = TotalValue;
        for(int i = 0; i < numOffChests; i++)
        {
            int minB = Math.Max(remaining / (numOffChests - i), maxChestValue);
            int maxB = Math.Min(remaining - (numOffChests - i * minChestValue), maxChestValue);
            int val = rand.Next(minB, maxB);
            remaining -= val;
            chests[i] = val;
        }
prof1990
  • 460
  • 3
  • 10
0

The distribution has to be heavily skewed to get that range of values with that mean. Try an exponential formula, X=exp(a*U+b)+c where U is uniform on [0,1]. Then the conditions are

 -2,500 = exp(b)+c
250,000 = exp(a+b)+c
  8,500 = integral(exp(a*u+b), u=0..1) 
        = exp(b)/a*(exp(a)-1)+c
        = 252,500/a+c

which gives the two equations

250,000+2,500*exp(a) = c*(1-exp(a))
               8,500 = 252,500/a+c

A bit of graphical and numerical solution gives the "magic" numbers

a =  22.954545, 
b = -10.515379, 
c = -2500.00002711621

Now fill 10,000 chests according to that formula, compute the sum over the chest prices and distribute the, with high probability small, excess in any pattern you like.


If you want to hit the upper and lower bounds more regularly, increase the bounds at the basis of the computation and cut the computed value if outside the original bounds.

Lutz Lehmann
  • 21,318
  • 2
  • 18
  • 42
0

I assume that a probabilistic function gives the chance of a win/lose value V to occur. Let's say that the probability for V is proportional to (250000-V)**2, giving fewer chances to get high prizes.

To simplify some rounding issues, let's also assume that win/lose are multiple of 100. You may then make the following (untested) computations:

int minwin = -2500 ;
int maxwin = 250000;
int chestcount = 10000;
int maxamount  = 85000;
// ----------- get probabilities for each win/lose amount to occur in all chests  ----------
long probtotal = 0 ;
List<long> prob = new List<long> ; 
for (long i=minwin;i<=maxwin;i++) if (i%100==0) 
{ long ii=(maxwin-i)*(maxwin-i) ; prob.Add((float)ii) ; probtotal+=ii ; }
for (int i=0;i<prob.Count;i++) prob[i]=prob[i]/(probtotal) ;
for (int i=0;i<prob.Count;i++) 
  Console.writeLine("Win/lose amount"+((i-minwin)*100).ToString()+" probability="+(proba[i]*100).ToString("0.000")) ;
// Transform "prob" so as to indicate the float count of chest corresponding to each win/lose amount
for (int i=0;i<prob.Count;i++) prob[i]=prob[i]*chestcount ;

// ---------- Set the 10000 chest values starting from the highest win -------------
int chestindex=0 ;
List<int> chestvalues = new List<int>();
float remainder = 0 ;
int totalwin=0 ;
for (int i=0;i<prob.Count;i++)
{
   int n = (int)(prob[i]+remainder)  ; // only the integer part of the float ;
   remainder = prob[i]+remainder-n ;    
   // set to n chests the win/lose amount
   int curwin=(i-minwin)*100 ;
   for (int j=0;j<n && chestvalues.count<chestcount;j++) chestvalues.Add(curwin) ; 
   totalwin+=curwin ;
}
// if stvalues.count lower than chestcount, create missing chestvalues  
for (int i=chestvalues.Count;i<chestcount;i++) chestvalues.Add(0) ;

// --------------- due to float computations, we perhaps want to make some adjustments --------------
// i.e. if totalwin>maxamount (not sure if it may happen), decrease some chestvalues
...
// --------------- We have now a list of 10000 chest values to be randomly sorted --------------
Random rnd = new Random();
SortedList<int,int> randomchestvalues = new SortedList<int,int>() ;
for (int i=0;i<chestcount;i++) randomchestvalues.Add(rnd.Next(0,99999999),chestvalues[i]) ;
// display the first chests amounts
for (int i=0;i<chestcount;i++) if (i<234) 
{ int chestamount = randomchestvalues.GetByIndex(i) ; Console.WriteLine(i.ToString()+":"+chestamount) ; }
}
Graffito
  • 1,570
  • 1
  • 8
  • 10