@dimcool/sdk 0.1.30 → 0.1.32
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/README.md +55 -45
- package/dist/index.cjs +229 -50
- package/dist/index.d.cts +62 -20
- package/dist/index.d.ts +62 -20
- package/dist/index.js +229 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1572,6 +1572,8 @@ const newFlag = await sdk.featureFlags.createFeatureFlag('new-feature', false);
|
|
|
1572
1572
|
|
|
1573
1573
|
Create a new game lobby.
|
|
1574
1574
|
|
|
1575
|
+
> **Note:** Creating a lobby automatically closes and refunds any existing open or queued lobby for this user. You do not need to manually leave or cancel a previous lobby first.
|
|
1576
|
+
|
|
1575
1577
|
```typescript
|
|
1576
1578
|
const lobby = await sdk.lobbies.createLobby('rock-paper-scissors');
|
|
1577
1579
|
// lobby: { id, gameType, status, creatorId, maxPlayers, players: [...], ... }
|
|
@@ -1729,51 +1731,6 @@ const lobby = await sdk.lobbies.cancelQueue('lobby-id');
|
|
|
1729
1731
|
|
|
1730
1732
|
**Note:** The queue is automatically cancelled if any player leaves the lobby while it's in queue.
|
|
1731
1733
|
|
|
1732
|
-
#### `playAgain(gameType: string, betAmount: number, escrow: Escrow): Promise<{ lobby: Lobby; unsignedTransaction: string }>`
|
|
1733
|
-
|
|
1734
|
-
Create a new lobby, start deposits, and prepare a deposit transaction for playing again with the same bet amount. This is a convenience method that combines lobby creation, deposit initialization, and transaction preparation.
|
|
1735
|
-
|
|
1736
|
-
```typescript
|
|
1737
|
-
const { lobby, unsignedTransaction } = await sdk.lobbies.playAgain(
|
|
1738
|
-
'rock-paper-scissors',
|
|
1739
|
-
25,
|
|
1740
|
-
sdk.escrow,
|
|
1741
|
-
);
|
|
1742
|
-
```
|
|
1743
|
-
|
|
1744
|
-
**Parameters:**
|
|
1745
|
-
|
|
1746
|
-
- `gameType: string` - The game type to play again (e.g., 'rock-paper-scissors')
|
|
1747
|
-
- `betAmount: number` - The bet amount (same as previous game)
|
|
1748
|
-
- `escrow: Escrow` - The escrow service instance (from `sdk.escrow`)
|
|
1749
|
-
|
|
1750
|
-
**Returns:**
|
|
1751
|
-
|
|
1752
|
-
- `Promise<{ lobby: Lobby; unsignedTransaction: string }>` - The created lobby and unsigned transaction (base64 encoded) that needs to be signed
|
|
1753
|
-
|
|
1754
|
-
**Behavior:**
|
|
1755
|
-
|
|
1756
|
-
1. Creates a new lobby with the specified game type and bet amount
|
|
1757
|
-
2. Starts the deposit flow (transitions lobby to 'preparing' state)
|
|
1758
|
-
3. Prepares the deposit transaction
|
|
1759
|
-
4. Returns the lobby and unsigned transaction
|
|
1760
|
-
|
|
1761
|
-
**Example:**
|
|
1762
|
-
|
|
1763
|
-
```typescript
|
|
1764
|
-
// Play again with same bet amount
|
|
1765
|
-
const { lobby, unsignedTransaction } = await sdk.lobbies.playAgain(
|
|
1766
|
-
'rock-paper-scissors',
|
|
1767
|
-
25,
|
|
1768
|
-
sdk.escrow,
|
|
1769
|
-
);
|
|
1770
|
-
|
|
1771
|
-
// Deposit and wait for your own deposit to confirm (polls status endpoint)
|
|
1772
|
-
const result = await sdk.escrow.depositForLobbySync(lobby.id);
|
|
1773
|
-
```
|
|
1774
|
-
|
|
1775
|
-
**Note:** After `playAgain()`, use `depositForLobbySync()` (for agents) or `depositForLobby()` (for React hooks) to complete the deposit flow. The server auto-joins the matchmaking queue when all deposits are confirmed.
|
|
1776
|
-
|
|
1777
1734
|
### Admin Methods
|
|
1778
1735
|
|
|
1779
1736
|
#### `getActiveLobbies(): Promise<Lobby[]>` (Admin Only)
|
|
@@ -2971,6 +2928,59 @@ const sdk = new SDK({
|
|
|
2971
2928
|
These stores are updated by the `WsRouter` and are the single source of truth for
|
|
2972
2929
|
realtime UI. Room membership is ref-counted and mapped to backend join/leave emits.
|
|
2973
2930
|
|
|
2931
|
+
### `gameActionsStore` Selectors
|
|
2932
|
+
|
|
2933
|
+
Pure functions that extract derived state from `GameActionsStoreState` without any React dependency. Pass them to `useSdkSelector` in React or call them directly in Node.
|
|
2934
|
+
|
|
2935
|
+
#### `selectGameLifecycleState(gameState)`
|
|
2936
|
+
|
|
2937
|
+
Extracts the common lifecycle fields from any `GameStateResponse`. Accepts the state directly so it can be called after a stable `useSdkSelector` call — the selector itself returns the existing store reference, then you project outside it. This avoids creating a new object inside `getSnapshot`, which would break `useSyncExternalStore`'s referential-equality contract.
|
|
2938
|
+
|
|
2939
|
+
```ts
|
|
2940
|
+
import { selectGameLifecycleState } from '@dimcool/sdk';
|
|
2941
|
+
|
|
2942
|
+
// Correct: selector returns stable store ref, projection happens outside
|
|
2943
|
+
const rawState = useSdkSelector(store, (s) => s.statesByGameId[gameId] ?? null);
|
|
2944
|
+
const lifecycle = selectGameLifecycleState(rawState);
|
|
2945
|
+
// { status, winnerId, betAmount, wonAmount, totalPotMinor, currentPlayerId }
|
|
2946
|
+
```
|
|
2947
|
+
|
|
2948
|
+
#### `getCountdownDigit(gameId, nowMs)`
|
|
2949
|
+
|
|
2950
|
+
Returns the current pre-game countdown digit (`3 | 2 | 1`) or `null` when the buffer has elapsed. Works for all game types (turn-based: reads `bufferEndsAt`; RPS: reads `roundState.selectionEndsAt` during the `starting` phase).
|
|
2951
|
+
|
|
2952
|
+
```ts
|
|
2953
|
+
const digit = sdk.gameActionsStore.getCountdownDigit(gameId, Date.now());
|
|
2954
|
+
```
|
|
2955
|
+
|
|
2956
|
+
#### `getChessClockTimes(gameId, nowMs)` / `getTicTacToeClockTimes` / `getConnect4ClockTimes`
|
|
2957
|
+
|
|
2958
|
+
Returns live per-color clock values deducting elapsed time from the current player's budget.
|
|
2959
|
+
|
|
2960
|
+
```ts
|
|
2961
|
+
const clocks = sdk.gameActionsStore.getChessClockTimes(gameId, Date.now());
|
|
2962
|
+
// { whiteTimeMs, blackTimeMs }
|
|
2963
|
+
|
|
2964
|
+
const tttClocks = sdk.gameActionsStore.getTicTacToeClockTimes(
|
|
2965
|
+
gameId,
|
|
2966
|
+
Date.now(),
|
|
2967
|
+
);
|
|
2968
|
+
// { xTimeMs, oTimeMs }
|
|
2969
|
+
|
|
2970
|
+
const c4Clocks = sdk.gameActionsStore.getConnect4ClockTimes(gameId, Date.now());
|
|
2971
|
+
// { redTimeMs, yellowTimeMs }
|
|
2972
|
+
```
|
|
2973
|
+
|
|
2974
|
+
#### `getChessCapturedPieces(gameId)`
|
|
2975
|
+
|
|
2976
|
+
Derives captured pieces from the FEN placement string — no external chess library needed.
|
|
2977
|
+
|
|
2978
|
+
```ts
|
|
2979
|
+
const pieces = sdk.gameActionsStore.getChessCapturedPieces(gameId);
|
|
2980
|
+
// { capturedByWhite: string[], capturedByBlack: string[] }
|
|
2981
|
+
// e.g. capturedByWhite: ['bp', 'bp', 'br'] means white captured 2 black pawns + 1 rook
|
|
2982
|
+
```
|
|
2983
|
+
|
|
2974
2984
|
### Event Bus (low-level)
|
|
2975
2985
|
|
|
2976
2986
|
If you need raw event streams, use `sdk.events.subscribe(eventName, handler)`.
|
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var require_money = __commonJS({
|
|
|
35
35
|
"../utils/dist/money.js"(exports2) {
|
|
36
36
|
"use strict";
|
|
37
37
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
38
|
-
exports2.SOL_LAMPORTS = exports2.MICRO_UNITS = void 0;
|
|
38
|
+
exports2.SOL_MINT = exports2.SOL_LAMPORTS = exports2.MICRO_UNITS = void 0;
|
|
39
39
|
exports2.isSolMint = isSolMint;
|
|
40
40
|
exports2.formatMoneyMinor = formatMoneyMinor2;
|
|
41
41
|
exports2.toMajor = toMajor2;
|
|
@@ -48,12 +48,12 @@ var require_money = __commonJS({
|
|
|
48
48
|
exports2.formatMoneyMinorCompact = formatMoneyMinorCompact;
|
|
49
49
|
exports2.MICRO_UNITS = 1e6;
|
|
50
50
|
exports2.SOL_LAMPORTS = 1e9;
|
|
51
|
-
|
|
51
|
+
exports2.SOL_MINT = "SOL";
|
|
52
52
|
var SOL_SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
53
53
|
function isSolMint(mint) {
|
|
54
54
|
if (!mint)
|
|
55
55
|
return false;
|
|
56
|
-
return mint ===
|
|
56
|
+
return mint === exports2.SOL_MINT || mint === SOL_SYSTEM_PROGRAM;
|
|
57
57
|
}
|
|
58
58
|
function formatMoneyMinor2(amountMinor, fractionDigits = 2) {
|
|
59
59
|
return (amountMinor / exports2.MICRO_UNITS).toFixed(fractionDigits);
|
|
@@ -159,7 +159,7 @@ var require_dist = __commonJS({
|
|
|
159
159
|
"../utils/dist/index.js"(exports2) {
|
|
160
160
|
"use strict";
|
|
161
161
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
162
|
-
exports2.formatMoneyMinorCompact = exports2.formatCurrencyPartsForToken = exports2.formatCurrencyParts = exports2.kformatMoney = exports2.kformat = exports2.formatSolMinor = exports2.toSolMajor = exports2.toMajor = exports2.formatMoneyMinor = exports2.isSolMint = exports2.SOL_LAMPORTS = exports2.MICRO_UNITS = void 0;
|
|
162
|
+
exports2.formatMoneyMinorCompact = exports2.formatCurrencyPartsForToken = exports2.formatCurrencyParts = exports2.kformatMoney = exports2.kformat = exports2.formatSolMinor = exports2.toSolMajor = exports2.toMajor = exports2.formatMoneyMinor = exports2.isSolMint = exports2.SOL_MINT = exports2.SOL_LAMPORTS = exports2.MICRO_UNITS = void 0;
|
|
163
163
|
var money_1 = require_money();
|
|
164
164
|
Object.defineProperty(exports2, "MICRO_UNITS", { enumerable: true, get: function() {
|
|
165
165
|
return money_1.MICRO_UNITS;
|
|
@@ -167,6 +167,9 @@ var require_dist = __commonJS({
|
|
|
167
167
|
Object.defineProperty(exports2, "SOL_LAMPORTS", { enumerable: true, get: function() {
|
|
168
168
|
return money_1.SOL_LAMPORTS;
|
|
169
169
|
} });
|
|
170
|
+
Object.defineProperty(exports2, "SOL_MINT", { enumerable: true, get: function() {
|
|
171
|
+
return money_1.SOL_MINT;
|
|
172
|
+
} });
|
|
170
173
|
Object.defineProperty(exports2, "isSolMint", { enumerable: true, get: function() {
|
|
171
174
|
return money_1.isSolMint;
|
|
172
175
|
} });
|
|
@@ -209,7 +212,7 @@ __export(index_exports, {
|
|
|
209
212
|
ESTIMATED_SOL_FEE_LAMPORTS: () => ESTIMATED_SOL_FEE_LAMPORTS,
|
|
210
213
|
HttpClient: () => HttpClient,
|
|
211
214
|
Leaderboards: () => Leaderboards,
|
|
212
|
-
MICRO_UNITS: () =>
|
|
215
|
+
MICRO_UNITS: () => import_utils2.MICRO_UNITS,
|
|
213
216
|
MIN_SOL_TRANSFER_AMOUNT: () => MIN_SOL_TRANSFER_AMOUNT,
|
|
214
217
|
MIN_TRANSFER_AMOUNT: () => MIN_TRANSFER_AMOUNT,
|
|
215
218
|
Markets: () => Markets,
|
|
@@ -220,7 +223,7 @@ __export(index_exports, {
|
|
|
220
223
|
SDK: () => SDK,
|
|
221
224
|
SDK_VERSION: () => SDK_VERSION,
|
|
222
225
|
SOL_DECIMALS: () => SOL_DECIMALS,
|
|
223
|
-
SOL_MINT: () => SOL_MINT,
|
|
226
|
+
SOL_MINT: () => import_utils.SOL_MINT,
|
|
224
227
|
SharedWorkerTransport: () => SharedWorkerTransport,
|
|
225
228
|
Spectate: () => Spectate,
|
|
226
229
|
StandaloneWsTransport: () => StandaloneWsTransport,
|
|
@@ -239,10 +242,11 @@ __export(index_exports, {
|
|
|
239
242
|
createLogger: () => createLogger,
|
|
240
243
|
createNotificationsStore: () => createNotificationsStore,
|
|
241
244
|
createSdkStore: () => createSdkStore,
|
|
242
|
-
formatMoneyMinor: () =>
|
|
245
|
+
formatMoneyMinor: () => import_utils2.formatMoneyMinor,
|
|
243
246
|
isRetryableError: () => isRetryableError,
|
|
244
247
|
logger: () => logger,
|
|
245
|
-
|
|
248
|
+
selectGameLifecycleState: () => selectGameLifecycleState,
|
|
249
|
+
toMajor: () => import_utils2.toMajor,
|
|
246
250
|
withRetry: () => withRetry
|
|
247
251
|
});
|
|
248
252
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -796,6 +800,9 @@ var Admin = class {
|
|
|
796
800
|
this.http = http;
|
|
797
801
|
this.logger = logger2;
|
|
798
802
|
}
|
|
803
|
+
async getInternalBots() {
|
|
804
|
+
return this.http.get("/admin/internal-bots");
|
|
805
|
+
}
|
|
799
806
|
async getUserById(id) {
|
|
800
807
|
return this.http.get(`/admin/users/${id}`);
|
|
801
808
|
}
|
|
@@ -1153,6 +1160,13 @@ var Lobbies = class {
|
|
|
1153
1160
|
setLobbyStore(store) {
|
|
1154
1161
|
this.lobbyStore = store;
|
|
1155
1162
|
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Create a new game lobby.
|
|
1165
|
+
*
|
|
1166
|
+
* **Important:** Creating a lobby automatically closes and refunds any
|
|
1167
|
+
* existing open or queued lobby for this user. You do not need to manually
|
|
1168
|
+
* leave or cancel a previous lobby before calling this.
|
|
1169
|
+
*/
|
|
1156
1170
|
async createLobby(gameType, betAmount) {
|
|
1157
1171
|
return this.http.post("/lobbies", { gameType, betAmount });
|
|
1158
1172
|
}
|
|
@@ -1216,21 +1230,6 @@ var Lobbies = class {
|
|
|
1216
1230
|
async deleteLobby(lobbyId) {
|
|
1217
1231
|
return this.http.delete(`/lobbies/admin/${lobbyId}`);
|
|
1218
1232
|
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Play again: Create a new lobby and prepare deposit in one flow.
|
|
1221
|
-
* Returns the lobby and unsigned transaction that needs to be signed.
|
|
1222
|
-
* @param gameType - The game type to play again
|
|
1223
|
-
* @param betAmount - The bet amount (same as previous game)
|
|
1224
|
-
* @param escrow - The escrow service instance (from sdk.escrow)
|
|
1225
|
-
*/
|
|
1226
|
-
async playAgain(gameType, betAmount, escrow) {
|
|
1227
|
-
const lobby = await this.createLobby(gameType, betAmount);
|
|
1228
|
-
const { transaction } = await escrow.prepareAndStartDeposit(lobby.id);
|
|
1229
|
-
return {
|
|
1230
|
-
lobby,
|
|
1231
|
-
unsignedTransaction: transaction
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
1233
|
};
|
|
1235
1234
|
|
|
1236
1235
|
// src/games.ts
|
|
@@ -1769,11 +1768,11 @@ var Achievements = class {
|
|
|
1769
1768
|
|
|
1770
1769
|
// src/wallet.ts
|
|
1771
1770
|
var import_web32 = require("@solana/web3.js");
|
|
1771
|
+
var import_utils = __toESM(require_dist(), 1);
|
|
1772
1772
|
var TRANSFER_FEE_MINOR = 1e4;
|
|
1773
1773
|
var MIN_TRANSFER_AMOUNT = 5e4;
|
|
1774
1774
|
var MIN_SOL_TRANSFER_AMOUNT = 1e6;
|
|
1775
1775
|
var SOL_DECIMALS = 9;
|
|
1776
|
-
var SOL_MINT = "SOL";
|
|
1777
1776
|
var ESTIMATED_SOL_FEE_LAMPORTS = 1e4;
|
|
1778
1777
|
var Wallet = class {
|
|
1779
1778
|
constructor(http, logger2) {
|
|
@@ -3463,6 +3462,19 @@ function createGameStore(transport) {
|
|
|
3463
3462
|
}
|
|
3464
3463
|
|
|
3465
3464
|
// src/stores/game-actions-store.ts
|
|
3465
|
+
function selectGameLifecycleState(gameState) {
|
|
3466
|
+
const s = gameState;
|
|
3467
|
+
if (!s) return null;
|
|
3468
|
+
const raw = s;
|
|
3469
|
+
return {
|
|
3470
|
+
status: raw.status,
|
|
3471
|
+
winnerId: raw.winnerId ?? null,
|
|
3472
|
+
betAmount: raw.betAmount ?? 0,
|
|
3473
|
+
wonAmount: raw.wonAmount ?? null,
|
|
3474
|
+
totalPotMinor: raw.totalPotMinor,
|
|
3475
|
+
currentPlayerId: raw.currentPlayerId ?? null
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3466
3478
|
var isRpsCompletionPayload = (payload) => payload.gameType === "rock-paper-scissors";
|
|
3467
3479
|
function createGameActionsStore(transport) {
|
|
3468
3480
|
const store = createSdkStore({
|
|
@@ -3484,8 +3496,21 @@ function createGameActionsStore(transport) {
|
|
|
3484
3496
|
});
|
|
3485
3497
|
};
|
|
3486
3498
|
const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
|
|
3499
|
+
const pendingEvents = /* @__PURE__ */ new Map();
|
|
3500
|
+
function enqueue(gameId, event) {
|
|
3501
|
+
const q = pendingEvents.get(gameId) ?? [];
|
|
3502
|
+
q.push(event);
|
|
3503
|
+
pendingEvents.set(gameId, q);
|
|
3504
|
+
}
|
|
3505
|
+
function drainQueue(gameId) {
|
|
3506
|
+
const q = pendingEvents.get(gameId);
|
|
3507
|
+
if (!q?.length) return;
|
|
3508
|
+
pendingEvents.delete(gameId);
|
|
3509
|
+
for (const ev of q) applyWsEvent(ev);
|
|
3510
|
+
}
|
|
3487
3511
|
const setBaseState = (gameId, state) => {
|
|
3488
3512
|
updateState(gameId, state);
|
|
3513
|
+
drainQueue(gameId);
|
|
3489
3514
|
};
|
|
3490
3515
|
const clearState = (gameId) => {
|
|
3491
3516
|
store.updateState((state) => {
|
|
@@ -3495,6 +3520,7 @@ function createGameActionsStore(transport) {
|
|
|
3495
3520
|
const { [gameId]: _, ...rest } = state.statesByGameId;
|
|
3496
3521
|
return { ...state, statesByGameId: rest };
|
|
3497
3522
|
});
|
|
3523
|
+
pendingEvents.delete(gameId);
|
|
3498
3524
|
};
|
|
3499
3525
|
const applyWsEvent = (event) => {
|
|
3500
3526
|
switch (event.event) {
|
|
@@ -3513,7 +3539,10 @@ function createGameActionsStore(transport) {
|
|
|
3513
3539
|
}
|
|
3514
3540
|
case "game:rps:starting": {
|
|
3515
3541
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3516
|
-
if (!current || !isRpsState(current))
|
|
3542
|
+
if (!current || !isRpsState(current)) {
|
|
3543
|
+
enqueue(event.payload.gameId, event);
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3517
3546
|
const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
|
|
3518
3547
|
const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
|
|
3519
3548
|
const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
|
|
@@ -3534,7 +3563,10 @@ function createGameActionsStore(transport) {
|
|
|
3534
3563
|
}
|
|
3535
3564
|
case "game:rps:round:started": {
|
|
3536
3565
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3537
|
-
if (!current || !isRpsState(current))
|
|
3566
|
+
if (!current || !isRpsState(current)) {
|
|
3567
|
+
enqueue(event.payload.gameId, event);
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3538
3570
|
const actions = {};
|
|
3539
3571
|
const baseUsers = /* @__PURE__ */ new Set();
|
|
3540
3572
|
Object.keys(current.roundState.actions).forEach(
|
|
@@ -3568,7 +3600,10 @@ function createGameActionsStore(transport) {
|
|
|
3568
3600
|
}
|
|
3569
3601
|
case "game:rps:action:received": {
|
|
3570
3602
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3571
|
-
if (!current || !isRpsState(current))
|
|
3603
|
+
if (!current || !isRpsState(current)) {
|
|
3604
|
+
enqueue(event.payload.gameId, event);
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3572
3607
|
const updated = {
|
|
3573
3608
|
...current,
|
|
3574
3609
|
roundState: {
|
|
@@ -3587,7 +3622,10 @@ function createGameActionsStore(transport) {
|
|
|
3587
3622
|
}
|
|
3588
3623
|
case "game:rps:timer:cutoff": {
|
|
3589
3624
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3590
|
-
if (!current || !isRpsState(current))
|
|
3625
|
+
if (!current || !isRpsState(current)) {
|
|
3626
|
+
enqueue(event.payload.gameId, event);
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3591
3629
|
const updated = {
|
|
3592
3630
|
...current,
|
|
3593
3631
|
roundState: {
|
|
@@ -3601,7 +3639,10 @@ function createGameActionsStore(transport) {
|
|
|
3601
3639
|
}
|
|
3602
3640
|
case "game:rps:round:reveal": {
|
|
3603
3641
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3604
|
-
if (!current || !isRpsState(current))
|
|
3642
|
+
if (!current || !isRpsState(current)) {
|
|
3643
|
+
enqueue(event.payload.gameId, event);
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3605
3646
|
const actions = {};
|
|
3606
3647
|
const payloadActions = event.payload.actions;
|
|
3607
3648
|
Object.keys(payloadActions || {}).forEach((userId) => {
|
|
@@ -3625,7 +3666,10 @@ function createGameActionsStore(transport) {
|
|
|
3625
3666
|
}
|
|
3626
3667
|
case "game:rps:round:completed": {
|
|
3627
3668
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3628
|
-
if (!current || !isRpsState(current))
|
|
3669
|
+
if (!current || !isRpsState(current)) {
|
|
3670
|
+
enqueue(event.payload.gameId, event);
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3629
3673
|
const roundHistory = [
|
|
3630
3674
|
...current.roundHistory || [],
|
|
3631
3675
|
{
|
|
@@ -3650,7 +3694,10 @@ function createGameActionsStore(transport) {
|
|
|
3650
3694
|
}
|
|
3651
3695
|
case "game:rps:timeout": {
|
|
3652
3696
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3653
|
-
if (!current || !isRpsState(current))
|
|
3697
|
+
if (!current || !isRpsState(current)) {
|
|
3698
|
+
enqueue(event.payload.gameId, event);
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3654
3701
|
const timedOutUser = event.payload.playerId;
|
|
3655
3702
|
const action = event.payload.action;
|
|
3656
3703
|
const updated = {
|
|
@@ -3675,7 +3722,10 @@ function createGameActionsStore(transport) {
|
|
|
3675
3722
|
const payload = event.payload;
|
|
3676
3723
|
const { gameId } = payload;
|
|
3677
3724
|
const current = store.getState().statesByGameId[gameId];
|
|
3678
|
-
if (!current)
|
|
3725
|
+
if (!current) {
|
|
3726
|
+
enqueue(gameId, event);
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3679
3729
|
const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
|
|
3680
3730
|
...current,
|
|
3681
3731
|
status: "completed",
|
|
@@ -3724,11 +3774,15 @@ function createGameActionsStore(transport) {
|
|
|
3724
3774
|
const current = store.getState().statesByGameId[gameId];
|
|
3725
3775
|
const updated = current ? { ...current, ...incoming } : incoming;
|
|
3726
3776
|
updateState(gameId, updated);
|
|
3777
|
+
drainQueue(gameId);
|
|
3727
3778
|
break;
|
|
3728
3779
|
}
|
|
3729
3780
|
case "game:rematch:requested": {
|
|
3730
3781
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3731
|
-
if (!current)
|
|
3782
|
+
if (!current) {
|
|
3783
|
+
enqueue(event.payload.gameId, event);
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3732
3786
|
const requestedBy = event.payload.requestedBy;
|
|
3733
3787
|
const userId = event.payload.userId;
|
|
3734
3788
|
const requested = new Set(
|
|
@@ -3744,7 +3798,10 @@ function createGameActionsStore(transport) {
|
|
|
3744
3798
|
}
|
|
3745
3799
|
case "game:rematch:cancelled": {
|
|
3746
3800
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3747
|
-
if (!current)
|
|
3801
|
+
if (!current) {
|
|
3802
|
+
enqueue(event.payload.gameId, event);
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3748
3805
|
const requestedBy = event.payload.requestedBy ?? [];
|
|
3749
3806
|
const updated = {
|
|
3750
3807
|
...current,
|
|
@@ -3755,7 +3812,10 @@ function createGameActionsStore(transport) {
|
|
|
3755
3812
|
}
|
|
3756
3813
|
case "game:rematch:started": {
|
|
3757
3814
|
const current = store.getState().statesByGameId[event.payload.gameId];
|
|
3758
|
-
if (!current)
|
|
3815
|
+
if (!current) {
|
|
3816
|
+
enqueue(event.payload.gameId, event);
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3759
3819
|
const updated = {
|
|
3760
3820
|
...current,
|
|
3761
3821
|
rematchRequestedBy: event.payload.playerIds ?? []
|
|
@@ -3766,7 +3826,10 @@ function createGameActionsStore(transport) {
|
|
|
3766
3826
|
case "game:pot:updated": {
|
|
3767
3827
|
const { gameId, totalPotMinor } = event.payload;
|
|
3768
3828
|
const current = store.getState().statesByGameId[gameId];
|
|
3769
|
-
if (!current)
|
|
3829
|
+
if (!current) {
|
|
3830
|
+
enqueue(gameId, event);
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3770
3833
|
const updated = {
|
|
3771
3834
|
...current,
|
|
3772
3835
|
totalPotMinor
|
|
@@ -3779,12 +3842,123 @@ function createGameActionsStore(transport) {
|
|
|
3779
3842
|
}
|
|
3780
3843
|
};
|
|
3781
3844
|
const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
|
|
3845
|
+
const getCountdownDigit = (gameId, nowMs) => {
|
|
3846
|
+
const state = store.getState().statesByGameId[gameId];
|
|
3847
|
+
if (!state) return null;
|
|
3848
|
+
if (isRpsState(state)) {
|
|
3849
|
+
if (state.roundState.phase !== "starting") return null;
|
|
3850
|
+
const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
|
|
3851
|
+
if (remaining <= 0) return null;
|
|
3852
|
+
return Math.ceil(remaining / 1e3);
|
|
3853
|
+
}
|
|
3854
|
+
const bufferEndsAt = state.bufferEndsAt;
|
|
3855
|
+
if (bufferEndsAt) {
|
|
3856
|
+
const remaining = new Date(bufferEndsAt).getTime() - nowMs;
|
|
3857
|
+
if (remaining <= 0) return null;
|
|
3858
|
+
return Math.ceil(remaining / 1e3);
|
|
3859
|
+
}
|
|
3860
|
+
return null;
|
|
3861
|
+
};
|
|
3862
|
+
const getChessClockTimes = (gameId, nowMs) => {
|
|
3863
|
+
const state = store.getState().statesByGameId[gameId];
|
|
3864
|
+
if (!state || state.gameType !== "chess") return null;
|
|
3865
|
+
const s = state;
|
|
3866
|
+
let whiteMs = s.whiteTimeMs ?? 0;
|
|
3867
|
+
let blackMs = s.blackTimeMs ?? 0;
|
|
3868
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
3869
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
3870
|
+
if (!Number.isNaN(startedAt)) {
|
|
3871
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
3872
|
+
if (s.currentPlayerId === s.whitePlayerId) {
|
|
3873
|
+
whiteMs = Math.max(0, whiteMs - elapsed);
|
|
3874
|
+
} else if (s.currentPlayerId === s.blackPlayerId) {
|
|
3875
|
+
blackMs = Math.max(0, blackMs - elapsed);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
|
|
3880
|
+
};
|
|
3881
|
+
const getChessCapturedPieces = (gameId) => {
|
|
3882
|
+
const state = store.getState().statesByGameId[gameId];
|
|
3883
|
+
if (!state || state.gameType !== "chess") return null;
|
|
3884
|
+
const fen = state.fen;
|
|
3885
|
+
if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
|
|
3886
|
+
const placement = fen.split(" ")[0];
|
|
3887
|
+
const white = {};
|
|
3888
|
+
const black = {};
|
|
3889
|
+
for (const char of placement) {
|
|
3890
|
+
if (char === "/" || char >= "1" && char <= "8") continue;
|
|
3891
|
+
const lower = char.toLowerCase();
|
|
3892
|
+
if (char === lower) {
|
|
3893
|
+
black[lower] = (black[lower] ?? 0) + 1;
|
|
3894
|
+
} else {
|
|
3895
|
+
white[lower] = (white[lower] ?? 0) + 1;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
const INITIAL = {
|
|
3899
|
+
p: 8,
|
|
3900
|
+
r: 2,
|
|
3901
|
+
n: 2,
|
|
3902
|
+
b: 2,
|
|
3903
|
+
q: 1,
|
|
3904
|
+
k: 1
|
|
3905
|
+
};
|
|
3906
|
+
const capturedByWhite = [];
|
|
3907
|
+
const capturedByBlack = [];
|
|
3908
|
+
for (const [type, initial] of Object.entries(INITIAL)) {
|
|
3909
|
+
const missingBlack = initial - (black[type] ?? 0);
|
|
3910
|
+
for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
|
|
3911
|
+
const missingWhite = initial - (white[type] ?? 0);
|
|
3912
|
+
for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
|
|
3913
|
+
}
|
|
3914
|
+
return { capturedByWhite, capturedByBlack };
|
|
3915
|
+
};
|
|
3916
|
+
const getTicTacToeClockTimes = (gameId, nowMs) => {
|
|
3917
|
+
const state = store.getState().statesByGameId[gameId];
|
|
3918
|
+
if (!state || state.gameType !== "tic-tac-toe") return null;
|
|
3919
|
+
const s = state;
|
|
3920
|
+
let xMs = s.xTimeMs ?? 0;
|
|
3921
|
+
let oMs = s.oTimeMs ?? 0;
|
|
3922
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
3923
|
+
const currentMark = s.playerMarks[s.currentPlayerId];
|
|
3924
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
3925
|
+
if (!Number.isNaN(startedAt)) {
|
|
3926
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
3927
|
+
if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
|
|
3928
|
+
else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
return { xTimeMs: xMs, oTimeMs: oMs };
|
|
3932
|
+
};
|
|
3933
|
+
const getConnect4ClockTimes = (gameId, nowMs) => {
|
|
3934
|
+
const state = store.getState().statesByGameId[gameId];
|
|
3935
|
+
if (!state || state.gameType !== "connect-four") return null;
|
|
3936
|
+
const s = state;
|
|
3937
|
+
let redMs = s.redTimeMs ?? 0;
|
|
3938
|
+
let yellowMs = s.yellowTimeMs ?? 0;
|
|
3939
|
+
if (s.status === "active" && s.currentPlayerId) {
|
|
3940
|
+
const currentColor = s.playerColors[s.currentPlayerId];
|
|
3941
|
+
const startedAt = Date.parse(s.turnStartedAt);
|
|
3942
|
+
if (!Number.isNaN(startedAt)) {
|
|
3943
|
+
const elapsed = Math.max(0, nowMs - startedAt);
|
|
3944
|
+
if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
|
|
3945
|
+
else if (currentColor === "YELLOW")
|
|
3946
|
+
yellowMs = Math.max(0, yellowMs - elapsed);
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
return { redTimeMs: redMs, yellowTimeMs: yellowMs };
|
|
3950
|
+
};
|
|
3782
3951
|
return {
|
|
3783
3952
|
store,
|
|
3784
3953
|
setBaseState,
|
|
3785
3954
|
clearState,
|
|
3786
3955
|
applyWsEvent,
|
|
3787
|
-
joinGame
|
|
3956
|
+
joinGame,
|
|
3957
|
+
getCountdownDigit,
|
|
3958
|
+
getChessClockTimes,
|
|
3959
|
+
getChessCapturedPieces,
|
|
3960
|
+
getTicTacToeClockTimes,
|
|
3961
|
+
getConnect4ClockTimes
|
|
3788
3962
|
};
|
|
3789
3963
|
}
|
|
3790
3964
|
function isRpsState(state) {
|
|
@@ -4454,12 +4628,14 @@ var WsRouter = class {
|
|
|
4454
4628
|
}
|
|
4455
4629
|
const decoded = decodeWsEvent(eventName, payload);
|
|
4456
4630
|
if (!decoded) return;
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
this.deps.
|
|
4460
|
-
this.deps.
|
|
4461
|
-
this.deps.
|
|
4462
|
-
this.deps.
|
|
4631
|
+
const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
|
|
4632
|
+
const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
|
|
4633
|
+
this.deps.lobbyStore.applyWsEvent(event);
|
|
4634
|
+
this.deps.gameStore.applyWsEvent(event);
|
|
4635
|
+
this.deps.gameActionsStore.applyWsEvent(event);
|
|
4636
|
+
this.deps.chatStore.applyWsEvent(event);
|
|
4637
|
+
this.deps.dmThreadsStore.applyWsEvent(event);
|
|
4638
|
+
this.deps.notificationsStore.applyWsEvent(event);
|
|
4463
4639
|
});
|
|
4464
4640
|
}
|
|
4465
4641
|
stop() {
|
|
@@ -4471,12 +4647,14 @@ var WsRouter = class {
|
|
|
4471
4647
|
this.transport.subscribeEvent(eventName, (payload) => {
|
|
4472
4648
|
const decoded = decodeWsEvent(eventName, payload);
|
|
4473
4649
|
if (!decoded) return;
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
this.deps.
|
|
4477
|
-
this.deps.
|
|
4478
|
-
this.deps.
|
|
4479
|
-
this.deps.
|
|
4650
|
+
const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
|
|
4651
|
+
const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
|
|
4652
|
+
this.deps.lobbyStore.applyWsEvent(event);
|
|
4653
|
+
this.deps.gameStore.applyWsEvent(event);
|
|
4654
|
+
this.deps.gameActionsStore.applyWsEvent(event);
|
|
4655
|
+
this.deps.chatStore.applyWsEvent(event);
|
|
4656
|
+
this.deps.dmThreadsStore.applyWsEvent(event);
|
|
4657
|
+
this.deps.notificationsStore.applyWsEvent(event);
|
|
4480
4658
|
});
|
|
4481
4659
|
}
|
|
4482
4660
|
}
|
|
@@ -4835,7 +5013,7 @@ var SharedWorkerTransport = class extends BaseWsTransport {
|
|
|
4835
5013
|
};
|
|
4836
5014
|
|
|
4837
5015
|
// src/utils/money.ts
|
|
4838
|
-
var
|
|
5016
|
+
var import_utils2 = __toESM(require_dist(), 1);
|
|
4839
5017
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4840
5018
|
0 && (module.exports = {
|
|
4841
5019
|
Admin,
|
|
@@ -4877,6 +5055,7 @@ var import_utils = __toESM(require_dist(), 1);
|
|
|
4877
5055
|
formatMoneyMinor,
|
|
4878
5056
|
isRetryableError,
|
|
4879
5057
|
logger,
|
|
5058
|
+
selectGameLifecycleState,
|
|
4880
5059
|
toMajor,
|
|
4881
5060
|
withRetry
|
|
4882
5061
|
});
|