@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 +5 -4
- package/dist/definition.js +5 -5
- package/dist/engine.d.ts +14 -5
- package/dist/engine.js +25 -13
- package/dist/session.js +7 -3
- package/docs/design.md +2 -2
- package/package.json +1 -1
- package/public/client.js +3 -3
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.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
package/dist/definition.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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 =
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
89
|
-
*
|
|
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
|
-
/**
|
|
475
|
-
|
|
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 =
|
|
483
|
+
player.slapCooldownUntilCardPlayCount =
|
|
484
|
+
(state.cardsPlayedCount ?? 0) + nextEligibleCardOffset;
|
|
480
485
|
}
|
|
481
486
|
/**
|
|
482
|
-
* Returns the status of
|
|
483
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
400
|
-
(0, engine_js_1.checkEgyptianWarSlapCooldown)(this.state, memberId)
|
|
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:
|
|
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
|
|
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
|
|
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
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
|
-
"
|
|
132
|
+
"False slap while out: skip two cards, then slap again on the third.";
|
|
133
133
|
egyptianWarSlapRulesList.appendChild(eliminatedCooldownItem);
|
|
134
134
|
}
|
|
135
135
|
|