shufflecom-calculations 4.2.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/lib/games/coinflip/coinflip-classic-progressive.d.ts +1 -1
  2. package/lib/games/coinflip/coinflip-classic.d.ts +1 -1
  3. package/lib/games/coinflip/coinflip-rules.d.ts +1 -1
  4. package/lib/games/coinflip/coinflip-target.d.ts +1 -1
  5. package/lib/games/floor-is-lava/floor-is-lava-board.d.ts +8 -0
  6. package/lib/games/floor-is-lava/floor-is-lava-board.js +37 -0
  7. package/lib/games/floor-is-lava/floor-is-lava-board.js.map +1 -0
  8. package/lib/games/floor-is-lava/floor-is-lava-rules.d.ts +69 -0
  9. package/lib/games/floor-is-lava/floor-is-lava-rules.js +133 -0
  10. package/lib/games/floor-is-lava/floor-is-lava-rules.js.map +1 -0
  11. package/lib/games/floor-is-lava/floor-is-lava.d.ts +34 -0
  12. package/lib/games/floor-is-lava/floor-is-lava.js +106 -0
  13. package/lib/games/floor-is-lava/floor-is-lava.js.map +1 -0
  14. package/lib/games/rng/fixed-rng.js.map +1 -0
  15. package/lib/games/rng/random-number-generator.interface.js.map +1 -0
  16. package/lib/games/rng/shuffle-random-number-generator.d.ts +21 -0
  17. package/lib/games/rng/shuffle-random-number-generator.js +76 -0
  18. package/lib/games/rng/shuffle-random-number-generator.js.map +1 -0
  19. package/lib/games/rng/true-random-rng.js.map +1 -0
  20. package/lib/index.d.ts +7 -2
  21. package/lib/index.js +7 -2
  22. package/lib/index.js.map +1 -1
  23. package/lib/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +2 -2
  25. package/src/games/coinflip/coinflip-classic-progressive.spec.ts +14 -3
  26. package/src/games/coinflip/coinflip-classic-progressive.ts +1 -1
  27. package/src/games/coinflip/coinflip-classic.spec.ts +1 -1
  28. package/src/games/coinflip/coinflip-classic.ts +1 -1
  29. package/src/games/coinflip/coinflip-rules.spec.ts +1 -1
  30. package/src/games/coinflip/coinflip-rules.ts +1 -1
  31. package/src/games/coinflip/coinflip-target.spec.ts +1 -1
  32. package/src/games/coinflip/coinflip-target.ts +1 -1
  33. package/src/games/floor-is-lava/floor-is-lava-board.spec.ts +61 -0
  34. package/src/games/floor-is-lava/floor-is-lava-board.ts +41 -0
  35. package/src/games/floor-is-lava/floor-is-lava-rules.spec.ts +316 -0
  36. package/src/games/floor-is-lava/floor-is-lava-rules.ts +217 -0
  37. package/src/games/floor-is-lava/floor-is-lava.spec.ts +455 -0
  38. package/src/games/floor-is-lava/floor-is-lava.ts +138 -0
  39. package/src/games/rng/shuffle-random-number-generator.ts +100 -0
  40. package/src/index.ts +7 -2
  41. package/lib/games/fixed-rng.js.map +0 -1
  42. package/lib/games/floor-is-lava-rules.d.ts +0 -52
  43. package/lib/games/floor-is-lava-rules.js +0 -82
  44. package/lib/games/floor-is-lava-rules.js.map +0 -1
  45. package/lib/games/floor-is-lava.d.ts +0 -56
  46. package/lib/games/floor-is-lava.js +0 -134
  47. package/lib/games/floor-is-lava.js.map +0 -1
  48. package/lib/games/random-number-generator.interface.js.map +0 -1
  49. package/lib/games/true-random-rng.js.map +0 -1
  50. package/src/games/floor-is-lava-rules.spec.ts +0 -126
  51. package/src/games/floor-is-lava-rules.ts +0 -125
  52. package/src/games/floor-is-lava.spec.ts +0 -502
  53. package/src/games/floor-is-lava.ts +0 -184
  54. /package/lib/games/{fixed-rng.d.ts → rng/fixed-rng.d.ts} +0 -0
  55. /package/lib/games/{fixed-rng.js → rng/fixed-rng.js} +0 -0
  56. /package/lib/games/{random-number-generator.interface.d.ts → rng/random-number-generator.interface.d.ts} +0 -0
  57. /package/lib/games/{random-number-generator.interface.js → rng/random-number-generator.interface.js} +0 -0
  58. /package/lib/games/{true-random-rng.d.ts → rng/true-random-rng.d.ts} +0 -0
  59. /package/lib/games/{true-random-rng.js → rng/true-random-rng.js} +0 -0
  60. /package/src/games/{fixed-rng.ts → rng/fixed-rng.ts} +0 -0
  61. /package/src/games/{random-number-generator.interface.ts → rng/random-number-generator.interface.ts} +0 -0
  62. /package/src/games/{true-random-rng.ts → rng/true-random-rng.ts} +0 -0
