diff --git a/docs/documentation/secret-state.md b/docs/documentation/secret-state.md index 6b469bf18..a172608a3 100644 --- a/docs/documentation/secret-state.md +++ b/docs/documentation/secret-state.md @@ -1,7 +1,7 @@ # Secret State In some games you might need to hide information from -players. For example, you might not want to reveal the +players or spectators. For example, you might not want to reveal the hands of opponents in card games. This is easily accomplished at the UI layer (by not @@ -18,6 +18,7 @@ from that specific player. ```js const game = { // ... + // `playerID` could also be null or undefined for spectators. playerView: (G, ctx, playerID) => { return StripSecrets(G, playerID); }, diff --git a/src/core/player-view.test.ts b/src/core/player-view.test.ts index 52af774c0..959fd32d6 100644 --- a/src/core/player-view.test.ts +++ b/src/core/player-view.test.ts @@ -21,7 +21,7 @@ test('secret', () => { expect(newG).toEqual({}); }); -test('players', () => { +describe('players', () => { const G = { players: { '0': {}, @@ -29,13 +29,18 @@ test('players', () => { }, }; - { + test('playerID: "0"', () => { const newG = PlayerView.STRIP_SECRETS(G, {} as Ctx, '0'); expect(newG.players).toEqual({ '0': {} }); - } + }); - { + test('playerID: "1"', () => { const newG = PlayerView.STRIP_SECRETS(G, {} as Ctx, '1'); expect(newG.players).toEqual({ '1': {} }); - } + }); + + test('playerID: null', () => { + const newG = PlayerView.STRIP_SECRETS(G, {} as Ctx, null); + expect(newG.players).toEqual({}); + }); }); diff --git a/src/core/player-view.ts b/src/core/player-view.ts index 4fdf563e6..6d8f8d7f5 100644 --- a/src/core/player-view.ts +++ b/src/core/player-view.ts @@ -19,7 +19,7 @@ export const PlayerView = { * removes all the keys in `players`, except for the one * corresponding to the current playerID. */ - STRIP_SECRETS: (G: any, ctx: Ctx, playerID: PlayerID) => { + STRIP_SECRETS: (G: any, ctx: Ctx, playerID: PlayerID | null) => { const r = { ...G }; if (r.secret !== undefined) { @@ -27,9 +27,11 @@ export const PlayerView = { } if (r.players) { - r.players = { - [playerID]: r.players[playerID], - }; + r.players = playerID + ? { + [playerID]: r.players[playerID], + } + : {}; } return r; diff --git a/src/master/filter-player-view.ts b/src/master/filter-player-view.ts index 22cf5499c..ff3022067 100644 --- a/src/master/filter-player-view.ts +++ b/src/master/filter-player-view.ts @@ -5,7 +5,7 @@ import type { TransportData, IntermediateTransportData } from './master'; const applyPlayerView = ( game: Game, - playerID: string, + playerID: string | null, state: State ): State => ({ ...state, @@ -19,7 +19,10 @@ const applyPlayerView = ( /** Gets a function that filters the TransportData for a given player and game. */ export const getFilterPlayerView = (game: Game) => - (playerID: string, payload: IntermediateTransportData): TransportData => { + ( + playerID: string | null, + payload: IntermediateTransportData + ): TransportData => { switch (payload.type) { case 'patch': { const [matchID, stateID, prevState, state] = payload.args; @@ -69,7 +72,7 @@ export const getFilterPlayerView = * @param {String} playerID - The playerID that this log is * to be sent to. */ -export function redactLog(log: LogEntry[], playerID: PlayerID) { +export function redactLog(log: LogEntry[], playerID: PlayerID | null) { if (log === undefined) { return log; } diff --git a/src/types.ts b/src/types.ts index 87eb05e01..e9508be16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -311,7 +311,7 @@ export interface Game< }; endIf?: (G: G, ctx: CtxWithPlugins) => any; onEnd?: (G: G, ctx: CtxWithPlugins) => any; - playerView?: (G: G, ctx: CtxWithPlugins, playerID: PlayerID) => any; + playerView?: (G: G, ctx: CtxWithPlugins, playerID: PlayerID | null) => any; plugins?: Array>; ai?: { enumerate: (