From a198694a6fae0dca03194d8474e2a6976d3e9779 Mon Sep 17 00:00:00 2001 From: "Dr. Groove" <166350737+DrGrooveDev@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:23:58 -0700 Subject: [PATCH 1/3] Refactor random tier function for clarity. Add simulations. --- .../pick-random-tier.go | 84 +++++++++++++------ 1 file changed, 58 insertions(+), 26 deletions(-) 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..7fee5d7f0 100644 --- a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go @@ -9,46 +9,78 @@ 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 - // PayoutTier3 - PayoutTier3 = 550_000_000 + // 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 - // 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)) } } + +// 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 +} + +// simulate a large number of draws and compare to the expected probabilities. +func simulateDraws() { + simIterations := 100000 + expectedResults := getExpectedTierPcts() + simResults := make(map[int]int) + for i := 0; i < 6; i++ { + simResults[i] = 0 + } + + for i := 0; i < simIterations; i++ { + lootboxRewardTier := pickRandomNumber() + simResults[lootboxRewardTier] += 1 + } + + fmt.Printf("\nIterations: %v", simIterations) + for i := 0; i < 6; i++ { + fmt.Printf("\nTier %v Got: %v = %v%% Expected:%v%%", i, simResults[i], (float64(simResults[i])/float64(simIterations))*100, expectedResults[i]) + } +} From 364f106dc1b43ec949c3e8a7007157db09db584b Mon Sep 17 00:00:00 2001 From: "Dr. Groove" Date: Mon, 8 Apr 2024 17:47:28 -0700 Subject: [PATCH 2/3] Properly configured test code --- .../pick-random-tier.go | 32 --------- .../pick-random-tier_test.go | 65 +++++++++++++++++++ 2 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go 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 7fee5d7f0..07d56bb6b 100644 --- a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier.go @@ -52,35 +52,3 @@ func pickRandomNumber() int { panic(fmt.Sprintf("bad pickRandomNumber impl: %v", n)) } } - -// 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 -} - -// simulate a large number of draws and compare to the expected probabilities. -func simulateDraws() { - simIterations := 100000 - expectedResults := getExpectedTierPcts() - simResults := make(map[int]int) - for i := 0; i < 6; i++ { - simResults[i] = 0 - } - - for i := 0; i < simIterations; i++ { - lootboxRewardTier := pickRandomNumber() - simResults[lootboxRewardTier] += 1 - } - - fmt.Printf("\nIterations: %v", simIterations) - for i := 0; i < 6; i++ { - fmt.Printf("\nTier %v Got: %v = %v%% Expected:%v%%", i, simResults[i], (float64(simResults[i])/float64(simIterations))*100, expectedResults[i]) - } -} 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..b66f2522d --- /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 + + //This is "how close" the simulated probabilities must be to the expected probs to pass the test + testTolerancePct := float64(0.5) //Percentage, so 0.5 = 0.5% or 1 = 1% + + 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 + } + + //Informational: Output Actual vs Expected probabilities + simResultPcts := make(map[int]float64) + fmt.Printf("\nIterations: %v", simIterations) + for i := 0; i < 6; i++ { + simResultPcts[i] = (float64(simResults[i]) / float64(simIterations)) * 100 + fmt.Printf("\nTier %v Got: %v = %f%% Expected:%f%%", i, simResults[i], simResultPcts[i], expectedResults[i]) + } + + fmt.Printf("\n") + + //Actual testing & asserts + for i := 0; i < 6; i++ { + acceptableHigh := expectedResults[i] + testTolerancePct //expectedResults[i] is a pct. so we just +/- the tolerance + acceptableLow := expectedResults[i] - testTolerancePct + if acceptableLow < 0 { + acceptableLow = 0 + } + + thisResult := simResultPcts[i] + fmt.Printf("\nTier %v Acceptable Range %v - %f Actual:%f", i, acceptableLow, acceptableHigh, thisResult) + assert.True(t, thisResult >= float64(acceptableLow) && thisResult <= 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 +} From 0f80680608cc0329b9d8a7d344fff013aeb31f98 Mon Sep 17 00:00:00 2001 From: "Dr. Groove" Date: Fri, 12 Apr 2024 08:16:18 -0700 Subject: [PATCH 3/3] Removed Printf statements. Added comments & modified variable names for clarity --- .../pick-random-tier_test.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 index b66f2522d..61152dc95 100644 --- a/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go +++ b/cmd/microservice-ethereum-create-transaction-lootboxes/pick-random-tier_test.go @@ -11,9 +11,6 @@ import ( func TestPickRandomTier(t *testing.T) { simIterations := 10_000_000 - //This is "how close" the simulated probabilities must be to the expected probs to pass the test - testTolerancePct := float64(0.5) //Percentage, so 0.5 = 0.5% or 1 = 1% - expectedResults := getExpectedTierPcts() //Prepare result data structure @@ -28,27 +25,30 @@ func TestPickRandomTier(t *testing.T) { simResults[lootboxRewardTier] += 1 } - //Informational: Output Actual vs Expected probabilities + //Calculate the Simulated Result Outcome Percentages for each Tier simResultPcts := make(map[int]float64) - fmt.Printf("\nIterations: %v", simIterations) for i := 0; i < 6; i++ { simResultPcts[i] = (float64(simResults[i]) / float64(simIterations)) * 100 - fmt.Printf("\nTier %v Got: %v = %f%% Expected:%f%%", i, simResults[i], simResultPcts[i], expectedResults[i]) } - fmt.Printf("\n") + //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 + //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 } - thisResult := simResultPcts[i] - fmt.Printf("\nTier %v Acceptable Range %v - %f Actual:%f", i, acceptableLow, acceptableHigh, thisResult) - assert.True(t, thisResult >= float64(acceptableLow) && thisResult <= float64(acceptableHigh), fmt.Sprintf("Tier %v value is not within expected range", i)) + 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)) } }