@masonf539/egyptian-war 1.0.0 → 1.0.1-beta.1

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/LICENSE CHANGED
@@ -1,27 +1,27 @@
1
- Egyptian War License
2
-
3
- Copyright (c) 2026 Mason Fong
4
-
5
- Required Notice: Copyright (c) 2026 Mason Fong
6
- Required Notice: Egyptian War source code is licensed under the PolyForm Noncommercial License 1.0.0.
7
-
8
- The Egyptian War software, including its source code, browser code, styles,
9
- templates, tests, documentation, rules engine, and networking implementation,
10
- is licensed under the PolyForm Noncommercial License 1.0.0.
11
-
12
- You may use, copy, modify, and distribute the Egyptian War software only for
13
- purposes permitted by that license. Commercial use requires a separate written
14
- license from the copyright holder.
15
-
16
- The complete PolyForm Noncommercial License 1.0.0 is available at:
17
-
18
- https://polyformproject.org/licenses/noncommercial/1.0.0
19
-
20
- The creative assets located in public/assets are part of the Egyptian War software
21
- and are licensed under the PolyForm Noncommercial License 1.0.0, like the rest of this package.
22
-
23
- Third-party dependencies and separately identified third-party materials remain
24
- governed by their respective licenses.
25
-
26
- THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OR CONDITION, TO THE EXTENT
1
+ Egyptian War License
2
+
3
+ Copyright (c) 2026 Mason Fong
4
+
5
+ Required Notice: Copyright (c) 2026 Mason Fong
6
+ Required Notice: Egyptian War source code is licensed under the PolyForm Noncommercial License 1.0.0.
7
+
8
+ The Egyptian War software, including its source code, browser code, styles,
9
+ templates, tests, documentation, rules engine, and networking implementation,
10
+ is licensed under the PolyForm Noncommercial License 1.0.0.
11
+
12
+ You may use, copy, modify, and distribute the Egyptian War software only for
13
+ purposes permitted by that license. Commercial use requires a separate written
14
+ license from the copyright holder.
15
+
16
+ The complete PolyForm Noncommercial License 1.0.0 is available at:
17
+
18
+ https://polyformproject.org/licenses/noncommercial/1.0.0
19
+
20
+ The creative assets located in public/assets are part of the Egyptian War software
21
+ and are licensed under the PolyForm Noncommercial License 1.0.0, like the rest of this package.
22
+
23
+ Third-party dependencies and separately identified third-party materials remain
24
+ governed by their respective licenses.
25
+
26
+ THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OR CONDITION, TO THE EXTENT
27
27
  PERMITTED BY LAW.
