@hellacardgames/speed 0.5.2 → 0.6.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.
Files changed (45) hide show
  1. package/dist/client/index.cjs.map +1 -1
  2. package/dist/client/index.d.cts +2 -3
  3. package/dist/client/index.d.cts.map +1 -1
  4. package/dist/client/index.d.mts +2 -3
  5. package/dist/client/index.d.mts.map +1 -1
  6. package/dist/client/index.mjs.map +1 -1
  7. package/dist/{startGame-5InkAgEp.d.cts → index-FveApXzq.d.cts} +52 -36
  8. package/dist/index-FveApXzq.d.cts.map +1 -0
  9. package/dist/{startGame-ayp8a-Cl.d.mts → index-J8s2QklC.d.mts} +52 -36
  10. package/dist/index-J8s2QklC.d.mts.map +1 -0
  11. package/dist/index-YAYbNr-s.d.cts +200 -0
  12. package/dist/index-YAYbNr-s.d.cts.map +1 -0
  13. package/dist/index-YAYbNr-s.d.mts +200 -0
  14. package/dist/index-YAYbNr-s.d.mts.map +1 -0
  15. package/dist/manager/index.cjs +2 -32
  16. package/dist/manager/index.d.cts +2 -109
  17. package/dist/manager/index.d.mts +2 -109
  18. package/dist/manager/index.mjs +2 -22
  19. package/dist/manager-BOymBQ4E.mjs +817 -0
  20. package/dist/manager-BOymBQ4E.mjs.map +1 -0
  21. package/dist/manager-BpKvQbcp.cjs +822 -0
  22. package/dist/manager-BpKvQbcp.cjs.map +1 -0
  23. package/dist/server/index.cjs +138 -157
  24. package/dist/server/index.cjs.map +1 -1
  25. package/dist/server/index.d.cts +3 -40
  26. package/dist/server/index.d.mts +3 -40
  27. package/dist/server/index.mjs +138 -157
  28. package/dist/server/index.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/dist/GameEvent-DDQ4jXxt.d.cts +0 -111
  31. package/dist/GameEvent-DDQ4jXxt.d.cts.map +0 -1
  32. package/dist/GameEvent-DDQ4jXxt.d.mts +0 -111
  33. package/dist/GameEvent-DDQ4jXxt.d.mts.map +0 -1
  34. package/dist/manager/index.cjs.map +0 -1
  35. package/dist/manager/index.d.cts.map +0 -1
  36. package/dist/manager/index.d.mts.map +0 -1
  37. package/dist/manager/index.mjs.map +0 -1
  38. package/dist/server/index.d.cts.map +0 -1
  39. package/dist/server/index.d.mts.map +0 -1
  40. package/dist/startGame-5InkAgEp.d.cts.map +0 -1
  41. package/dist/startGame-BC9XfFzG.mjs +0 -815
  42. package/dist/startGame-BC9XfFzG.mjs.map +0 -1
  43. package/dist/startGame-Bhk__XWk.cjs +0 -898
  44. package/dist/startGame-Bhk__XWk.cjs.map +0 -1
  45. package/dist/startGame-ayp8a-Cl.d.mts.map +0 -1
