Skip to content

Commit

Permalink
fix(random): handle redacted state on multiplayer clients (#885)
Browse files Browse the repository at this point in the history
Fixes #870

Prior to the current plugin system, the randomness API set a default 
seed '0' if it didn’t receive one from game state (i.e. when it was run 
on a multiplayer client and the randomness state was redacted). When the 
new plugin system was built, this fallback value was removed, but that 
didn’t cause errors because the PRNG seed and state were no longer 
redacted. After #857 (released in v0.42.2) reintroduced redacted state 
for plugins, the plugin broke for multiplayer clients that no longer had 
access to plugin state.

This commit reinstates the default seed (the result of which is 
discarded in any case) and adds a test for running the plugin with 
redacted state to catch a similar error in any future refactoring.

See 4b1c135 for details of the 
randomness API before the current implementation.
  • Loading branch information
delucis committed Jan 5, 2021
1 parent 8f85fb0 commit e515573
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 2 deletions.
19 changes: 19 additions & 0 deletions src/plugins/random/random.test.ts
Expand Up @@ -11,6 +11,7 @@ import { makeMove } from '../../core/action-creators';
import { CreateGameReducer } from '../../core/reducer';
import { InitializeGame } from '../../core/initialize';
import { Client } from '../../client/client';
import { PlayerView } from '../main';

function Init(seed) {
return new Random({ seed });
Expand Down Expand Up @@ -123,6 +124,24 @@ test('Random API is not executed optimisitically', () => {
}
});

test('Random API works when its state is redacted by playerView', () => {
const game = {
seed: 0,
moves: {
rollDie: (G, ctx) => ({ ...G, die: ctx.random.D6() }),
},
};

const opts = { game, isClient: true };
const reducer = CreateGameReducer(opts);
let state = InitializeGame({ game });
state.plugins = PlayerView(state, { ...opts, playerID: '0' });
expect(state.plugins.random.data).not.toBeDefined();
expect(state.G.die).not.toBeDefined();
state = reducer(state, makeMove('rollDie'));
expect(state.G.die).not.toBeDefined();
});

test('turn.onBegin has ctx APIs at the beginning of the game', () => {
let random = null;
let events = null;
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/random/random.ts
Expand Up @@ -60,12 +60,12 @@ export class Random {
* constructor
* @param {object} ctx - The ctx object to initialize from.
*/
constructor(state: RandomState) {
constructor(state?: RandomState) {
// If we are on the client, the seed is not present.
// Just use a temporary seed to execute the move without
// crashing it. The move state itself is discarded,
// so the actual value doesn't matter.
this.state = state;
this.state = state || { seed: '0' };
this.used = false;
}

Expand Down

0 comments on commit e515573

Please sign in to comment.