@@ -1,126 +0,0 @@
1
- import BigNumber from 'bignumber.js';
2
- import { calculateEdgeMultiplier } from '../utils/edge';
3
- import { FixedSequenceRng } from './fixed-rng';
4
- import {
5
- BOARD_SIZE,
6
- DIFFICULTY_DROP_MAP,
7
- FloorIsLavaActionPhase,
8
- FloorIsLavaDifficulty,
9
- FloorIsLavaRules,
10
- FloorIsLavaStartAction,
11
- FloorIsLavaSuccessfulPickAction,
12
- } from './floor-is-lava-rules';
13
-
14
- const EDGE_BPS = 200;
15
-
16
- const startAction = (difficulty: FloorIsLavaDifficulty): FloorIsLavaStartAction => ({ phase: FloorIsLavaActionPhase.START, difficulty });
17
-
18
- const pickAction = (): FloorIsLavaSuccessfulPickAction => ({
19
- phase: FloorIsLavaActionPhase.SUCCESSFUL_PICK,
20
- selectedTile: 0,
21
- droppedTiles: [],
22
- multiplier: new BigNumber(0),
23
- });
24
-
25
- describe('FloorIsLavaRules.maxRounds', () => {
26
- it.each([
27
- [FloorIsLavaDifficulty.EASY, 12],
28
- [FloorIsLavaDifficulty.MEDIUM, 8],
29
- [FloorIsLavaDifficulty.HARD, 6],
30
- [FloorIsLavaDifficulty.TOXIC, 1],
31
- ])('%s allows %i rounds', (difficulty, expected) => {
32
- expect(FloorIsLavaRules.maxRounds(difficulty)).toBe(expected);
33
- });
34
- });
35
-
36
- describe('FloorIsLavaRules.tilesRemaining', () => {
37
- it('returns the full board when no rounds have been survived', () => {
38
- expect(FloorIsLavaRules.tilesRemaining(0, 6)).toBe(BOARD_SIZE);
39
- });
40
-
41
- it('subtracts dropped tiles for the rounds survived so far', () => {
42
- expect(FloorIsLavaRules.tilesRemaining(2, 6)).toBe(BOARD_SIZE - 12);
43
- });
44
- });
45
-
46
- describe('FloorIsLavaRules.isValidTile', () => {
47
- it.each([0, 1, BOARD_SIZE - 1])('accepts an in-range integer tile (%s)', tile => {
48
- expect(FloorIsLavaRules.isValidTile(tile)).toBe(true);
49
- });
50
-
51
- it.each([-1, BOARD_SIZE, 1.5, NaN, Infinity])('rejects an out-of-range or non-integer tile (%s)', tile => {
52
- expect(FloorIsLavaRules.isValidTile(tile)).toBe(false);
53
- });
54
- });
55
-
56
- describe('FloorIsLavaRules.multiplier', () => {
57
- it('scales the edge multiplier by board size over standing tiles', () => {
58
- const expected = calculateEdgeMultiplier(EDGE_BPS)
59
- .multipliedBy(BOARD_SIZE)
60
- .dividedBy(BOARD_SIZE - 6);
61
- expect(FloorIsLavaRules.multiplier(1, 6, EDGE_BPS).isEqualTo(expected)).toBe(true);
62
- });
63
- });
64
-
65
- describe('FloorIsLavaRules.getRoundResults', () => {
66
- it('chunks the drawn sequence into per-round groups of dropsPerRound', () => {
67
- const rng = new FixedSequenceRng([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
68
- expect(FloorIsLavaRules.getRoundResults(rng, 4, 3)).toEqual([
69
- [0, 1, 2, 3],
70
- [4, 5, 6, 7],
71
- [8, 9, 10, 11],
72
- ]);
73
- });
74
- });
75
-
76
- describe('FloorIsLavaRules.lastSurvivingTile', () => {
77
- it('returns the final tile of the full-board drop order — the last to fall', () => {
78
- const champion = 7;
79
- const dropOrder = [...Array.from({ length: BOARD_SIZE }, (_, i) => i).filter(tile => tile !== champion), champion];
80
- expect(FloorIsLavaRules.lastSurvivingTile(new FixedSequenceRng(dropOrder))).toBe(champion);
81
- });
82
- });
83
-
84
- describe('FloorIsLavaRules.reconstructRounds', () => {
85
- it('counts the PICK actions following START as survived rounds', () => {
86
- const actions = [startAction(FloorIsLavaDifficulty.MEDIUM), pickAction(), pickAction()];
87
- expect(FloorIsLavaRules.reconstructRounds(actions)).toEqual({
88
- roundsSurvived: 2,
89
- dropsPerRound: DIFFICULTY_DROP_MAP[FloorIsLavaDifficulty.MEDIUM],
90
- });
91
- });
92
-
93
- it('throws when the first action is not START', () => {
94
- expect(() => FloorIsLavaRules.reconstructRounds([pickAction()])).toThrow('First action must be START');
95
- });
96
-
97
- it('throws when a START action appears after the first position', () => {
98
- expect(() => FloorIsLavaRules.reconstructRounds([startAction(FloorIsLavaDifficulty.MEDIUM), startAction(FloorIsLavaDifficulty.MEDIUM)])).toThrow(
99
- 'START action can only appear as the first action',
100
- );
101
- });
102
-
103
- it('throws when a CASHOUT action appears in the history', () => {
104
- expect(() =>
105
- FloorIsLavaRules.reconstructRounds([
106
- startAction(FloorIsLavaDifficulty.MEDIUM),
107
- { phase: FloorIsLavaActionPhase.CASHOUT, multiplier: new BigNumber(1), lastSurvivingTile: 0 },
108
- ]),
109
- ).toThrow('An active game cannot have a CASHOUT action');
110
- });
111
-
112
- it('throws when a BUST_PICK action appears in the history', () => {
113
- expect(() =>
114
- FloorIsLavaRules.reconstructRounds([
115
- startAction(FloorIsLavaDifficulty.MEDIUM),
116
- { phase: FloorIsLavaActionPhase.BUST_PICK, selectedTile: 0, droppedTiles: [], lastSurvivingTile: 1 },
117
- ]),
118
- ).toThrow('An active game cannot have a BUST_PICK action');
119
- });
120
-
121
- it('throws when survived rounds exceed the difficulty max', () => {
122
- expect(() => FloorIsLavaRules.reconstructRounds([startAction(FloorIsLavaDifficulty.TOXIC), pickAction(), pickAction()])).toThrow(
123
- 'Reconstructed rounds (2) exceeds max rounds (1)',
124
- );
125
- });
126
- });
@@ -1,125 +0,0 @@
1
- import BigNumber from 'bignumber.js';
2
- import { calculateEdgeMultiplier } from '../utils/edge';
3
- import { RandomNumberGenerator } from './random-number-generator.interface';
4
-
5
- export const BOARD_SIZE = 49;
6
- export const DROPPABLE_TILES = BOARD_SIZE - 1;
7
-
8
- export enum FloorIsLavaDifficulty {
9
- EASY = 'EASY',
10
- MEDIUM = 'MEDIUM',
11
- HARD = 'HARD',
12
- TOXIC = 'TOXIC',
13
- }
14
-
15
- export const DIFFICULTY_DROP_MAP: Readonly<Record<FloorIsLavaDifficulty, number>> = {
16
- [FloorIsLavaDifficulty.EASY]: 4,
17
- [FloorIsLavaDifficulty.MEDIUM]: 6,
18
- [FloorIsLavaDifficulty.HARD]: 8,
19
- [FloorIsLavaDifficulty.TOXIC]: 48,
20
- };
21
-
22
- export const MAX_ROUNDS = DROPPABLE_TILES / Math.min(...Object.values(DIFFICULTY_DROP_MAP));
23
-
24
- export enum FloorIsLavaActionPhase {
25
- START = 'START',
26
- SUCCESSFUL_PICK = 'SUCCESSFUL_PICK',
27
- BUST_PICK = 'BUST_PICK',
28
- CASHOUT = 'CASHOUT',
29
- }
30
-
31
- export interface FloorIsLavaStartAction {
32
- phase: FloorIsLavaActionPhase.START;
33
- difficulty: FloorIsLavaDifficulty;
34
- }
35
-
36
- export interface FloorIsLavaSuccessfulPickAction {
37
- phase: FloorIsLavaActionPhase.SUCCESSFUL_PICK;
38
- selectedTile: number;
39
- droppedTiles: number[];
40
- multiplier: BigNumber;
41
- }
42
-
43
- export interface FloorIsLavaBustPickAction {
44
- phase: FloorIsLavaActionPhase.BUST_PICK;
45
- selectedTile: number;
46
- droppedTiles: number[];
47
- lastSurvivingTile: number;
48
- }
49
-
50
- export interface FloorIsLavaCashoutAction {
51
- phase: FloorIsLavaActionPhase.CASHOUT;
52
- multiplier: BigNumber;
53
- lastSurvivingTile: number;
54
- }
55
-
56
- export type FloorIsLavaGameAction =
57
- | FloorIsLavaStartAction
58
- | FloorIsLavaSuccessfulPickAction
59
- | FloorIsLavaBustPickAction
60
- | FloorIsLavaCashoutAction;
61
-
62
- export class FloorIsLavaRules {
63
- public static maxRounds(difficulty: FloorIsLavaDifficulty): number {
64
- return DROPPABLE_TILES / DIFFICULTY_DROP_MAP[difficulty];
65
- }
66
-
67
- public static tilesRemaining(roundsSurvived: number, dropsPerRound: number): number {
68
- return BOARD_SIZE - roundsSurvived * dropsPerRound;
69
- }
70
-
71
- public static isValidTile(tile: number): boolean {
72
- return Number.isInteger(tile) && tile >= 0 && tile <= BOARD_SIZE - 1;
73
- }
74
-
75
- // RTP * 49 / surviving tiles = edge multiplier
76
- public static multiplier(roundsSurvived: number, dropsPerRound: number, edge: number): BigNumber {
77
- return calculateEdgeMultiplier(edge).multipliedBy(BOARD_SIZE).dividedBy(FloorIsLavaRules.tilesRemaining(roundsSurvived, dropsPerRound));
78
- }
79
-
80
- public static lastSurvivingTile(generator: RandomNumberGenerator): number {
81
- const dropOrder = generator.getRandomIntsWithoutReplacement({ min: 0, max: BOARD_SIZE - 1, count: BOARD_SIZE });
82
- return dropOrder[dropOrder.length - 1];
83
- }
84
-
85
- public static getRoundResults(generator: RandomNumberGenerator, dropsPerRound: number, numRounds: number): number[][] {
86
- const dropSequence = generator.getRandomIntsWithoutReplacement({ min: 0, max: BOARD_SIZE - 1, count: numRounds * dropsPerRound });
87
-
88
- const rounds: number[][] = [];
89
- for (let round = 0; round < numRounds; round++) {
90
- rounds.push(dropSequence.slice(round * dropsPerRound, (round + 1) * dropsPerRound));
91
- }
92
- return rounds;
93
- }
94
-
95
- public static reconstructRounds(gameActions: FloorIsLavaGameAction[]): { roundsSurvived: number; dropsPerRound: number } {
96
- const first = gameActions[0];
97
- if (!first || first.phase !== FloorIsLavaActionPhase.START) {
98
- throw new Error('First action must be START');
99
- }
100
-
101
- const dropsPerRound = DIFFICULTY_DROP_MAP[first.difficulty];
102
- const maxRounds = FloorIsLavaRules.maxRounds(first.difficulty);
103
- let roundsSurvived = 0;
104
-
105
- for (let i = 1; i < gameActions.length; i++) {
106
- const action = gameActions[i];
107
- switch (action.phase) {
108
- case FloorIsLavaActionPhase.START:
109
- throw new Error('START action can only appear as the first action');
110
- case FloorIsLavaActionPhase.SUCCESSFUL_PICK:
111
- roundsSurvived++;
112
- if (roundsSurvived > maxRounds) {
113
- throw new Error(`Reconstructed rounds (${roundsSurvived}) exceeds max rounds (${maxRounds})`);
114
- }
115
- break;
116
- case FloorIsLavaActionPhase.BUST_PICK:
117
- throw new Error('An active game cannot have a BUST_PICK action');
118
- case FloorIsLavaActionPhase.CASHOUT:
119
- throw new Error('An active game cannot have a CASHOUT action');
120
- }
121
- }
122
-
123
- return { roundsSurvived, dropsPerRound };
124
- }
125
- }