Skip to content

Commit

Permalink
fix(core): wrong score when having multiple "Oops! All 6s" jokers
Browse files Browse the repository at this point in the history
Fix probabilistic effects being incorrectly simulated with multiple instances of the "Oops! All 6s" joker. Each instance doubles the odds meaning the probability for a lucky card's mult effect to trigger is 1/5 by default, 2/5 with one instance of "Oops! All 6s", 4/5 with 2 instances, and 8/5 (i.e. 1, the guaranteed event) with 3 instances. Previously, the calculator simply added 1 to the numerator (i.e. 1/5 → 2/5 → 3/5, etc.) which isn't correct.
kleinfreund committed Jan 15, 2025
1 parent 0953ecb commit f0325e4
Showing 3 changed files with 31 additions and 9 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -13,3 +13,21 @@
## Contributing

[Contribution guidelines for this project](CONTRIBUTING.md)

## Notes

### Tell me the odds: how probabilistic effects are handled

Balatrolator always returns deterministic scores and doesn't roll any “dice” when probabilistic effects (e.g. lucky card) are at play. Instead, it gives you scores for three “luck modes”:

- **no luck**: the lower bound
- **all luck**: the upper bound
- **average luck**: the score you can expect with perfectly average luck

“No luck” means probabilistic effects never contribute anything to the score. Lucky cards never apply their +Mult value; Bloodstone never applies its xMult value.

“All luck” means probablistic effects always contribute to the score. Lucky cards always add their +mult value; Bloodstone always applies its xMult value.

“Average luck” means probabilistic effects are scored with the scores that you can expect on average. That is, the resulting score is the one you would get as if you would play the same hand an infinite amount of times and averaged the resulting scores. In other words, a lucky card's +Mult value of 20 with standard odds of 1 in 5 would add 4 (20 * 1/5). “Oops! All 6s” jokers do factor into this math: having two instances of that joker would raise the odds of a lucky card's +Mult effect to 4 in 5 and so the value scored would be 16 (20 * 4/5).

Of special note is the case when there are enough instances of the “Oops! All 6s” joker to guarantee an effect in the game. In that case, the three luck modes become irrelevant and the score is calculated the same way it would in “all luck” mode.
18 changes: 11 additions & 7 deletions src/utilities/balanceMultWithLuck.test.ts
Original file line number Diff line number Diff line change
@@ -33,32 +33,36 @@ describe('balance', () => {
[3, 3, 3, 'all', 'times', 3],
[3, 4, 3, 'all', 'times', 3],

[4, 0, 3, 'none', 'times', 1],
[4, 0, 3, 'average', 'times', 2],
[4, 1, 3, 'average', 'times', 3],
[4, 2, 3, 'average', 'times', 4],
[4, 0, 3, 'none', 'times', 1],
[4, 1, 3, 'all', 'times', 4],

[5, 0, 4, 'none', 'plus', 0],
[5, 0, 4, 'average', 'plus', 1.25],
[5, 1, 4, 'average', 'plus', 2.5],
[5, 2, 4, 'average', 'plus', 5],
[5, 1, 4, 'all', 'plus', 5],

[20, 0, 5, 'none', 'plus', 0],
[20, 1, 5, 'none', 'plus', 0],
[20, 2, 5, 'none', 'plus', 0],
[20, 3, 5, 'none', 'plus', 0],
[20, 3, 5, 'none', 'plus', 20],
[20, 4, 5, 'none', 'plus', 20],
[20, 5, 5, 'none', 'plus', 20],
[20, 0, 5, 'average', 'plus', 4],
[20, 1, 5, 'average', 'plus', 8],
[20, 2, 5, 'average', 'plus', 12],
[20, 3, 5, 'average', 'plus', 16],
[20, 4, 5, 'average', 'plus', 20],
[20, 5, 5, 'average', 'plus', 20],
[20, 2, 5, 'average', 'plus', 16],
[20, 3, 5, 'average', 'plus', 20],
[20, 0, 5, 'all', 'plus', 20],
[20, 1, 5, 'all', 'plus', 20],
[20, 2, 5, 'all', 'plus', 20],
[20, 3, 5, 'all', 'plus', 20],
[20, 4, 5, 'all', 'plus', 20],
[20, 5, 5, 'all', 'plus', 20],
[20, 6, 5, 'all', 'plus', 20],
])('works', (mult, oopses, denominator, luck, mode, expectedResult) => {
])('works (mult: %s, oopses: %s, p: 1/%s, luck: %s, mode: %s)', (mult, oopses, denominator, luck, mode, expectedResult) => {
expect(balanceMultWithLuck(mult, oopses, denominator, luck, mode)).toBe(expectedResult)
})
})
4 changes: 2 additions & 2 deletions src/utilities/balanceMultWithLuck.ts
Original file line number Diff line number Diff line change
@@ -5,13 +5,13 @@ import type { Luck } from '#lib/types.js'
*
* For +Mult (as indicated by `mode === 'plus'`), the mult value is balanced in the range [0, mult] (e.g. [0, 20] for a lucky card). For xMult (as indicated by `mode === 'times'`), the mult value is balanced in the range [1, mult] (e.g. [1, 2] for Bloodstone).
*
* The probability of an effect (i.e. “1 in $denominator chance”) is used to determine the _average_ mult value. For example, with lucky card's 1 in 5 chance, the average value used would be 1/5*20 = 4. The numerator is raised by the number of “Oops! All 6s” jokers. Notably, having “Oops! All 6s” four times would guarantee lucky cards to trigger because their probability would be 5/5 = 1.
* The probability of an effect (i.e. “1 in $denominator chance”) is used to determine the _average_ mult value. For example, with lucky card's 1 in 5 chance, the average value used would be 1/5*20 = 4. The numerator is 2 raised to the power of number of “Oops! All 6s” jokers (i.e. without any of them, it would be 1, then 2, then 4, and so on). Notably, having “Oops! All 6s” three times would guarantee lucky cards's mult effects to trigger because their probability would be 8/5 = 1.
*
* The `luck` parameter modifies the result by either implying “no luck” (in order words: minimum luck or a probability of 0) or “all luck” (in other words: maximum luck or a probability of 1). A number of “Oops! All 6s” jokers resulting in a probability of 1 forces “all luck” even if the `luck` parameter is not “all luck”.
*/
export function balanceMultWithLuck (mult: number, oopses: number, denominator: number, luck: Luck, mode: 'times' | 'plus'): number {
const neutralElement = mode === 'times' ? 1 : 0
const minimumNumerator = Math.max(0, Math.min(oopses + 1, denominator))
const minimumNumerator = Math.max(0, Math.min(Math.pow(2, oopses), denominator))

let numerator = minimumNumerator
if (luck === 'all') {

0 comments on commit f0325e4

Please sign in to comment.