@masonf539/egyptian-war 1.0.1-beta.2 → 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
@@ -52,10 +52,11 @@ Slap patterns are validated by the server and configured in the lobby. The
52
52
  descending rank sequence, including Ace-low and Ace-high sequences. The
53
53
  "Tens" pattern checks only two directly adjacent number cards that total ten;
54
54
  an Ace counts as one. An active player who false-slaps forfeits the configured
55
- number of cards, up to the number they hold. An eliminated player has no cards
56
- to forfeit, so a false slap prevents them from slapping the unchanged pile
57
- again. The cooldown clears when the next card is played, allowing that card to
58
- be slapped and the eliminated player to re-enter immediately.
55
+ number of cards, up to the number they hold. A false slap by an eliminated
56
+ player, or one that forfeits a player's last card, prevents another slap on the
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.2",
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 repeated free slaps 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 an eliminated player's slap cooldown. Once another
89
- * card has been 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 repeated free slaps 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 an eliminated player's slap cooldown. Once another
483
- * card has been 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);
@@ -529,6 +540,7 @@ function applyAction(state, playerId, action) {
529
540
  state.penaltyPileCardCount += cardsForfeited.length;
530
541
  if (player.cards.length === 0) {
531
542
  player.isEliminated = true;
543
+ startEgyptianWarSlapCooldown(state, playerId);
532
544
  if (endIfOnePlayerOwnsRemainingCards(state)) {
533
545
  return state.activityMessage;
534
546
  }
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 an eliminated player who false-slaps 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.
@@ -121,6 +121,6 @@ Checked implementation items do not imply that every multiplayer interaction has
121
121
  - [x] Give the CSS table a layered casino-felt, wooden-rail, depth, and drop-shadow treatment while retaining its original oval shape.
122
122
  - [x] Draw cards as accessible HTML/CSS playing-card faces; do not depend on external card image assets.
123
123
  - [x] Render mirrored card corners, distinct face-card styling, and a Joker with vertical JOKER labels and a decorative center that is distinguishable from a Jack.
124
- - [x] Render slap hands with the original splayed-hand emoji on desktop and a clearer raised-palm variant on coarse-pointer/mobile devices, scale them and their stopping distance with the rendered card size, keep their shadows visually below the hand at every seat rotation, and layer later collected attempts above the official winner.
124
+ - [x] Render slap hands with the splayed-hand emoji on desktop and the backhand emoji on coarse-pointer/mobile devices, scale them and their stopping distance with the rendered card size, keep their shadows visually below the hand at every seat rotation, and layer later collected attempts above the official winner.
125
125
  - [x] Keep the table, player seats, card motion, spectator area, and controls usable on narrow screens and with reduced-motion preferences.
126
126
  - Offer chat per game rather than requiring it in every game; games that rely on secrecy or fast rounds can opt out, while social multiplayer games can enable it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masonf539/egyptian-war",
3
- "version": "1.0.1-beta.2",
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
@@ -86,11 +86,11 @@ function createEgyptianWarSlapHand() {
86
86
 
87
87
  const desktopHand = document.createElement("span");
88
88
  desktopHand.className = "egyptian-war-slap-hand-desktop";
89
- desktopHand.textContent = "💐️";
89
+ desktopHand.textContent = "\u{1F590}\uFE0F";
90
90
 
91
91
  const mobileHand = document.createElement("span");
92
92
  mobileHand.className = "egyptian-war-slap-hand-mobile";
93
- mobileHand.textContent = "✋";
93
+ mobileHand.textContent = "\u{1F91A}";
94
94
 
95
95
  handGlyph.append(desktopHand, mobileHand);
96
96
  return handGlyph;
@@ -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