shufflecom-calculations 4.3.2 → 4.3.3
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/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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shufflecom-calculations",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.3",
|
|
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": "3d5d2ae67ace14d80a6d16becc6a2107d7f15080"
|
|
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 {
|