shufflecom-calculations 4.3.2 → 4.4.0
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.
- package/lib/games/blitz.d.ts +2 -2
- package/lib/games/blitz.js +23 -15
- package/lib/games/blitz.js.map +1 -1
- package/lib/sports/sports-competition-fixture.types.js.map +1 -1
- package/lib/sports/sports-content-home-page.types.d.ts +17 -0
- package/lib/sports/sports-content-home-page.types.js +10 -0
- package/lib/sports/sports-content-home-page.types.js.map +1 -0
- package/lib/sports/sports-selection.types.d.ts +8 -0
- package/lib/sports/sports-selection.types.js +4 -0
- package/lib/sports/sports-selection.types.js.map +1 -1
- package/lib/sports/sports.types.d.ts +15 -0
- package/lib/sports/sports.types.js +15 -2
- package/lib/sports/sports.types.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/games/blitz.spec.ts +50 -21
- package/src/games/blitz.ts +25 -15
- package/src/sports/sports-competition-fixture.types.ts +0 -1
- package/src/sports/sports-content-home-page.types.ts +20 -0
- package/src/sports/sports-selection.types.ts +10 -0
- package/src/sports/sports.types.ts +17 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shufflecom-calculations",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
},
|
|
15
15
|
"author": "",
|
|
16
16
|
"license": "ISC",
|
|
17
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "12c4928ea14e1a42051cac1343a075debff13562"
|
|
18
18
|
}
|
package/src/games/blitz.spec.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHmac } from 'crypto';
|
|
2
2
|
import { Blitz } from './blitz';
|
|
3
|
+
import { CardGames } from './cardGames';
|
|
3
4
|
|
|
4
5
|
const SERVER_SEED = 'ebd3ff712ee7ed3cabe3753146f95847b017f847758ed254c531ff0d45686cea';
|
|
5
6
|
const CLIENT_SEED = 'pf4jl5q97q';
|
|
@@ -47,36 +48,20 @@ function generateDigestHex(nonce: string, rounds: number): string[] {
|
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
describe('Blitz.getGameSeedRounds', () => {
|
|
51
|
-
it('returns 1 round for picks 1–8', () => {
|
|
52
|
-
expect(Blitz.getGameSeedRounds(5)).toBe(1);
|
|
53
|
-
expect(Blitz.getGameSeedRounds(8)).toBe(1);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('returns 2 rounds for picks 9–16', () => {
|
|
57
|
-
expect(Blitz.getGameSeedRounds(9)).toBe(2);
|
|
58
|
-
expect(Blitz.getGameSeedRounds(16)).toBe(2);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('returns 4 rounds for 30 picks (max)', () => {
|
|
62
|
-
expect(Blitz.getGameSeedRounds(30)).toBe(4);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
51
|
describe('Blitz.getGameOutcome', () => {
|
|
67
52
|
it('returns the correct number of cards', () => {
|
|
68
|
-
const hexStrings = generateDigestHex('1', Blitz.
|
|
53
|
+
const hexStrings = generateDigestHex('1', Blitz.getMaxUniqueCardsSeedRounds());
|
|
69
54
|
const { cards } = Blitz.getGameOutcome(hexStrings, 10);
|
|
70
55
|
expect(cards).toHaveLength(10);
|
|
71
56
|
|
|
72
|
-
const hexStrings2 = generateDigestHex('2', Blitz.
|
|
57
|
+
const hexStrings2 = generateDigestHex('2', Blitz.getMaxUniqueCardsSeedRounds());
|
|
73
58
|
const { cards: cards2 } = Blitz.getGameOutcome(hexStrings2, 25);
|
|
74
59
|
expect(cards2).toHaveLength(25);
|
|
75
60
|
});
|
|
76
61
|
|
|
77
62
|
it('maxCount is 1 when all cards are unique', () => {
|
|
78
63
|
// Force a known set of unique cards by checking the outcome
|
|
79
|
-
const hexStrings = generateDigestHex('1', Blitz.
|
|
64
|
+
const hexStrings = generateDigestHex('1', Blitz.getMaxUniqueCardsSeedRounds());
|
|
80
65
|
const { maxRepeatedCount, cards } = Blitz.getGameOutcome(hexStrings, 5);
|
|
81
66
|
const uniqueCount = new Set(cards).size;
|
|
82
67
|
if (uniqueCount === 5) {
|
|
@@ -87,15 +72,59 @@ describe('Blitz.getGameOutcome', () => {
|
|
|
87
72
|
});
|
|
88
73
|
|
|
89
74
|
it('maxCount reflects the highest duplicate count', () => {
|
|
90
|
-
const hexStrings = generateDigestHex('5', Blitz.
|
|
75
|
+
const hexStrings = generateDigestHex('5', Blitz.getMaxUniqueCardsSeedRounds());
|
|
91
76
|
const { maxRepeatedCount } = Blitz.getGameOutcome(hexStrings, 30);
|
|
92
77
|
|
|
93
78
|
expect(maxRepeatedCount).toBe(2);
|
|
94
79
|
|
|
95
|
-
const hexStrings2 = generateDigestHex('6', Blitz.
|
|
80
|
+
const hexStrings2 = generateDigestHex('6', Blitz.getMaxUniqueCardsSeedRounds());
|
|
96
81
|
const { maxRepeatedCount: maxRepeatedCount2 } = Blitz.getGameOutcome(hexStrings2, 30);
|
|
97
82
|
expect(maxRepeatedCount2).toBe(3);
|
|
98
83
|
});
|
|
84
|
+
|
|
85
|
+
describe('maxUniqueCardsBeforeDuplicate', () => {
|
|
86
|
+
function bruteForceUniqueCountBeforeDuplicate(hexStrings: string[]): number {
|
|
87
|
+
const cards = CardGames.getResults(hexStrings);
|
|
88
|
+
const seen = new Set<number>();
|
|
89
|
+
|
|
90
|
+
for (const card of cards) {
|
|
91
|
+
if (seen.has(card)) return seen.size;
|
|
92
|
+
seen.add(card);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return seen.size;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
it('matches a brute-force scan of all drawn cards for several nonces', () => {
|
|
99
|
+
for (const nonce of ['1', '2', '5', '6', '42']) {
|
|
100
|
+
const hexStrings = generateDigestHex(nonce, Blitz.getMaxUniqueCardsSeedRounds());
|
|
101
|
+
const { maxUniqueCardsBeforeDuplicate } = Blitz.getGameOutcome(hexStrings, 5);
|
|
102
|
+
expect(maxUniqueCardsBeforeDuplicate).toBe(bruteForceUniqueCountBeforeDuplicate(hexStrings));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('never exceeds the number of distinct cards in a deck', () => {
|
|
107
|
+
for (const nonce of ['1', '2', '5', '6', '42']) {
|
|
108
|
+
const hexStrings = generateDigestHex(nonce, Blitz.getMaxUniqueCardsSeedRounds());
|
|
109
|
+
const { maxUniqueCardsBeforeDuplicate } = Blitz.getGameOutcome(hexStrings, 5);
|
|
110
|
+
expect(maxUniqueCardsBeforeDuplicate).toBeLessThanOrEqual(52);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('is independent of picks, since it scans the full drawn card sequence', () => {
|
|
115
|
+
const hexStrings = generateDigestHex('1', Blitz.getMaxUniqueCardsSeedRounds());
|
|
116
|
+
const outcomeA = Blitz.getGameOutcome(hexStrings, 5);
|
|
117
|
+
const outcomeB = Blitz.getGameOutcome(hexStrings, 30);
|
|
118
|
+
|
|
119
|
+
expect(outcomeA.maxUniqueCardsBeforeDuplicate).toBe(outcomeB.maxUniqueCardsBeforeDuplicate);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('Blitz.getMaxUniqueCardsSeedRounds', () => {
|
|
125
|
+
it('returns enough rounds to guarantee a duplicate within a 52-card deck', () => {
|
|
126
|
+
expect(Blitz.getMaxUniqueCardsSeedRounds()).toBe(7);
|
|
127
|
+
});
|
|
99
128
|
});
|
|
100
129
|
|
|
101
130
|
describe('Blitz.isCleanSweepWin', () => {
|
package/src/games/blitz.ts
CHANGED
|
@@ -8,36 +8,46 @@ export const BLITZ_MAX_PICKS = 36;
|
|
|
8
8
|
export interface BlitzResult {
|
|
9
9
|
maxRepeatedCount: number;
|
|
10
10
|
cards: number[];
|
|
11
|
+
maxUniqueCardsBeforeDuplicate: number;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export class Blitz {
|
|
14
|
-
static
|
|
15
|
-
return Math.ceil(
|
|
15
|
+
static getMaxUniqueCardsSeedRounds(): number {
|
|
16
|
+
return Math.ceil((CARDS_PER_DECK + 1) / 8);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
const
|
|
19
|
+
static getGameOutcome(hexStrings: string[], picks: number): BlitzResult {
|
|
20
|
+
const allCards = CardGames.getResults(hexStrings);
|
|
20
21
|
|
|
21
|
-
if (
|
|
22
|
+
if (allCards.length < picks) {
|
|
22
23
|
throw new Error('Not enough cards drawn');
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
return cards.slice(0, picks);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static getGameOutcome(hexStrings: string[], picks: number): BlitzResult {
|
|
29
|
-
const cards = this.drawCards(hexStrings, picks);
|
|
30
26
|
const cardCounts: Record<number, number> = {};
|
|
31
27
|
let maxRepeatedCount = 0;
|
|
28
|
+
let maxUniqueCardsBeforeDuplicate = 0;
|
|
29
|
+
let duplicateFound = false;
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < allCards.length; i++) {
|
|
32
|
+
const card = allCards[i];
|
|
33
|
+
const isNewCard = !(card in cardCounts);
|
|
34
|
+
|
|
35
|
+
cardCounts[card] = (cardCounts[card] || 0) + 1;
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
if (i < picks && cardCounts[card] > maxRepeatedCount) {
|
|
38
|
+
maxRepeatedCount = cardCounts[card];
|
|
39
|
+
}
|
|
36
40
|
|
|
37
|
-
if (
|
|
41
|
+
if (!duplicateFound) {
|
|
42
|
+
if (isNewCard) {
|
|
43
|
+
maxUniqueCardsBeforeDuplicate++;
|
|
44
|
+
} else {
|
|
45
|
+
duplicateFound = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
return { maxRepeatedCount, cards };
|
|
50
|
+
return { maxRepeatedCount, cards: allCards.slice(0, picks), maxUniqueCardsBeforeDuplicate };
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
static calculateWinChanceBN(picks: number): BigNumber {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PrebuildBetFeaturedPrebuilds } from './prebuild-bet.types';
|
|
2
|
+
import { PopularSportsCompetitionsV2Info } from './sports-competition-fixture.types';
|
|
3
|
+
import { SportsContentFeaturedEvents, SportsContentFeaturedOutright } from './sports-content-fixture.types';
|
|
4
|
+
import { PaginatedSportsBoostedSelectionsInfo } from './sports-selection.types';
|
|
5
|
+
import { Nullable, SportsContentType } from './sports.types';
|
|
6
|
+
|
|
7
|
+
export class SportsContentWeight {
|
|
8
|
+
sportsContentType: SportsContentType;
|
|
9
|
+
weight: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SportsContentHomePage {
|
|
13
|
+
featuredEvents: Nullable<SportsContentFeaturedEvents>;
|
|
14
|
+
featuredOutright: Nullable<SportsContentFeaturedOutright>;
|
|
15
|
+
popularCompetitions: Nullable<PopularSportsCompetitionsV2Info>;
|
|
16
|
+
featuredPrebuilds: Nullable<PrebuildBetFeaturedPrebuilds>;
|
|
17
|
+
sportsCustomBoosts: Nullable<PaginatedSportsBoostedSelectionsInfo>;
|
|
18
|
+
/** Enabled content types with their weights, ordered by weight DESC. */
|
|
19
|
+
weights: SportsContentWeight[];
|
|
20
|
+
}
|
|
@@ -27,6 +27,7 @@ export type SportsMarketSelectionType = {
|
|
|
27
27
|
name: string;
|
|
28
28
|
isOutrightLike: boolean;
|
|
29
29
|
shortName: string;
|
|
30
|
+
startTime: string;
|
|
30
31
|
};
|
|
31
32
|
competition: {
|
|
32
33
|
id: string;
|
|
@@ -41,3 +42,12 @@ export type SportsMarketSelectionType = {
|
|
|
41
42
|
};
|
|
42
43
|
provider: SportsMarketProvider;
|
|
43
44
|
};
|
|
45
|
+
|
|
46
|
+
export type SportsCustomBoostMarketSelectionType = SportsMarketSelectionType & {
|
|
47
|
+
boost: SportsMarketSelectionOddsBoost;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export class PaginatedSportsBoostedSelectionsInfo {
|
|
51
|
+
nodes: SportsCustomBoostMarketSelectionType[];
|
|
52
|
+
nextCursor: string | null;
|
|
53
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import BigNumber from 'bignumber.js';
|
|
2
2
|
import { SportsMatchPhaseLabel } from './sports-match-state.types';
|
|
3
|
+
import { SportsCustomBoostMarketSelectionType } from './sports-selection.types';
|
|
3
4
|
|
|
4
5
|
export enum SportsMarketProvider {
|
|
5
6
|
SHUFFLE = 'SHUFFLE',
|
|
@@ -124,6 +125,20 @@ export enum SportsFixtureBannerType {
|
|
|
124
125
|
SINGLE_ELIMINATION = 'SINGLE_ELIMINATION',
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
/** SPORT_1..SPORT_6 are positional slots on the home page; each holds one popular sport's competitions. */
|
|
129
|
+
export enum SportsContentType {
|
|
130
|
+
HOME_FEATURED_EVENT = 'HOME_FEATURED_EVENT',
|
|
131
|
+
FEATURED_CUSTOM_BETS = 'FEATURED_CUSTOM_BETS',
|
|
132
|
+
FEATURED_OUTRIGHT = 'FEATURED_OUTRIGHT',
|
|
133
|
+
FEATURED_BOOSTS = 'FEATURED_BOOSTS',
|
|
134
|
+
SPORT_1 = 'SPORT_1',
|
|
135
|
+
SPORT_2 = 'SPORT_2',
|
|
136
|
+
SPORT_3 = 'SPORT_3',
|
|
137
|
+
SPORT_4 = 'SPORT_4',
|
|
138
|
+
SPORT_5 = 'SPORT_5',
|
|
139
|
+
SPORT_6 = 'SPORT_6',
|
|
140
|
+
}
|
|
141
|
+
|
|
127
142
|
export enum SportsMarketSelectionTeam {
|
|
128
143
|
HOME = 'HOME',
|
|
129
144
|
AWAY = 'AWAY',
|
|
@@ -665,6 +680,7 @@ export class DisplayGroup extends BaseDisplayGroup {
|
|
|
665
680
|
groupDisplayType: SportsGroupedSelectionsDisplayType;
|
|
666
681
|
playerMarkets: PlayerMarket[] | null;
|
|
667
682
|
groups: Partial<Record<SportsMarketGroup, number>> | null; // For SGM sub-groups
|
|
683
|
+
cmsDescriptionSlug: string | null;
|
|
668
684
|
}
|
|
669
685
|
|
|
670
686
|
export class SelectionGroup {
|
|
@@ -726,6 +742,7 @@ export class SgmSubGroup {
|
|
|
726
742
|
export class SportsMarketInfo {
|
|
727
743
|
odds: SportsNewMarketOddsUpdate[];
|
|
728
744
|
display: DisplayGroup[];
|
|
745
|
+
customBoosts: SportsCustomBoostMarketSelectionType[];
|
|
729
746
|
updatedAt: Nullable<Date>;
|
|
730
747
|
sgmSubGroups: Nullable<SgmSubGroup[]>;
|
|
731
748
|
}
|