@masonf539/egyptian-war 1.0.1-beta.3 → 1.0.1-beta.4

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 CHANGED
@@ -54,8 +54,9 @@ descending rank sequence, including Ace-low and Ace-high sequences. The
54
54
  an Ace counts as one. An active player who false-slaps forfeits the configured
55
55
  number of cards, up to the number they hold. A false slap by an eliminated
56
56
  player, or one that forfeits a player's last card, prevents another slap on the
57
- unchanged pile. The cooldown clears when the next card is played, allowing that
58
- new card to be slapped and the eliminated player to re-enter immediately.
57
+ unchanged pile. A newly eliminated player may try once on the next played card.
58
+ If an eliminated player false-slaps, they must skip the next two played cards
59
+ and may try again when the third card is placed.
59
60
  Invalid reentry attempts use the same visible one-hand slap animation as other
60
61
  false slaps; additional attempts during the cooldown do not create extra hands.
61
62
 
@@ -5,7 +5,7 @@ const game_sdk_1 = require("@masonf539/game-sdk");
5
5
  exports.egyptianWar = {
6
6
  id: "egyptian-war",
7
7
  name: "Egyptian War",
8
- description: "Play cards into a central pile, survive face-card challenges, and race to slap enabled patterns. False slaps carry penalties, and eliminated players must wait for another card before slapping again. The last player holding all the cards wins.",
8
+ description: "Play cards into a central pile, survive face-card challenges, and race to slap enabled patterns. False slaps carry penalties, including a two-card wait for eliminated players. The last player holding all the cards wins.",
9
9
  isPlayable: true,
10
10
  chatEnabled: true,
11
11
  minPlayers: 2,
@@ -39,10 +39,10 @@ exports.egyptianWar = {
39
39
  "If the challenged player uses every attempt without revealing a face card, Ace, or Joker, the player who most recently played a challenge card wins the pile unless the final card creates a valid slap combination.",
40
40
  "When the final failed challenge card creates a valid slap combination, players get a short window of about four seconds to slap; if nobody slaps, the most recent challenge card player wins the pile.",
41
41
  "If the challenged player runs out of cards before completing their attempts, the next player with cards inherits the challenge with the remaining attempts.",
42
- "A player who runs out of cards is skipped until they reenter by winning a valid slap. An eliminated player who false-slaps cannot slap the unchanged pile again, but may slap as soon as the next card is played.",
42
+ "A player who runs out of cards is skipped until they reenter by winning a valid slap. They may try once on the next played card. If an eliminated player false-slaps, they skip the next two played cards and may try again on the third.",
43
43
  "A player who wins the pile places all of its cards at the bottom of their deck without shuffling and begins the next pile.",
44
44
  "When a valid slap combination appears, the first player to slap wins the central pile.",
45
- "Slapping without a valid combination forfeits the configured number of cards face down to the bottom of the pile, or all remaining cards if fewer remain. Penalty cards do not create slap combinations. A player with no cards forfeits no cards for a false slap and must wait for another card before trying again.",
45
+ "Slapping without a valid combination forfeits the configured number of cards face down to the bottom of the pile, or all remaining cards if fewer remain. Penalty cards do not create slap combinations. A player with no cards forfeits no cards for a false slap, skips the next two played cards, and may try again on the third.",
46
46
  "When only one player still has cards, that player wins unless the current face-up pile has a valid slap that allows an eliminated player to reenter.",
47
47
  "If several players slap at nearly the same moment, the pile goes to one of them by weighted chance that favors the earlier slap. The others forfeit nothing.",
48
48
  "The winner is the player who collects every card."
@@ -103,7 +103,7 @@ exports.egyptianWar = {
103
103
  type: "number",
104
104
  key: "falseSlapPenaltyCards",
105
105
  label: "False Slap Penalty",
106
- description: "Cards forfeited to the bottom of the pile for an invalid slap. An eliminated player forfeits no cards and must wait for another card before slapping again.",
106
+ description: "Cards forfeited to the bottom of the pile for an invalid slap. An eliminated player forfeits no cards, skips the next two played cards, and may slap again on the third.",
107
107
  defaultValue: 2,
108
108
  options: [1, 2, 3],
109
109
  unit: "card"
@@ -124,7 +124,7 @@ exports.egyptianWar = {
124
124
  };
125
125
  exports.egyptianWarManifest = {
126
126
  apiVersion: game_sdk_1.gameHubGameApiVersion,
127
- packageVersion: "1.0.1-beta.3",
127
+ packageVersion: "1.0.1-beta.4",
128
128
  definition: exports.egyptianWar,
129
129
  client: {
130
130
  delivery: "embedded",
package/dist/engine.d.ts CHANGED
@@ -81,12 +81,21 @@ export type EgyptianWarDeparture = "removed" | "left";
81
81
  export declare function removeEgyptianWarPlayer(state: EgyptianWarState, playerId: string, departure?: EgyptianWarDeparture): string;
82
82
  export declare function isEgyptianWarPileSlappable(pile: readonly Card[], settings: EgyptianWarSettings): boolean;
83
83
  export declare function isEgyptianWarStateSlappable(state: Pick<EgyptianWarState, "pile" | "penaltyPileCardCount" | "settings">): boolean;
84
- export type EgyptianWarSlapCooldownStatus = "none" | "waiting";
85
- /** Prevent another slap until one more card has been played. */
86
- export declare function startEgyptianWarSlapCooldown(state: EgyptianWarState, playerId: string): void;
84
+ export type EgyptianWarSlapCooldownStatus = {
85
+ status: "none";
86
+ } | {
87
+ status: "waiting";
88
+ cardPlacementsRemaining: number;
89
+ };
90
+ /**
91
+ * Prevent another slap until the requested future card opportunity. An offset
92
+ * of 1 allows the next played card; an offset of 3 skips two cards and allows
93
+ * the third.
94
+ */
95
+ export declare function startEgyptianWarSlapCooldown(state: EgyptianWarState, playerId: string, nextEligibleCardOffset?: number): void;
87
96
  /**
88
- * Returns the status of a player's slap cooldown. Once another card has been
89
- * played, the cooldown clears and that new card may be slapped.
97
+ * Returns the status of a player's slap cooldown, including the number of card
98
+ * placements remaining before another attempt is allowed.
90
99
  */
91
100
  export declare function checkEgyptianWarSlapCooldown(state: EgyptianWarState, playerId: string): EgyptianWarSlapCooldownStatus;
92
101
  export declare function applyEgyptianWarAction(state: EgyptianWarState, playerId: string, action: EgyptianWarAction): string;
package/dist/engine.js CHANGED
@@ -471,27 +471,36 @@ function trackPileVersion(state, run) {
471
471
  }
472
472
  }
473
473
  }
474
- /** Prevent another slap until one more card has been played. */
475
- function startEgyptianWarSlapCooldown(state, playerId) {
474
+ /**
475
+ * Prevent another slap until the requested future card opportunity. An offset
476
+ * of 1 allows the next played card; an offset of 3 skips two cards and allows
477
+ * the third.
478
+ */
479
+ function startEgyptianWarSlapCooldown(state, playerId, nextEligibleCardOffset = 1) {
476
480
  const player = state.players.find((candidate) => candidate.id === playerId);
477
481
  if (!player)
478
482
  return;
479
- player.slapCooldownUntilCardPlayCount = (state.cardsPlayedCount ?? 0) + 1;
483
+ player.slapCooldownUntilCardPlayCount =
484
+ (state.cardsPlayedCount ?? 0) + nextEligibleCardOffset;
480
485
  }
481
486
  /**
482
- * Returns the status of a player's slap cooldown. Once another card has been
483
- * played, the cooldown clears and that new card may be slapped.
487
+ * Returns the status of a player's slap cooldown, including the number of card
488
+ * placements remaining before another attempt is allowed.
484
489
  */
485
490
  function checkEgyptianWarSlapCooldown(state, playerId) {
486
491
  const player = state.players.find((candidate) => candidate.id === playerId);
487
492
  const cooldownTarget = player?.slapCooldownUntilCardPlayCount;
488
493
  if (!player || cooldownTarget == null)
489
- return "none";
494
+ return { status: "none" };
490
495
  const cardsPlayedCount = state.cardsPlayedCount ?? 0;
491
- if (cardsPlayedCount < cooldownTarget)
492
- return "waiting";
496
+ if (cardsPlayedCount < cooldownTarget) {
497
+ return {
498
+ status: "waiting",
499
+ cardPlacementsRemaining: cooldownTarget - cardsPlayedCount
500
+ };
501
+ }
493
502
  player.slapCooldownUntilCardPlayCount = null;
494
- return "none";
503
+ return { status: "none" };
495
504
  }
496
505
  function applyEgyptianWarAction(state, playerId, action) {
497
506
  return trackPileVersion(state, () => applyAction(state, playerId, action));
@@ -510,8 +519,10 @@ function applyAction(state, playerId, action) {
510
519
  const player = state.players[playerIndex];
511
520
  if (action === "slap") {
512
521
  const slapCooldown = checkEgyptianWarSlapCooldown(state, playerId);
513
- if (slapCooldown === "waiting") {
514
- throw new EgyptianWarRuleError("You must wait until the next card is played before slapping again.");
522
+ if (slapCooldown.status === "waiting") {
523
+ const remaining = slapCooldown.cardPlacementsRemaining;
524
+ throw new EgyptianWarRuleError(`You must wait for ${remaining} more card ` +
525
+ `${remaining === 1 ? "placement" : "placements"} before slapping again.`);
515
526
  }
516
527
  const faceUpPile = state.pile.slice(state.penaltyPileCardCount);
517
528
  if (faceUpPile.length === 0) {
@@ -519,9 +530,9 @@ function applyAction(state, playerId, action) {
519
530
  }
520
531
  if (!isEgyptianWarPileSlappable(faceUpPile, state.settings)) {
521
532
  if (player.cards.length === 0) {
522
- startEgyptianWarSlapCooldown(state, playerId);
533
+ startEgyptianWarSlapCooldown(state, playerId, 3);
523
534
  state.activityMessage =
524
- `${player.name} slapped too early and must wait until the next card is played before slapping again.`;
535
+ `${player.name} slapped too early while out and must sit out the next two cards.`;
525
536
  return state.activityMessage;
526
537
  }
527
538
  const cardsForfeited = player.cards.splice(0, state.settings.falseSlapPenaltyCards);
package/dist/session.js CHANGED
@@ -396,11 +396,15 @@ class EgyptianWarSession {
396
396
  return;
397
397
  }
398
398
  const isLiveSlap = action === "slap" && !isTurnTimeout && !isResolvedSlap;
399
- if (isLiveSlap &&
400
- (0, engine_js_1.checkEgyptianWarSlapCooldown)(this.state, memberId) === "waiting") {
399
+ const slapCooldown = isLiveSlap
400
+ ? (0, engine_js_1.checkEgyptianWarSlapCooldown)(this.state, memberId)
401
+ : { status: "none" };
402
+ if (slapCooldown.status === "waiting") {
403
+ const remaining = slapCooldown.cardPlacementsRemaining;
401
404
  respond({
402
405
  success: false,
403
- message: "You must wait until the next card is played before slapping again."
406
+ message: `You must wait for ${remaining} more card ` +
407
+ `${remaining === 1 ? "placement" : "placements"} before slapping again.`
404
408
  });
405
409
  return;
406
410
  }
package/docs/design.md CHANGED
@@ -60,7 +60,7 @@ Checked implementation items do not imply that every multiplayer interaction has
60
60
  - [x] Keep face-down false-slap penalty cards out of slap-pattern validation so repeated penalties cannot manufacture a valid slap.
61
61
  - [x] End the game when only one player has cards and the face-up pile offers no valid slap for an eliminated player to reenter.
62
62
  - [x] Never let a challenger answer their own challenge: when nobody but the challenger can continue, the challenger wins the pile (or the slap window opens).
63
- - [x] Prevent a player who false-slaps while out, or false-slaps away their last card, from repeatedly slapping the unchanged pile; clear that cooldown when the next card is played so they can immediately slap back in.
63
+ - [x] Prevent a player who false-slaps away their last card from retrying the unchanged pile while allowing one attempt on the next played card. When an already eliminated player false-slaps, make them skip two played cards and allow another attempt on the third.
64
64
  - [x] Keep slap-pattern information off the wire: the public state carries a `pileVersion` but no slappable flag, and messages about a pending pile are neutral.
65
65
  - [x] Have slaps carry the `pileVersion` the player last saw. A slap that lost a short race with a card play (previous version, previous pile slappable, under 350 ms) is ignored without a penalty.
66
66
  - [x] Allow a player to leave voluntarily; their cards are held face-down and added to the next pile, just like a host removal. If removing a player leaves nobody holding cards, the remaining cards go to the next player in turn order immediately.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masonf539/egyptian-war",
3
- "version": "1.0.1-beta.3",
3
+ "version": "1.0.1-beta.4",
4
4
  "description": "Egyptian War game plugin for GameHub.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/public/client.js CHANGED
@@ -129,7 +129,7 @@ function renderEgyptianWarSlapRules(settings) {
129
129
 
130
130
  const eliminatedCooldownItem = document.createElement("li");
131
131
  eliminatedCooldownItem.textContent =
132
- "Eliminated player false slap: wait for another card before slapping again.";
132
+ "False slap while out: skip two cards, then slap again on the third.";
133
133
  egyptianWarSlapRulesList.appendChild(eliminatedCooldownItem);
134
134
  }
135
135