package/README.md CHANGED
@@ -1,66 +1,68 @@
1
- # Egyptian War for GameHub
2
-
3
- This directory contains the server-authoritative Egyptian War game plugin for
4
- GameHub and is exported to the `egyptian-war-standalone` branch for release.
5
-
6
- Egyptian War is designed to be installed and run by GameHub through the
7
- `@masonf539/game-sdk` contracts. It is independently versioned, but it is not a
8
- standalone web application and depends on GameHub for rooms, networking, the
9
- lobby, shared settings, and browser hosting.
10
-
11
- The package owns its rules, state engine, slap arbitration, tests, browser
12
- presentation, game-specific audio, preview media, and design documentation.
13
- GameHub supplies rooms, members, spectators, transport, and shared services.
14
-
15
- `src/session.ts` owns the authoritative runtime lifecycle, including turn
16
- timers, slap collection, pause/disconnect recovery, animations, member
17
- removal, and completion. `public/client.js` owns the browser controller and
18
- registers it with GameHub's generic client host.
19
-
20
- Game-specific music, effects, and picker previews live under `public/assets`
21
- and are served by GameHub from `/games/egyptian-war`.
22
- The package-owned `public/register.js` registers those sounds with GameHub's
23
- shared audio service.
24
- The game board lives in `public/template.html`, and its presentation lives in
25
- `public/style.css`. GameHub injects the fragment into a package-specific root;
26
- the browser controller scopes its element lookups to that root.
27
- During active play, the stylesheet fixes the board to the available viewport,
28
- accounts for mobile safe areas, and keeps its compact Pause/connection row
29
- below GameHub's Settings shortcut. Full Screen and confirmed Exit/Leave
30
- actions remain platform-owned controls in GameHub's reusable Game tab. The
31
- shortcut is mounted inside the active game viewport, while the Settings tabs
32
- move into one horizontal row at medium and small widths.
33
- Egyptian War declares `play-card` and `slap` input actions for GameHub's
34
- package-aware Keybinds tab. Their defaults are E and Space respectively, and
35
- the game subscribes through the shared input service so remapping remains a
36
- personal GameHub preference.
37
- The package's public state also states whether the current player may legally
38
- play a card, keeping the action disabled during the final slap window after a
39
- failed challenge. Its numeric countdown stays fixed at the right edge inside
40
- the full-width timer track while the yellow fill passes beneath it, and the
41
- complete timer scales with the action controls on narrow screens.
42
-
43
- The package consumes an exact `@masonf539/game-sdk` release. Once both packages
44
- are published, GameHub installs an exact Egyptian War version and enables it
45
- through `gamehub.config.json`. GameHub's `npm run verify:packages` command
46
- currently verifies that both packed artifacts work without monorepo source.
47
-
48
- ## Slap rules and penalties
49
-
50
- Slap patterns are validated by the server and configured in the lobby. The
51
- "Three in a Row" pattern checks the latest three cards for an ascending or
52
- descending rank sequence, including Ace-low and Ace-high sequences. The
53
- "Tens" pattern checks only two directly adjacent number cards that total ten;
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 instead makes them miss the next card's slap
57
- opportunity. A valid slap can still let an eliminated player re-enter when
58
- their cooldown is clear.
59
-
60
- ## Independent repository preparation
61
-
62
- The package directory includes its own README, license, TypeScript settings,
63
- tests, public assets, and package metadata. Its only GameHub code dependency is
64
- the exact `@masonf539/game-sdk` version declared in `package.json`. A standalone
65
- lockfile will be generated after that SDK version is published so it records
66
- the registry artifact rather than the monorepo workspace link.
1
+ # Egyptian War for GameHub
2
+
3
+ This repository contains the server-authoritative Egyptian War game plugin
4
+ for GameHub.
5
+
6
+ Egyptian War is designed to be installed and run by GameHub through the
7
+ `@masonf539/game-sdk` contracts. It is independently versioned, but it is not a
8
+ standalone web application and depends on GameHub for rooms, networking, the
9
+ lobby, shared settings, and browser hosting.
10
+
11
+ The package owns its rules, state engine, slap arbitration, tests, browser
12
+ presentation, game-specific audio, preview media, and design documentation.
13
+ GameHub supplies rooms, members, spectators, transport, and shared services.
14
+
15
+ `src/session.ts` owns the authoritative runtime lifecycle, including turn
16
+ timers, slap collection, pause/disconnect recovery, animations, member
17
+ removal, and completion. `public/client.js` owns the browser controller and
18
+ registers it with GameHub's generic client host.
19
+
20
+ Game-specific music, effects, and picker previews live under `public/assets`
21
+ and are served by GameHub from `/games/egyptian-war`.
22
+ The package-owned `public/register.js` registers those sounds with GameHub's
23
+ shared audio service.
24
+ The game board lives in `public/template.html`, and its presentation lives in
25
+ `public/style.css`. GameHub injects the fragment into a package-specific root;
26
+ the browser controller scopes its element lookups to that root.
27
+ During active play, the stylesheet fixes the board to the available viewport,
28
+ accounts for mobile safe areas, and keeps its compact Pause/connection row
29
+ below GameHub's Settings shortcut. Full Screen and confirmed Exit/Leave
30
+ actions remain platform-owned controls in GameHub's reusable Game tab. The
31
+ shortcut is mounted inside the active game viewport, while the Settings tabs
32
+ move into one horizontal row at medium and small widths.
33
+ Egyptian War declares `play-card` and `slap` input actions for GameHub's
34
+ package-aware Keybinds tab. Their defaults are E and Space respectively, and
35
+ the game subscribes through the shared input service so remapping remains a
36
+ personal GameHub preference.
37
+ The package's public state also states whether the current player may legally
38
+ play a card, keeping the action disabled during the final slap window after a
39
+ failed challenge. Its numeric countdown stays fixed at the right edge inside
40
+ the full-width timer track while the yellow fill passes beneath it, and the
41
+ complete timer scales with the action controls on narrow screens.
42
+
43
+ The package consumes an exact `@masonf539/game-sdk` release. Once both packages
44
+ are published, GameHub installs an exact Egyptian War version and enables it
45
+ through `gamehub.config.json`. GameHub's `npm run verify:packages` command
46
+ currently verifies that both packed artifacts work without monorepo source.
47
+
48
+ ## Slap rules and penalties
49
+
50
+ Slap patterns are validated by the server and configured in the lobby. The
51
+ "Three in a Row" pattern checks the latest three cards for an ascending or
52
+ descending rank sequence, including Ace-low and Ace-high sequences. The
53
+ "Tens" pattern checks only two directly adjacent number cards that total ten;
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.
59
+ Invalid reentry attempts use the same visible one-hand slap animation as other
60
+ false slaps; additional attempts during the cooldown do not create extra hands.
61
+
62
+ ## Installation preparation
63
+
64
+ The repository includes its own README, license, TypeScript settings, tests,
65
+ public assets, package metadata, and ignore rules. Its only GameHub code
66
+ dependency is the exact `@masonf539/game-sdk` version declared in `package.json`.
67
+ A lockfile will be generated after that SDK version is published so it records
68
+ the registry artifact rather than the former monorepo workspace link.
@@ -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 skip the next card's slap opportunity after a false slap. 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, and eliminated players must wait for another card before slapping again. 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 must miss the next card's slap opportunity before trying again.",
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.",
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, but will miss the next card's slap opportunity before being able to try 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 and must wait for another card before trying again.",
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, but must miss the next card's slap opportunity.",
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.",
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.0",
127
+ packageVersion: "1.0.1-beta.1",
128
128
  definition: exports.egyptianWar,