@@ -0,0 +1,817 @@
1
+ //#region src/manager/constants.ts
2
+ const GAME_KEY = "speed";
3
+ const WATCHDOG_INTERVAL_MS = 1e4;
4
+ const EXPIRY_EXTENSION_MS = 3e5;
5
+ const CAN_PLAY_AT_DELAY_MS = 3e3;
6
+ const CARDS = [
7
+ {
8
+ id: crypto.randomUUID(),
9
+ rank: 2,
10
+ suite: "clubs"
11
+ },
12
+ {
13
+ id: crypto.randomUUID(),
14
+ rank: 3,
15
+ suite: "clubs"
16
+ },
17
+ {
18
+ id: crypto.randomUUID(),
19
+ rank: 4,
20
+ suite: "clubs"
21
+ },
22
+ {
23
+ id: crypto.randomUUID(),
24
+ rank: 5,
25
+ suite: "clubs"
26
+ },
27
+ {
28
+ id: crypto.randomUUID(),
29
+ rank: 6,
30
+ suite: "clubs"
31
+ },
32
+ {
33
+ id: crypto.randomUUID(),
34
+ rank: 7,
35
+ suite: "clubs"
36
+ },
37
+ {
38
+ id: crypto.randomUUID(),
39
+ rank: 8,
40
+ suite: "clubs"
41
+ },
42
+ {
43
+ id: crypto.randomUUID(),
44
+ rank: 9,
45
+ suite: "clubs"
46
+ },
47
+ {
48
+ id: crypto.randomUUID(),
49
+ rank: 10,
50
+ suite: "clubs"
51
+ },
52
+ {
53
+ id: crypto.randomUUID(),
54
+ rank: 11,
55
+ suite: "clubs"
56
+ },
57
+ {
58
+ id: crypto.randomUUID(),
59
+ rank: 12,
60
+ suite: "clubs"
61
+ },
62
+ {
63
+ id: crypto.randomUUID(),
64
+ rank: 13,
65
+ suite: "clubs"
66
+ },
67
+ {
68
+ id: crypto.randomUUID(),
69
+ rank: 14,
70
+ suite: "clubs"
71
+ },
72
+ {
73
+ id: crypto.randomUUID(),
74
+ rank: 2,
75
+ suite: "diamonds"
76
+ },
77
+ {
78
+ id: crypto.randomUUID(),
79
+ rank: 3,
80
+ suite: "diamonds"
81
+ },
82
+ {
83
+ id: crypto.randomUUID(),
84
+ rank: 4,
85
+ suite: "diamonds"
86
+ },
87
+ {
88
+ id: crypto.randomUUID(),
89
+ rank: 5,
90
+ suite: "diamonds"
91
+ },
92
+ {
93
+ id: crypto.randomUUID(),
94
+ rank: 6,
95
+ suite: "diamonds"
96
+ },
97
+ {
98
+ id: crypto.randomUUID(),
99
+ rank: 7,
100
+ suite: "diamonds"
101
+ },
102
+ {
103
+ id: crypto.randomUUID(),
104
+ rank: 8,
105
+ suite: "diamonds"
106
+ },
107
+ {
108
+ id: crypto.randomUUID(),
109
+ rank: 9,
110
+ suite: "diamonds"
111
+ },
112
+ {
113
+ id: crypto.randomUUID(),
114
+ rank: 10,
115
+ suite: "diamonds"
116
+ },
117
+ {
118
+ id: crypto.randomUUID(),
119
+ rank: 11,
120
+ suite: "diamonds"
121
+ },
122
+ {
123
+ id: crypto.randomUUID(),
124
+ rank: 12,
125
+ suite: "diamonds"
126
+ },
127
+ {
128
+ id: crypto.randomUUID(),
129
+ rank: 13,
130
+ suite: "diamonds"
131
+ },
132
+ {
133
+ id: crypto.randomUUID(),
134
+ rank: 14,
135
+ suite: "diamonds"
136
+ },
137
+ {
138
+ id: crypto.randomUUID(),
139
+ rank: 2,
140
+ suite: "hearts"
141
+ },
142
+ {
143
+ id: crypto.randomUUID(),
144
+ rank: 3,
145
+ suite: "hearts"
146
+ },
147
+ {
148
+ id: crypto.randomUUID(),
149
+ rank: 4,
150
+ suite: "hearts"
151
+ },
152
+ {
153
+ id: crypto.randomUUID(),
154
+ rank: 5,
155
+ suite: "hearts"
156
+ },
157
+ {
158
+ id: crypto.randomUUID(),
159
+ rank: 6,
160
+ suite: "hearts"
161
+ },
162
+ {
163
+ id: crypto.randomUUID(),
164
+ rank: 7,
165
+ suite: "hearts"
166
+ },
167
+ {
168
+ id: crypto.randomUUID(),
169
+ rank: 8,
170
+ suite: "hearts"
171
+ },
172
+ {
173
+ id: crypto.randomUUID(),
174
+ rank: 9,
175
+ suite: "hearts"
176
+ },
177
+ {
178
+ id: crypto.randomUUID(),
179
+ rank: 10,
180
+ suite: "hearts"
181
+ },
182
+ {
183
+ id: crypto.randomUUID(),
184
+ rank: 11,
185
+ suite: "hearts"
186
+ },
187
+ {
188
+ id: crypto.randomUUID(),
189
+ rank: 12,
190
+ suite: "hearts"
191
+ },
192
+ {
193
+ id: crypto.randomUUID(),
194
+ rank: 13,
195
+ suite: "hearts"
196
+ },
197
+ {
198
+ id: crypto.randomUUID(),
199
+ rank: 14,
200
+ suite: "hearts"
201
+ },
202
+ {
203
+ id: crypto.randomUUID(),
204
+ rank: 2,
205
+ suite: "spades"
206
+ },
207
+ {
208
+ id: crypto.randomUUID(),
209
+ rank: 3,
210
+ suite: "spades"
211
+ },
212
+ {
213
+ id: crypto.randomUUID(),
214
+ rank: 4,
215
+ suite: "spades"
216
+ },
217
+ {
218
+ id: crypto.randomUUID(),
219
+ rank: 5,
220
+ suite: "spades"
221
+ },
222
+ {
223
+ id: crypto.randomUUID(),
224
+ rank: 6,
225
+ suite: "spades"
226
+ },
227
+ {
228
+ id: crypto.randomUUID(),
229
+ rank: 7,
230
+ suite: "spades"
231
+ },
232
+ {
233
+ id: crypto.randomUUID(),
234
+ rank: 8,
235
+ suite: "spades"
236
+ },
237
+ {
238
+ id: crypto.randomUUID(),
239
+ rank: 9,
240
+ suite: "spades"
241
+ },
242
+ {
243
+ id: crypto.randomUUID(),
244
+ rank: 10,
245
+ suite: "spades"
246
+ },
247
+ {
248
+ id: crypto.randomUUID(),
249
+ rank: 11,
250
+ suite: "spades"
251
+ },
252
+ {
253
+ id: crypto.randomUUID(),
254
+ rank: 12,
255
+ suite: "spades"
256
+ },
257
+ {
258
+ id: crypto.randomUUID(),
259
+ rank: 13,
260
+ suite: "spades"
261
+ },
262
+ {
263
+ id: crypto.randomUUID(),
264
+ rank: 14,
265
+ suite: "spades"
266
+ }
267
+ ];
268
+ //#endregion
269
+ //#region src/manager/Watchdog.ts
270
+ var Watchdog = class {
271
+ games;
272
+ constructor(games) {
273
+ this.games = games;
274
+ }
275
+ start() {
276
+ console.log(`watchdog start at ${Date.now()} (${GAME_KEY})`);
277
+ setInterval(() => this.wakeUp(), WATCHDOG_INTERVAL_MS);
278
+ }
279
+ wakeUp() {
280
+ const now = Date.now();
281
+ for (const game of this.games.values()) if (game.expiresAt <= now) {
282
+ this.games.delete(game.id);
283
+ console.log(`watchdog purged ${game.id} (${GAME_KEY})`);
284
+ }
285
+ }
286
+ };
287
+ //#endregion
288
+ //#region src/lib/emitEvent.ts
289
+ function emitEvent(game, data) {
290
+ const event = {
291
+ ...data,
292
+ id: crypto.randomUUID()
293
+ };
294
+ for (const p of game.players) p.events.push(event);
295
+ }
296
+ //#endregion
297
+ //#region src/lib/emitEventToPlayer.ts
298
+ function emitEventToPlayer(player, data) {
299
+ player.events.push({
300
+ ...data,
301
+ id: crypto.randomUUID()
302
+ });
303
+ }
304
+ //#endregion
305
+ //#region src/manager/lib/isCardPlayable.ts
306
+ function isCardPlayable(card, targetPile) {
307
+ const targetCard = targetPile[targetPile.length - 1];
308
+ return Math.abs(card.rank - targetCard.rank) % 11 === 1;
309
+ }
310
+ //#endregion
311
+ //#region src/manager/lib/hasPlayableCard.ts
312
+ function hasPlayableCard(player, game) {
313
+ const otherPlayer = game.players.find((p) => p !== player);
314
+ const targetPile1 = player.centerPile;
315
+ const targetPile2 = otherPlayer.centerPile;
316
+ for (const card of player.hand) if (isCardPlayable(card, targetPile1) || isCardPlayable(card, targetPile2)) return true;
317
+ return false;
318
+ }
319
+ //#endregion
320
+ //#region src/lib/shuffleCards.ts
321
+ function shuffleCards(cards) {
322
+ for (let i = cards.length - 1; i > 0; i--) {
323
+ const j = Math.floor(Math.random() * (i + 1));
324
+ [cards[i], cards[j]] = [cards[j], cards[i]];
325
+ }
326
+ }
327
+ //#endregion
328
+ //#region src/manager/Manager.ts
329
+ var Manager = class {
330
+ games;
331
+ watchdog;
332
+ constructor() {
333
+ this.games = /* @__PURE__ */ new Map();
334
+ this.watchdog = new Watchdog(this.games);
335
+ this.watchdog.start();
336
+ }
337
+ createGame(userId, username) {
338
+ if (this.games.size === 50) return {
339
+ success: false,
340
+ error: "maxGamesReached"
341
+ };
342
+ const player = {
343
+ id: crypto.randomUUID(),
344
+ userId,
345
+ username,
346
+ events: [],
347
+ hand: [],
348
+ drawPile: [],
349
+ sidePile: [],
350
+ centerPile: [],
351
+ hasNoPlayableCards: false
352
+ };
353
+ const createdAt = Date.now();
354
+ const game = {
355
+ status: "open",
356
+ id: crypto.randomUUID(),
357
+ createdAt,
358
+ expiresAt: createdAt + EXPIRY_EXTENSION_MS,
359
+ chatMessages: [],
360
+ players: [player]
361
+ };
362
+ this.games.set(game.id, game);
363
+ return {
364
+ success: true,
365
+ gameId: game.id,
366
+ playerId: player.id
367
+ };
368
+ }
369
+ drawCard(gameId, playerId) {
370
+ const game = this.games.get(gameId);
371
+ if (!game) return {
372
+ success: false,
373
+ error: "gameNotFound"
374
+ };
375
+ const player = game.players.find((p) => p.id === playerId);
376
+ if (!player) return {
377
+ success: false,
378
+ error: "playerNotFound"
379
+ };
380
+ if (game.status !== "started") return {
381
+ success: false,
382
+ error: "invalidStatus"
383
+ };
384
+ if (Date.now() < game.canPlayAt) return {
385
+ success: false,
386
+ error: "canPlayAtNotReached"
387
+ };
388
+ if (player.hand.length >= 5) return {
389
+ success: false,
390
+ error: "handFull"
391
+ };
392
+ if (player.drawPile.length === 0) return {
393
+ success: false,
394
+ error: "drawPileEmpty"
395
+ };
396
+ game.expiresAt = Date.now() + EXPIRY_EXTENSION_MS;
397
+ emitEvent(game, {
398
+ type: "expirationUpdated",
399
+ expiresAt: game.expiresAt
400
+ });
401
+ const card = player.drawPile.pop();
402
+ player.hand.push(card);
403
+ emitEventToPlayer(player, {
404
+ type: "drewCard",
405
+ card
406
+ });
407
+ emitEvent(game, {
408
+ type: "playerDrewCard",
409
+ username: player.username
410
+ });
411
+ return { success: true };
412
+ }
413
+ getClientStateAndClearEvents(gameId, playerId) {
414
+ const game = this.games.get(gameId);
415
+ if (!game) return {
416
+ success: false,
417
+ error: "gameNotFound"
418
+ };
419
+ const player = game.players.find((p) => p.id === playerId);
420
+ if (!player) return {
421
+ success: false,
422
+ error: "playerNotFound"
423
+ };
424
+ const state = {
425
+ status: game.status,
426
+ gameId,
427
+ playerId,
428
+ username: player.username,
429
+ players: game.players.map((p) => ({
430
+ username: p.username,
431
+ handSize: p.hand.length,
432
+ drawPileSize: p.drawPile.length,
433
+ sidePileSize: p.sidePile.length,
434
+ centerPileTopCard: p.centerPile.length ? p.centerPile[p.centerPile.length - 1] : null
435
+ })),
436
+ expiresAt: game.expiresAt,
437
+ chatMessages: game.chatMessages,
438
+ hand: player.hand,
439
+ canPlayAt: game.status === "started" ? game.canPlayAt : null
440
+ };
441
+ player.events.length = 0;
442
+ return {
443
+ success: true,
444
+ state
445
+ };
446
+ }
447
+ getEventsAndClearAcknowledged(gameId, playerId, lastReadId) {
448
+ const game = this.games.get(gameId);
449
+ if (!game) return {
450
+ success: false,
451
+ error: "gameNotFound"
452
+ };
453
+ const player = game.players.find((p) => p.id === playerId);
454
+ if (!player) return {
455
+ success: false,
456
+ error: "playerNotFound"
457
+ };
458
+ const lastReadEventIndex = player.events.findIndex((e) => e.id === lastReadId);
459
+ player.events.splice(0, lastReadEventIndex + 1);
460
+ return {
461
+ success: true,
462
+ events: player.events
463
+ };
464
+ }
465
+ getJoinableGames() {
466
+ return { games: Array.from(this.games.values()).filter((g) => g.status === "open" && g.players.length < 2).map((g) => ({
467
+ id: g.id,
468
+ numPlayers: g.players.length
469
+ })) };
470
+ }
471
+ joinGame(gameId, userId, username) {
472
+ const game = this.games.get(gameId);
473
+ if (!game) return {
474
+ success: false,
475
+ error: "gameNotFound"
476
+ };
477
+ if (game.status !== "open") return {
478
+ success: false,
479
+ error: "invalidStatus"
480
+ };
481
+ if (game.players.length === 2) return {
482
+ success: false,
483
+ error: "maxPlayersReached"
484
+ };
485
+ if (game.players.find((p) => p.userId === userId)) return {
486
+ success: false,
487
+ error: "alreadyInGame"
488
+ };
489
+ const player = {
490
+ id: crypto.randomUUID(),
491
+ userId,
492
+ username,
493
+ events: [],
494
+ hand: [],
495
+ drawPile: [],
496
+ sidePile: [],
497
+ centerPile: [],
498
+ hasNoPlayableCards: false
499
+ };
500
+ game.players.push(player);
501
+ emitEvent(game, {
502
+ type: "playerJoined",
503
+ username
504
+ });
505
+ return {
506
+ success: true,
507
+ playerId: player.id
508
+ };
509
+ }
510
+ leaveGame(gameId, playerId) {
511
+ const game = this.games.get(gameId);
512
+ if (!game) return {
513
+ success: false,
514
+ error: "gameNotFound"
515
+ };
516
+ const playerIndex = game.players.findIndex((p) => p.id === playerId);
517
+ if (playerIndex === -1) return {
518
+ success: false,
519
+ error: "playerNotFound"
520
+ };
521
+ const player = game.players[playerIndex];
522
+ emitEvent(game, {
523
+ type: "playerLeft",
524
+ username: player.username
525
+ });
526
+ game.players.splice(playerIndex, 1);
527
+ if (game.status === "started" && game.players.length < 2) {
528
+ const forfeitedGame = {
529
+ ...game,
530
+ status: "forfeited",
531
+ expiresAt: Date.now() + EXPIRY_EXTENSION_MS
532
+ };
533
+ this.games.set(game.id, forfeitedGame);
534
+ emitEvent(forfeitedGame, { type: "gameForfeited" });
535
+ emitEvent(forfeitedGame, {
536
+ type: "expirationUpdated",
537
+ expiresAt: forfeitedGame.expiresAt
538
+ });
539
+ }
540
+ if (game.players.length === 0) this.games.delete(game.id);
541
+ return { success: true };
542
+ }
543
+ playCard(gameId, playerId, cardId, isForOtherPlayerPile) {
544
+ const game = this.games.get(gameId);
545
+ if (!game) return {
546
+ success: false,
547
+ error: "gameNotFound"
548
+ };
549
+ const player = game.players.find((p) => p.id === playerId);
550
+ if (!player) return {
551
+ success: false,
552
+ error: "playerNotFound"
553
+ };
554
+ if (game.status !== "started") return {
555
+ success: false,
556
+ error: "invalidStatus"
557
+ };
558
+ if (Date.now() < game.canPlayAt) return {
559
+ success: false,
560
+ error: "canPlayAtNotReached"
561
+ };
562
+ const cardIndex = player.hand.findIndex((c) => c.id === cardId);
563
+ if (cardIndex === -1) return {
564
+ success: false,
565
+ error: "cardNotFound"
566
+ };
567
+ const otherPlayer = game.players.find((p) => p !== player);
568
+ const targetPile = isForOtherPlayerPile ? otherPlayer.centerPile : player.centerPile;
569
+ const card = player.hand[cardIndex];
570
+ if (!isCardPlayable(card, targetPile)) return {
571
+ success: false,
572
+ error: "cardNotPlayable"
573
+ };
574
+ game.expiresAt = Date.now() + EXPIRY_EXTENSION_MS;
575
+ emitEvent(game, {
576
+ type: "expirationUpdated",
577
+ expiresAt: game.expiresAt
578
+ });
579
+ player.hand.splice(cardIndex, 1);
580
+ targetPile.push(card);
581
+ emitEvent(game, {
582
+ type: "cardPlayed",
583
+ username: player.username,
584
+ card,
585
+ isForOtherPlayerPile
586
+ });
587
+ if (otherPlayer.hasNoPlayableCards && hasPlayableCard(otherPlayer, game)) otherPlayer.hasNoPlayableCards = false;
588
+ if (player.hand.length === 0 && player.drawPile.length === 0) {
589
+ emitEvent(game, { type: "gameCompleted" });
590
+ const completedGame = {
591
+ ...game,
592
+ status: "completed"
593
+ };
594
+ this.games.set(game.id, completedGame);
595
+ }
596
+ return { success: true };
597
+ }
598
+ reportNoPlayableCards(gameId, playerId) {
599
+ const game = this.games.get(gameId);
600
+ if (!game) return {
601
+ success: false,
602
+ error: "gameNotFound"
603
+ };
604
+ const player = game.players.find((p) => p.id === playerId);
605
+ if (!player) return {
606
+ success: false,
607
+ error: "playerNotFound"
608
+ };
609
+ if (game.status !== "started") return {
610
+ success: false,
611
+ error: "invalidStatus"
612
+ };
613
+ if (Date.now() < game.canPlayAt) return {
614
+ success: false,
615
+ error: "canPlayAtNotReached"
616
+ };
617
+ if (player.hasNoPlayableCards) return {
618
+ success: false,
619
+ error: "alreadyReported"
620
+ };
621
+ if (hasPlayableCard(player, game)) return {
622
+ success: false,
623
+ error: "hasPlayableCard"
624
+ };
625
+ if (player.drawPile.length > 0 && player.hand.length < 5) return {
626
+ success: false,
627
+ error: "canDraw"
628
+ };
629
+ game.expiresAt = Date.now() + EXPIRY_EXTENSION_MS;
630
+ emitEvent(game, {
631
+ type: "expirationUpdated",
632
+ expiresAt: game.expiresAt
633
+ });
634
+ player.hasNoPlayableCards = true;
635
+ const otherPlayer = game.players.find((p) => p !== player);
636
+ if (otherPlayer.hasNoPlayableCards) {
637
+ if (player.sidePile.length === 0 && otherPlayer.sidePile.length === 0) {
638
+ const cards = [...player.centerPile, ...otherPlayer.centerPile];
639
+ shuffleCards(cards);
640
+ player.sidePile.push(...cards.splice(0, 5));
641
+ emitEvent(game, {
642
+ type: "playerSidePileInitialized",
643
+ username: player.username,
644
+ numCards: player.sidePile.length
645
+ });
646
+ player.centerPile.push(...cards.splice(0, 1));
647
+ emitEvent(game, {
648
+ type: "playerCenterPileInitialized",
649
+ username: player.username,
650
+ card: player.centerPile[player.centerPile.length - 1]
651
+ });
652
+ otherPlayer.centerPile.push(...cards.splice(0, 1));
653
+ emitEvent(game, {
654
+ type: "playerCenterPileInitialized",
655
+ username: otherPlayer.username,
656
+ card: otherPlayer.centerPile[otherPlayer.centerPile.length - 1]
657
+ });
658
+ otherPlayer.sidePile.push(...cards.splice(0, 5));
659
+ emitEvent(game, {
660
+ type: "playerSidePileInitialized",
661
+ username: otherPlayer.username,
662
+ numCards: otherPlayer.sidePile.length
663
+ });
664
+ } else {
665
+ const playerSidePileCard = player.sidePile.pop();
666
+ player.centerPile.push(playerSidePileCard);
667
+ emitEvent(game, {
668
+ type: "cardDrawnFromSidePileToCenterPile",
669
+ username: player.username,
670
+ card: playerSidePileCard
671
+ });
672
+ const otherPlayerSidePileCard = otherPlayer.sidePile.pop();
673
+ otherPlayer.centerPile.push(otherPlayerSidePileCard);
674
+ emitEvent(game, {
675
+ type: "cardDrawnFromSidePileToCenterPile",
676
+ username: otherPlayer.username,
677
+ card: otherPlayerSidePileCard
678
+ });
679
+ }
680
+ player.hasNoPlayableCards = false;
681
+ otherPlayer.hasNoPlayableCards = false;
682
+ game.canPlayAt = Date.now() + CAN_PLAY_AT_DELAY_MS;
683
+ emitEvent(game, {
684
+ type: "canPlayAtUpdated",
685
+ canPlayAt: game.canPlayAt
686
+ });
687
+ }
688
+ return { success: true };
689
+ }
690
+ sendChat(gameId, playerId, text) {
691
+ const game = this.games.get(gameId);
692
+ if (!game) return {
693
+ success: false,
694
+ error: "gameNotFound"
695
+ };
696
+ const player = game.players.find((p) => p.id === playerId);
697
+ if (!player) return {
698
+ success: false,
699
+ error: "playerNotFound"
700
+ };
701
+ const message = {
702
+ id: crypto.randomUUID(),
703
+ username: player.username,
704
+ text
705
+ };
706
+ game.chatMessages.push(message);
707
+ emitEvent(game, {
708
+ type: "chat",
709
+ message
710
+ });
711
+ return { success: true };
712
+ }
713
+ startGame(gameId, playerId) {
714
+ const game = this.games.get(gameId);
715
+ if (!game) return {
716
+ success: false,
717
+ error: "gameNotFound"
718
+ };
719
+ const player = game.players.find((p) => p.id === playerId);
720
+ if (!player) return {
721
+ success: false,
722
+ error: "playerNotFound"
723
+ };
724
+ if (game.status !== "open") return {
725
+ success: false,
726
+ error: "invalidStatus"
727
+ };
728
+ if (game.players.indexOf(player) !== 0) return {
729
+ success: false,
730
+ error: "playerNotAdmin"
731
+ };
732
+ if (game.players.length < 2) return {
733
+ success: false,
734
+ error: "minPlayersNotReached"
735
+ };
736
+ const otherPlayer = game.players[1];
737
+ const cards = [...CARDS];
738
+ shuffleCards(cards);
739
+ player.sidePile.push(...cards.splice(0, 5));
740
+ emitEvent(game, {
741
+ type: "playerSidePileInitialized",
742
+ username: player.username,
743
+ numCards: player.sidePile.length
744
+ });
745
+ player.centerPile.push(...cards.splice(0, 1));
746
+ emitEvent(game, {
747
+ type: "playerCenterPileInitialized",
748
+ username: player.username,
749
+ card: player.centerPile[player.centerPile.length - 1]
750
+ });
751
+ otherPlayer.centerPile.push(...cards.splice(0, 1));
752
+ emitEvent(game, {
753
+ type: "playerCenterPileInitialized",
754
+ username: otherPlayer.username,
755
+ card: otherPlayer.centerPile[otherPlayer.centerPile.length - 1]
756
+ });
757
+ otherPlayer.sidePile.push(...cards.splice(0, 5));
758
+ emitEvent(game, {
759
+ type: "playerSidePileInitialized",
760
+ username: otherPlayer.username,
761
+ numCards: otherPlayer.sidePile.length
762
+ });
763
+ player.drawPile.push(...cards.splice(0, 15));
764
+ emitEvent(game, {
765
+ type: "playerDrawPileInitialized",
766
+ username: player.username,
767
+ numCards: player.drawPile.length
768
+ });
769
+ player.hand.push(...cards.splice(0, 5));
770
+ emitEventToPlayer(player, {
771
+ type: "handInitialized",
772
+ cards: player.hand
773
+ });
774
+ emitEvent(game, {
775
+ type: "playerHandInitialized",
776
+ username: player.username,
777
+ numCards: player.hand.length
778
+ });
779
+ otherPlayer.drawPile.push(...cards.splice(0, 15));
780
+ emitEvent(game, {
781
+ type: "playerDrawPileInitialized",
782
+ username: otherPlayer.username,
783
+ numCards: otherPlayer.drawPile.length
784
+ });
785
+ otherPlayer.hand.push(...cards.splice(0, 5));
786
+ emitEventToPlayer(otherPlayer, {
787
+ type: "handInitialized",
788
+ cards: otherPlayer.hand
789
+ });
790
+ emitEvent(game, {
791
+ type: "playerHandInitialized",
792
+ username: otherPlayer.username,
793
+ numCards: otherPlayer.hand.length
794
+ });
795
+ const startedGame = {
796
+ ...game,
797
+ status: "started",
798
+ expiresAt: Date.now() + EXPIRY_EXTENSION_MS,
799
+ canPlayAt: Date.now() + CAN_PLAY_AT_DELAY_MS
800
+ };
801
+ this.games.set(game.id, startedGame);
802
+ emitEvent(startedGame, { type: "gameStarted" });
803
+ emitEvent(startedGame, {
804
+ type: "expirationUpdated",
805
+ expiresAt: startedGame.expiresAt
806
+ });
807
+ emitEvent(startedGame, {
808
+ type: "canPlayAtUpdated",
809
+ canPlayAt: startedGame.canPlayAt
810
+ });
811
+ return { success: true };
812
+ }
813
+ };
814
+ //#endregion
815
+ export { Manager as t };
816
+
817
+ //# sourceMappingURL=manager-BOymBQ4E.mjs.map