diff --git a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go index ae368017a..07d56bb6b 100644 --- a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go @@ -9,45 +9,45 @@ import ( "math/rand" ) -// PayoutChances to pick the random number between +// PayoutChances to pick the random number between 0 and this value const PayoutChances = 1_000_000_000 const ( - // PayoutTier1 that the random number must fall below to win - PayoutTier1 = 400_000_000 - // PayoutTier2 that the payout must be below, but above PayoutTier1, to win - PayoutTier2 = 500_000_000 + // The probabilities of a Tier being drawn (out of PayoutChances draws) + Tier1Prob = 400_000_000 // this is 400_000_000 / 1_000_000_000 + Tier2Prob = 100_000_000 // this is 100_000_000 / 1_000_000_000 + Tier3Prob = 50_000_000 //etc... + Tier4Prob = 10_000_000 + Tier5Prob = 100 + //The probability of NoBottle is: (PayoutChances - (The Sum of all TierXProb values above)) / PayoutChances + + // Thresholds that the random number must fall below to win a tier + PayoutTier1 = Tier1Prob + PayoutTier2 = Tier2Prob + PayoutTier1 + PayoutTier3 = Tier3Prob + PayoutTier2 + PayoutTier4 = Tier4Prob + PayoutTier3 + PayoutTier5 = Tier5Prob + PayoutTier4 + //Anything above PayoutTier5 is NoBottle - // PayoutTier3 - PayoutTier3 = 550_000_000 - - // PayoutTier4 - PayoutTier4 = 560_000_000 - - // PayoutNoBottle is awarded when no bottle is sent to the user - PayoutNoBottle = 999_999_000 - - // PayoutTier5 (very low probability) - PayoutTier5 = 1_000_000_000 ) -// pickRandomReward, 0 being no rewards. +// pickRandomReward. Returns Lootbox Tier 0-5, 0 being no rewards. func pickRandomNumber() int { n := rand.Int31n(PayoutChances) switch { - case n >= PayoutNoBottle && n <= PayoutTier5: - return 5 - case n >= PayoutTier4 && n <= PayoutNoBottle: - return 0 - case n >= PayoutTier3 && n <= PayoutTier4: - return 4 - case n >= PayoutTier2 && n <= PayoutTier3: - return 3 - case n >= PayoutTier1 &&n <= PayoutTier2: - return 2 case n <= PayoutTier1: return 1 + case n <= PayoutTier2: + return 2 + case n <= PayoutTier3: + return 3 + case n <= PayoutTier4: + return 4 + case n <= PayoutTier5: + return 5 + case n > PayoutTier5 && n <= PayoutChances: + return 0 //NoBottle default: panic(fmt.Sprintf("bad pickRandomNumber impl: %v", n)) } diff --git a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go new file mode 100644 index 000000000..61152dc95 --- /dev/null +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestPickRandomTier. Simulate a large number of draws and compare to the expected probabilities. +func TestPickRandomTier(t *testing.T) { + simIterations := 10_000_000 + + expectedResults := getExpectedTierPcts() + + //Prepare result data structure + simResults := make(map[int]int) + for i := 0; i < 6; i++ { + simResults[i] = 0 + } + + //Run Simulations + for i := 0; i < simIterations; i++ { + lootboxRewardTier := pickRandomNumber() + simResults[lootboxRewardTier] += 1 + } + + //Calculate the Simulated Result Outcome Percentages for each Tier + simResultPcts := make(map[int]float64) + for i := 0; i < 6; i++ { + simResultPcts[i] = (float64(simResults[i]) / float64(simIterations)) * 100 + } + + //This is "how close" the simulated outcome percentages must be to the expected probabilities to pass the test + testTolerancePct := float64(0.5) //Percentage, so 0.5 = 0.5% or 1 = 1% + + //Actual testing & asserts. + //Compare the Simulated Result Outcome Pcts to an "acceptable" value range around the Expected Result + for i := 0; i < 6; i++ { + + //Define an acceptable range of [Expected Result Pct] + or - TestTolerancePct + acceptableHigh := expectedResults[i] + testTolerancePct //expectedResults[i] is a pct. so we just +/- the tolerance + acceptableLow := expectedResults[i] - testTolerancePct + if acceptableLow < 0 { + acceptableLow = 0 + } + + thisSimResultPct := simResultPcts[i] // The simulated outcome pct + + //Assert: Simulated Output Pct must be within the range of acceptable expected values. + assert.True(t, thisSimResultPct >= float64(acceptableLow) && thisSimResultPct <= float64(acceptableHigh), fmt.Sprintf("Tier %v value is not within expected range", i)) + } +} + +// getExpectedTierPcts. Returns the expected percentage chance of being drawn for all tiers +func getExpectedTierPcts() map[int]float64 { + expectedMap := make(map[int]float64) + expectedMap[0] = float64((PayoutChances - PayoutTier5)) / float64(PayoutChances) * 100 + expectedMap[1] = float64(Tier1Prob) / float64(PayoutChances) * 100 + expectedMap[2] = float64(Tier2Prob) / float64(PayoutChances) * 100 + expectedMap[3] = float64(Tier3Prob) / float64(PayoutChances) * 100 + expectedMap[4] = float64(Tier4Prob) / float64(PayoutChances) * 100 + expectedMap[5] = float64(Tier5Prob) / float64(PayoutChances) * 100 + return expectedMap +}