129
129
  client: {
130
130
  delivery: "embedded",
package/dist/engine.d.ts CHANGED
@@ -81,12 +81,12 @@ 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" | "blocked";
85
- /** Start a cooldown that lasts until one more card has been played. */
84
+ export type EgyptianWarSlapCooldownStatus = "none" | "waiting";
85
+ /** Prevent repeated free slaps until one more card has been played. */
86
86
  export declare function startEgyptianWarSlapCooldown(state: EgyptianWarState, playerId: string): void;
87
87
  /**
88
- * Returns the status of an eliminated player's one-card slap cooldown.
89
- * The next card's opportunity is consumed the first time they try to slap on it.
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.
90
90
  */
91
91
  export declare function checkEgyptianWarSlapCooldown(state: EgyptianWarState, playerId: string): EgyptianWarSlapCooldownStatus;
92
92
  export declare function applyEgyptianWarAction(state: EgyptianWarState, playerId: string, action: EgyptianWarAction): string;
package/dist/engine.js CHANGED
@@ -471,7 +471,7 @@ function trackPileVersion(state, run) {
471
471
  }
472
472
  }
473
473
  }
474
- /** Start a cooldown that lasts until one more card has been played. */
474
+ /** Prevent repeated free slaps until one more card has been played. */
475
475
  function startEgyptianWarSlapCooldown(state, playerId) {
476
476
  const player = state.players.find((candidate) => candidate.id === playerId);
477
477
  if (!player)
@@ -479,8 +479,8 @@ function startEgyptianWarSlapCooldown(state, playerId) {
479
479
  player.slapCooldownUntilCardPlayCount = (state.cardsPlayedCount ?? 0) + 1;
480
480
  }
481
481
  /**
482
- * Returns the status of an eliminated player's one-card slap cooldown.
483
- * The next card's opportunity is consumed the first time they try to slap on it.
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.
484
484
  */
485
485
  function checkEgyptianWarSlapCooldown(state, playerId) {
486
486
  const player = state.players.find((candidate) => candidate.id === playerId);
@@ -491,7 +491,7 @@ function checkEgyptianWarSlapCooldown(state, playerId) {
491
491
  if (cardsPlayedCount < cooldownTarget)
492
492
  return "waiting";
493
493
  player.slapCooldownUntilCardPlayCount = null;
494
- return cardsPlayedCount === cooldownTarget ? "blocked" : "none";
494
+ return "none";
495
495
  }
496
496
  function applyEgyptianWarAction(state, playerId, action) {
497
497
  return trackPileVersion(state, () => applyAction(state, playerId, action));
@@ -513,9 +513,6 @@ function applyAction(state, playerId, action) {
513
513
  if (slapCooldown === "waiting") {
514
514
  throw new EgyptianWarRuleError("You must wait until the next card is played before slapping again.");
515
515
  }
516
- if (slapCooldown === "blocked") {
517
- throw new EgyptianWarRuleError("Your one-card slap cooldown blocked that slap.");
518
- }
519
516
  const faceUpPile = state.pile.slice(state.penaltyPileCardCount);
520
517
  if (faceUpPile.length === 0) {
521
518
  throw new EgyptianWarRuleError("There are no face-up cards to slap.");
package/dist/session.js CHANGED
@@ -396,6 +396,14 @@ 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") {
401
+ respond({
402
+ success: false,
403
+ message: "You must wait until the next card is played before slapping again."
404
+ });
405
+ return;
406
+ }
399
407
  if (this.isAnimating &&
400
408
  !isResolvedSlap &&
401
409
  !(isLiveSlap && this.animationAllowsSlaps)) {
@@ -403,22 +411,6 @@ class EgyptianWarSession {
403
411
  return;
404
412
  }
405
413
  if (isLiveSlap) {
406
- const slapCooldown = (0, engine_js_1.checkEgyptianWarSlapCooldown)(this.state, memberId);
407
- if (slapCooldown === "waiting") {
408
- respond({
409
- success: false,
410
- message: "You must wait until the next card is played before slapping again."
411
- });
412
- return;
413
- }
414
- if (slapCooldown === "blocked") {
415
- respond({
416
- success: false,
417
- message: "Your one-card slap cooldown blocked that slap."
418
- });
419
- this.context.broadcastState();
420
- return;
421
- }
422
414
  if ((0, engine_js_1.isEgyptianWarStateSlappable)(this.state)) {
423
415
  this.collectSlap(memberId, respond);
424
416
  return;
@@ -436,16 +428,6 @@ class EgyptianWarSession {
436
428
  respond({ success: false, message: "Wait for the current action to finish." });
437
429
  return;
438
430
  }
439
- const eliminatedPlayer = this.state.players.find((player) => player.id === memberId);
440
- const faceUpCardCount = this.state.pile.length - this.state.penaltyPileCardCount;
441
- if (faceUpCardCount > 0 && eliminatedPlayer?.cards.length === 0) {
442
- (0, engine_js_1.startEgyptianWarSlapCooldown)(this.state, memberId);
443
- respond({
444
- success: false,
445
- message: "You are out of cards and must wait until the next card is played before slapping again."
446
- });
447
- return;
448
- }
449
431
  }
450
432
  if (this.pendingSlaps !== null && !isResolvedSlap) {
451
433
  respond({ success: false, message: "A slap is being resolved. Please wait." });
package/docs/design.md CHANGED
@@ -1,126 +1,126 @@
1
- # Egyptian War Design
2
-
3
- Egyptian War is a versioned game plugin made for GameHub. The package owns the
4
- game-specific server and browser behavior, while GameHub provides the room,
5
- transport, lobby, spectator, and hosting services required to run it.
6
-
7
- ## Implementation status
8
-
9
- This document distinguishes implemented behavior from planned gameplay. A checked item means the behavior exists in the application, not merely that its design has been selected.
10
- Checked implementation items do not imply that every multiplayer interaction has completed manual gameplay testing; automated engine, arbitration, Socket.IO integration, and browser-animation tests run during the Docker build.
11
-
12
- ### Game availability
13
-
14
- - [x] Keep Egyptian War visible so players can read its rules and configure its settings.
15
- - [x] Represent playability in the shared game definition.
16
- - [x] Disable the Start Game button and show a coming-soon status when the selected game is not playable.
17
- - [x] Reject attempts to start an unplayable game on the server.
18
- - [x] Mark Egyptian War playable after its server-authoritative game loop and client interface are implemented.
19
-
20
- ### Existing configuration
21
-
22
- - [x] Support 2–6 players in the game definition.
23
- - [x] Define standard cards and optional Jokers.
24
- - [x] Configure one, two, or three complete decks, defaulting to one; include two Jokers per deck when Jokers are enabled.
25
- - [x] Configure Jokers, doubles, sandwiches, three in a row, top-bottom, adjacent-card tens, and marriage as lobby settings.
26
- - [x] Apply the saved settings to server-side slap validation.
27
- - [x] Configure the false-slap penalty to 1, 2, or 3 cards, defaulting to 2.
28
- - [x] Configure a 5–120 second player timer with a default of 15 seconds using synchronized slider and number controls.
29
- - [x] Reset the active player's timer after they win a valid slap on their own turn.
30
-
31
- ### Server-authoritative gameplay
32
-
33
- - [x] Implement server-side deck creation and cryptographic shuffling.
34
- - [x] Implement face-down dealing and public state without hidden decks.
35
- - [x] Implement random first-player selection.
36
- - [x] Initialize the engine when a live room starts Egyptian War.
37
- - [x] Enforce clockwise turns.
38
- - [x] Implement Jack, Queen, King, Ace, and Joker challenge attempts (Jokers allow 5 attempts).
39
- - [x] Award the pile to the most recent challenge-card player when a challenge fails, except while a valid final-attempt slap window is active.
40
- - [x] Validate slaps on the server and resolve competing slaps fairly.
41
- - [x] Use server receipt time for slap candidates so clients cannot improve their position by delaying timing acknowledgements. Collect requests for 200 ms, assign a fixed server-controlled 25 ms uncertainty to each candidate, compare candidates within the resulting 50 ms window, and select eligible candidates with smooth exponential weighting driven by Node's cryptographically secure random source.
42
- - [x] Finish accepted slap arbitration before honoring a host pause, and allow candidates accepted before a disconnect-triggered pause to resolve instead of silently discarding them.
43
- - [x] Skip players who run out of cards; continue the turn with the next eligible player.
44
- - [x] Pass an unfinished challenge to the next eligible player with the remaining attempts unchanged when its responder runs out of cards.
45
- - [x] Allow eliminated players to reenter through a valid slap.
46
- - [x] Detect when one player owns every card and end the game.
47
- - [x] Return the room to the lobby after the game ends.
48
- - [x] Pause the active game when a player disconnects; allow the host to resume while the player is away, and restore the saved pause preference after they reconnect. Lobby-only disconnects still expire after two minutes.
49
- - [x] Gray out disconnected players' table seats and show a yellow disconnected alert beneath each seat.
50
- - [x] Provide a host-only Close Lobby control in the lobby and expose confirmed Exit Game or Leave Game actions through GameHub's reusable Game settings tab.
51
- - [x] Hide the room code by default for non-host players while showing it by default to the host.
52
- - [x] Preserve a reconnecting active player's remaining turn time and allow one short grace period per turn, capped at the configured turn duration, then continue normal automatic turn play if the timer expires.
53
- - [x] Send recent face-up cards in reconnect snapshots so the board restores the visible pile after reconnecting.
54
- - [x] Reject slap actions when there are no face-up cards available to slap, including immediately after a pile transfer or reconnect.
55
- - [x] Let the host kick a disconnected player during a game; hold their cards face-down until the current pile is awarded, then add them to the next pile.
56
- - [x] Tell a kicked player they were removed after disconnecting if they later try to reconnect, and notify the table when their cards are deferred.
57
- - [x] Play the game-winner animation when kicking opponents leaves the host as the last player.
58
- - [x] Keep slap actions available throughout active play, including for players out of cards, while rejecting actions during pile-transfer, penalty, and win animations and during pauses. Slaps are still collected during the short card-play animation, because the card is already on the pile.
59
- - [x] Penalize an invalid slap by moving the configured number of the player's cards to the bottom of the pile; forfeit any remaining cards if fewer than the configured penalty remain.
60
- - [x] Keep face-down false-slap penalty cards out of slap-pattern validation so repeated penalties cannot manufacture a valid slap.
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
- - [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] Give an eliminated player who false-slaps a one-card cooldown: they cannot win on the next card's slap opportunity, and they forfeit no cards, since they have none.
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
- - [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
- - [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.
67
- - [x] Allow only the host to pause and resume the game; enforce pause state on the server.
68
- - [x] Support per-game chat capability; validate room membership, message length, and send rate on the server.
69
- - [x] Preserve a final played card and winner state long enough for the win animation before returning to the lobby.
70
- - [x] Enforce the player timer on the server and pause it during host pauses and action animations; add a short reconnect grace period, then auto-play on timeout.
71
- - [x] When the final failed challenge card creates a valid slap, allow slaps for a short fixed window (four seconds, not a whole turn) before awarding the pile to the challenge owner.
72
- - [x] Let the host end an active game and return everyone to the lobby after confirming in the browser.
73
-
74
- ### Client gameplay
75
-
76
- - [x] Keep active gameplay fixed to the usable mobile viewport so browser chrome and the on-screen keyboard cannot push the board off-screen.
77
- - [x] Reserve space for GameHub's compact Settings shortcut above the package-owned Pause and connection-latency row on narrow screens.
78
- - [x] Render each player's public state without revealing hidden cards.
79
- - [x] Render the central pile and the most recently played card.
80
- - [x] Provide server-validated play-card and slap actions.
81
- - [x] Show turn, challenge, elimination, pile-winner, and game-winner updates.
82
- - [x] Replace the separate player list with a responsive table arena showing each player's avatar, name, and card count around the table.
83
- - [x] Render physical-looking cards and animate played cards into the central pile.
84
- - [x] Place play-card and slap controls immediately below the arena.
85
- - [x] Display action and challenge messages on the table rather than below it.
86
- - [x] Show late joiners in a dedicated in-game spectator list, including connection status and host removal controls.
87
- - [x] Preserve spectator-list updates that arrive while the game client mounts so each spectator sees themselves, allow spectators to return to the lobby through a dedicated confirmation, and offer lobby waiters a Spectate Game action while play remains active.
88
- - [x] Lay out each player avatar beside their name and card count, with an underline beneath the player details.
89
- - [x] Highlight the entire current player's seat.
90
- - [x] Animate cards moving from the pile into the pile winner's area and outline the winner in gold.
91
- - [x] Animate a hand moving from the slapper's seat to the pile for valid and invalid slap attempts.
92
- - [x] Animate and retain the final played card while highlighting the game winner in gold.
93
- - [x] Place a host-only pause/resume button at the upper-right of the gameplay panel.
94
- - [x] Place a host-only Close Lobby control in the lobby and route the Game settings tab's Exit/Leave action through the appropriate in-app confirmation modal.
95
- - [x] Put a host-only Kick button beside a disconnected player's table alert.
96
- - [x] Make the slap-rules list collapsible.
97
- - [x] On narrow screens, place the collapsed rules Show control directly beside the Slap Rules label without changing the expanded panel or Hide control.
98
- - [x] Show enabled slap patterns and the false-slap penalty in the upper-left of the gameplay panel.
99
- - [x] Keep turn-state messages neutral; do not announce when the current pile matches a slap pattern.
100
- - [x] Show a chat panel only for games whose definitions enable chat.
101
- - [x] Show the synchronized turn countdown below the table while reserving its layout space when hidden.
102
- - [x] Keep the current whole seconds stationary at the right inside the timer track above its moving fill, align the timer to both action-button edges, and scale the complete control group together on narrow screens.
103
- - [x] Decorate the countdown's uncovered track with a blue gradient and progressively revealed sleep marks, ending with a sleeping bear, without changing authoritative timer behavior.
104
- - [x] Show each browser its latest display-only network round-trip time in the upper-right; never use that client-acknowledged measurement for slap arbitration.
105
- - [x] Show server connection state as a colored dot with status text on the main menu, in the lobby, and beside the gameplay controls.
106
- - [x] Fit active gameplay into the browser viewport without page scrolling; overlay Chat and Spectators in one wide bottom-left tab panel with an explicit viewport-relative Expand/Collapse control; place the timer and same-level action buttons in the open bottom-right area; and show turn text directly above the central pile count.
107
- - [x] Let the Chat/Spectators panel collapse into a small round chat button: it opens at medium size by default, the arrow switches between medium and large, and a small diagonal-arrow button on the panel's corner collapses it to the circle. Reopening restores the size it had before it was collapsed, a new game starts at medium again, and the circle shows an unread count (capped at 9+) for messages from other players that arrived while it was collapsed.
108
- - [x] Give the side-panel expand and minimize controls separate, lighter utility-button surfaces so they remain visually distinct from the Chat and Spectators tabs.
109
- - [x] Keep the collapsed Slap Rules Show button in the top-right while shrinking the surrounding box around its title at every viewport size, then restore the full rules width when expanded.
110
- - [x] Style "Play a Card" and "Slap" buttons as prominent game-action controls, with a secondary "Bring the pain" label and fire accents on Slap.
111
- - [x] Keep slaps available during active play even when no pattern matches; disable them only when no face-up cards are present, the game is paused or finished, or a non-card-play animation is in progress.
112
- - [x] Disable Play a Card for every player while a completed challenge is waiting through its final slap window, even though the next player remains identified in public state.
113
- - [x] Restore active players from a complete reconnect snapshot.
114
- - [x] Reuse the limited public game-state projection for spectators so they receive the visible pile, public player state, timers, and live animations without hidden decks.
115
- - [x] Use game-specific background music and synchronize card, slap, pile-win, and game-win sounds with their corresponding presentation animations.
116
- - [x] Expose persistent, independently adjustable music, lobby alert, and gameplay volume controls through the personal GameHub Settings Audio tab (0% disables a category), and wait for browser-approved user interaction before starting music.
117
-
118
- ### Table presentation
119
-
120
- - [x] Draw the table with CSS until a project-owned table image is available.
121
- - [x] Give the CSS table a layered casino-felt, wooden-rail, depth, and drop-shadow treatment while retaining its original oval shape.
122
- - [x] Draw cards as accessible HTML/CSS playing-card faces; do not depend on external card image assets.
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] Scale slap hands 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
- - [x] Keep the table, player seats, card motion, spectator area, and controls usable on narrow screens and with reduced-motion preferences.
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.
1
+ # Egyptian War Design
2
+
3
+ Egyptian War is a versioned game plugin made for GameHub. The package owns the
4
+ game-specific server and browser behavior, while GameHub provides the room,
5
+ transport, lobby, spectator, and hosting services required to run it.
6
+
7
+ ## Implementation status
8
+
9
+ This document distinguishes implemented behavior from planned gameplay. A checked item means the behavior exists in the application, not merely that its design has been selected.
10
+ Checked implementation items do not imply that every multiplayer interaction has completed manual gameplay testing; automated engine, arbitration, Socket.IO integration, and browser-animation tests run during the Docker build.
11
+
12
+ ### Game availability
13
+
14
+ - [x] Keep Egyptian War visible so players can read its rules and configure its settings.
15
+ - [x] Represent playability in the shared game definition.
16
+ - [x] Disable the Start Game button and show a coming-soon status when the selected game is not playable.
17
+ - [x] Reject attempts to start an unplayable game on the server.
18
+ - [x] Mark Egyptian War playable after its server-authoritative game loop and client interface are implemented.
19
+
20
+ ### Existing configuration
21
+
22
+ - [x] Support 2–6 players in the game definition.
23
+ - [x] Define standard cards and optional Jokers.
24
+ - [x] Configure one, two, or three complete decks, defaulting to one; include two Jokers per deck when Jokers are enabled.
25
+ - [x] Configure Jokers, doubles, sandwiches, three in a row, top-bottom, adjacent-card tens, and marriage as lobby settings.
26
+ - [x] Apply the saved settings to server-side slap validation.
27
+ - [x] Configure the false-slap penalty to 1, 2, or 3 cards, defaulting to 2.
28
+ - [x] Configure a 5–120 second player timer with a default of 15 seconds using synchronized slider and number controls.
29
+ - [x] Reset the active player's timer after they win a valid slap on their own turn.
30
+
31
+ ### Server-authoritative gameplay
32
+
33
+ - [x] Implement server-side deck creation and cryptographic shuffling.
34
+ - [x] Implement face-down dealing and public state without hidden decks.
35
+ - [x] Implement random first-player selection.
36
+ - [x] Initialize the engine when a live room starts Egyptian War.
37
+ - [x] Enforce clockwise turns.
38
+ - [x] Implement Jack, Queen, King, Ace, and Joker challenge attempts (Jokers allow 5 attempts).
39
+ - [x] Award the pile to the most recent challenge-card player when a challenge fails, except while a valid final-attempt slap window is active.
40
+ - [x] Validate slaps on the server and resolve competing slaps fairly.
41
+ - [x] Use server receipt time for slap candidates so clients cannot improve their position by delaying timing acknowledgements. Collect requests for 300 ms, assign a fixed server-controlled 25 ms uncertainty to each candidate, compare candidates within the resulting 50 ms window, and select eligible candidates with smooth exponential weighting driven by Node's cryptographically secure random source.
42
+ - [x] Finish accepted slap arbitration before honoring a host pause, and allow candidates accepted before a disconnect-triggered pause to resolve instead of silently discarding them.
43
+ - [x] Skip players who run out of cards; continue the turn with the next eligible player.
44
+ - [x] Pass an unfinished challenge to the next eligible player with the remaining attempts unchanged when its responder runs out of cards.
45
+ - [x] Allow eliminated players to reenter through a valid slap.
46
+ - [x] Detect when one player owns every card and end the game.
47
+ - [x] Return the room to the lobby after the game ends.
48
+ - [x] Pause the active game when a player disconnects; allow the host to resume while the player is away, and restore the saved pause preference after they reconnect. Lobby-only disconnects still expire after two minutes.
49
+ - [x] Gray out disconnected players' table seats and show a yellow disconnected alert beneath each seat.
50
+ - [x] Provide a host-only Close Lobby control in the lobby and expose confirmed Exit Game or Leave Game actions through GameHub's reusable Game settings tab.
51
+ - [x] Hide the room code by default for non-host players while showing it by default to the host.
52
+ - [x] Preserve a reconnecting active player's remaining turn time and allow one short grace period per turn, capped at the configured turn duration, then continue normal automatic turn play if the timer expires.
53
+ - [x] Send recent face-up cards in reconnect snapshots so the board restores the visible pile after reconnecting.
54
+ - [x] Reject slap actions when there are no face-up cards available to slap, including immediately after a pile transfer or reconnect.
55
+ - [x] Let the host kick a disconnected player during a game; hold their cards face-down until the current pile is awarded, then add them to the next pile.
56
+ - [x] Tell a kicked player they were removed after disconnecting if they later try to reconnect, and notify the table when their cards are deferred.
57
+ - [x] Play the game-winner animation when kicking opponents leaves the host as the last player.
58
+ - [x] Keep slap actions available throughout active play, including for players out of cards, while rejecting actions during pile-transfer, penalty, and win animations and during pauses. Slaps are still collected during the short card-play animation, because the card is already on the pile.
59
+ - [x] Penalize an invalid slap by moving the configured number of the player's cards to the bottom of the pile; forfeit any remaining cards if fewer than the configured penalty remain.
60
+ - [x] Keep face-down false-slap penalty cards out of slap-pattern validation so repeated penalties cannot manufacture a valid slap.
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
+ - [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.
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
+ - [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
+ - [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.
67
+ - [x] Allow only the host to pause and resume the game; enforce pause state on the server.
68
+ - [x] Support per-game chat capability; validate room membership, message length, and send rate on the server.
69
+ - [x] Preserve a final played card and winner state long enough for the win animation before returning to the lobby.
70
+ - [x] Enforce the player timer on the server and pause it during host pauses and action animations; add a short reconnect grace period, then auto-play on timeout.
71
+ - [x] When the final failed challenge card creates a valid slap, allow slaps for a short fixed window (four seconds, not a whole turn) before awarding the pile to the challenge owner.
72
+ - [x] Let the host end an active game and return everyone to the lobby after confirming in the browser.
73
+
74
+ ### Client gameplay
75
+
76
+ - [x] Keep active gameplay fixed to the usable mobile viewport so browser chrome and the on-screen keyboard cannot push the board off-screen.
77
+ - [x] Reserve space for GameHub's compact Settings shortcut above the package-owned Pause and connection-latency row on narrow screens.
78
+ - [x] Render each player's public state without revealing hidden cards.
79
+ - [x] Render the central pile and the most recently played card.
80
+ - [x] Provide server-validated play-card and slap actions.
81
+ - [x] Show turn, challenge, elimination, pile-winner, and game-winner updates.
82
+ - [x] Replace the separate player list with a responsive table arena showing each player's avatar, name, and card count around the table.
83
+ - [x] Render physical-looking cards and animate played cards into the central pile.
84
+ - [x] Place play-card and slap controls immediately below the arena.
85
+ - [x] Display action and challenge messages on the table rather than below it.
86
+ - [x] Show late joiners in a dedicated in-game spectator list, including connection status and host removal controls.
87
+ - [x] Preserve spectator-list updates that arrive while the game client mounts so each spectator sees themselves, allow spectators to return to the lobby through a dedicated confirmation, and offer lobby waiters a Spectate Game action while play remains active.
88
+ - [x] Lay out each player avatar beside their name and card count, with an underline beneath the player details.
89
+ - [x] Highlight the entire current player's seat.
90
+ - [x] Animate cards moving from the pile into the pile winner's area and outline the winner in gold.
91
+ - [x] Animate one hand moving from the slapper's seat to the pile for valid and invalid slap attempts, including invalid reentry attempts by eliminated players; suppress extra hands while their cooldown is active.
92
+ - [x] Animate and retain the final played card while highlighting the game winner in gold.
93
+ - [x] Place a host-only pause/resume button at the upper-right of the gameplay panel.
94
+ - [x] Place a host-only Close Lobby control in the lobby and route the Game settings tab's Exit/Leave action through the appropriate in-app confirmation modal.
95
+ - [x] Put a host-only Kick button beside a disconnected player's table alert.
96
+ - [x] Make the slap-rules list collapsible.
97
+ - [x] On narrow screens, place the collapsed rules Show control directly beside the Slap Rules label without changing the expanded panel or Hide control.
98
+ - [x] Show enabled slap patterns and the false-slap penalty in the upper-left of the gameplay panel.
99
+ - [x] Keep turn-state messages neutral; do not announce when the current pile matches a slap pattern.
100
+ - [x] Show a chat panel only for games whose definitions enable chat.
101
+ - [x] Show the synchronized turn countdown below the table while reserving its layout space when hidden.
102
+ - [x] Keep the current whole seconds stationary at the right inside the timer track above its moving fill, align the timer to both action-button edges, and scale the complete control group together on narrow screens.
103
+ - [x] Decorate the countdown's uncovered track with a blue gradient and progressively revealed sleep marks, ending with a sleeping bear, without changing authoritative timer behavior.
104
+ - [x] Show each browser its latest display-only network round-trip time in the upper-right; never use that client-acknowledged measurement for slap arbitration.
105
+ - [x] Show server connection state as a colored dot with status text on the main menu, in the lobby, and beside the gameplay controls.
106
+ - [x] Fit active gameplay into the browser viewport without page scrolling; overlay Chat and Spectators in one wide bottom-left tab panel with an explicit viewport-relative Expand/Collapse control; place the timer and same-level action buttons in the open bottom-right area; and show turn text directly above the central pile count.
107
+ - [x] Let the Chat/Spectators panel collapse into a small round chat button: it opens at medium size by default, the arrow switches between medium and large, and a small diagonal-arrow button on the panel's corner collapses it to the circle. Reopening restores the size it had before it was collapsed, a new game starts at medium again, and the circle shows an unread count (capped at 9+) for messages from other players that arrived while it was collapsed.
108
+ - [x] Give the side-panel expand and minimize controls separate, lighter utility-button surfaces so they remain visually distinct from the Chat and Spectators tabs.
109
+ - [x] Keep the collapsed Slap Rules Show button in the top-right while shrinking the surrounding box around its title at every viewport size, then restore the full rules width when expanded.
110
+ - [x] Style "Play a Card" and "Slap" buttons as prominent game-action controls, with a secondary "Bring the pain" label and fire accents on Slap.
111
+ - [x] Keep slaps available during active play even when no pattern matches; disable them only when no face-up cards are present, the game is paused or finished, or a non-card-play animation is in progress.
112
+ - [x] Disable Play a Card for every player while a completed challenge is waiting through its final slap window, even though the next player remains identified in public state.
113
+ - [x] Restore active players from a complete reconnect snapshot.
114
+ - [x] Reuse the limited public game-state projection for spectators so they receive the visible pile, public player state, timers, and live animations without hidden decks.
115
+ - [x] Use game-specific background music and synchronize card, slap, pile-win, and game-win sounds with their corresponding presentation animations.
116
+ - [x] Expose persistent, independently adjustable music, lobby alert, and gameplay volume controls through the personal GameHub Settings Audio tab (0% disables a category), and wait for browser-approved user interaction before starting music.
117
+
118
+ ### Table presentation
119
+
120
+ - [x] Draw the table with CSS until a project-owned table image is available.
121
+ - [x] Give the CSS table a layered casino-felt, wooden-rail, depth, and drop-shadow treatment while retaining its original oval shape.
122
+ - [x] Draw cards as accessible HTML/CSS playing-card faces; do not depend on external card image assets.
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 a package-owned palm graphic for consistent mobile orientation, 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
+ - [x] Keep the table, player seats, card motion, spectator area, and controls usable on narrow screens and with reduced-motion preferences.
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.