Skip to content

Commit 4b1c135

Browse files
committed
move Random code into plugin
1 parent 88a386d commit 4b1c135

12 files changed

Lines changed: 134 additions & 131 deletions

File tree

src/ai/bot.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { makeMove, gameEvent } from '../core/action-creators';
10-
import { alea } from '../core/random.alea';
10+
import { alea } from '../plugins/random/random.alea';
1111

1212
/**
1313
* Base class that bots can extend.

src/core/context-enhancer.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Random } from './random';
21
import { Events } from './events';
32

43
/**
@@ -49,29 +48,25 @@ export class GameLoggerCtxAPI {
4948
* all separately.
5049
*/
5150
export class ContextEnhancer {
52-
constructor(ctx, game, player) {
53-
this.random = new Random(ctx);
51+
constructor(_, game, player) {
5452
this.events = new Events(game.flow, player);
5553
this.log = new GameLoggerCtxAPI();
5654
}
5755

5856
attachToContext(ctx) {
59-
let ctxWithAPI = this.random.attach(ctx);
60-
ctxWithAPI = this.events.attach(ctxWithAPI);
57+
let ctxWithAPI = this.events.attach(ctx);
6158
ctxWithAPI = this.log.attach(ctxWithAPI);
6259
return ctxWithAPI;
6360
}
6461

6562
static detachAllFromContext(ctx) {
66-
let ctxWithoutAPI = Random.detach(ctx);
67-
ctxWithoutAPI = Events.detach(ctxWithoutAPI);
63+
let ctxWithoutAPI = Events.detach(ctx);
6864
ctxWithoutAPI = GameLoggerCtxAPI.detach(ctxWithoutAPI);
6965
return ctxWithoutAPI;
7066
}
7167

7268
_update(state, updateEvents) {
7369
let newState = updateEvents ? this.events.update(state) : state;
74-
newState = this.random.update(newState);
7570
newState = this.log.update(newState);
7671
return newState;
7772
}

src/core/initialize.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { parse, stringify } from 'flatted';
2-
import { Random } from './random';
32
import { Game } from './game';
43
import { GameConfig } from '../types';
54
import * as plugins from '../plugins/main';
@@ -30,15 +29,7 @@ export function InitializeGame({
3029
numPlayers = 2;
3130
}
3231

33-
let seed = game.seed;
34-
if (seed === undefined) {
35-
seed = Random.seed();
36-
}
37-
38-
let ctx: Ctx = {
39-
...game.flow.ctx(numPlayers),
40-
_random: { seed },
41-
};
32+
let ctx: Ctx = game.flow.ctx(numPlayers);
4233

4334
let state: GameState = {
4435
// User managed state.

src/core/reducer.test.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,6 @@ describe('Random inside setup()', () => {
284284
setup: ctx => ({ n: ctx.random.D6() }),
285285
};
286286

287-
const game4 = {
288-
setup: ctx => ({ n: ctx.random.D6() }),
289-
};
290-
291287
test('setting seed', () => {
292288
const state1 = InitializeGame({ game: game1 });
293289
const state2 = InitializeGame({ game: game2 });
@@ -296,11 +292,6 @@ describe('Random inside setup()', () => {
296292
expect(state1.G.n).not.toBe(state2.G.n);
297293
expect(state2.G.n).toBe(state3.G.n);
298294
});
299-
300-
test('not setting seed sets a default', () => {
301-
const state = InitializeGame({ game: game4 });
302-
expect(state.ctx._random.seed).toBeDefined();
303-
});
304295
});
305296

306297
test('undo / redo', () => {

src/core/reducer.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,10 @@ export function CreateGameReducer({
199199
);
200200
let ctx = newState.ctx;
201201

202-
// Random API code was executed. If we are on the
203-
// client, wait for the master response instead.
204-
if (
205-
isClient &&
206-
ctx._random !== undefined &&
207-
ctx._random.prngstate !== undefined
208-
) {
202+
// Some plugin indicated that it is not suitable to be
203+
// materialized on the client (and must wait for the server
204+
// response instead).
205+
if (isClient && plugins.NoClient(newState, { game })) {
209206
return state;
210207
}
211208

src/master/master.test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ describe('update', () => {
123123
_stateID: 0,
124124
_undo: [],
125125
ctx: {
126-
_random: { seed: 0 },
127126
currentPlayer: '0',
128127
numPlayers: 2,
129128
phase: null,
@@ -136,7 +135,6 @@ describe('update', () => {
136135
_stateID: 1,
137136
_undo: [],
138137
ctx: {
139-
_random: undefined,
140138
currentPlayer: '1',
141139
numPlayers: 2,
142140
phase: null,

src/plugins/main.ts

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import PluginImmer from './plugin-immer';
10+
import PluginRandom from './plugin-random';
1011
import { GameState, State, GameConfig, Plugin, Ctx } from '../types';
1112

1213
interface PluginOpts {
@@ -17,7 +18,7 @@ interface PluginOpts {
1718
/**
1819
* List of plugins that are always added.
1920
*/
20-
const DEFAULT_PLUGINS = [PluginImmer];
21+
const DEFAULT_PLUGINS = [PluginImmer, PluginRandom];
2122

2223
/**
2324
* The API's created by various plugins are stored in the plugins
@@ -97,20 +98,22 @@ export const Enhance = (state: State, opts: PluginOpts): State => {
9798
const name = plugin.name;
9899
const pluginState = state.plugins[name];
99100

100-
const api = plugin.api({
101-
G: state.G,
102-
ctx: state.ctx,
103-
data: pluginState.data,
104-
game: opts.game,
105-
});
101+
if (pluginState) {
102+
const api = plugin.api({
103+
G: state.G,
104+
ctx: state.ctx,
105+
data: pluginState.data,
106+
game: opts.game,
107+
});
106108

107-
state = {
108-
...state,
109-
plugins: {
110-
...state.plugins,
111-
[name]: { ...pluginState, api },
112-
},
113-
};
109+
state = {
110+
...state,
111+
plugins: {
112+
...state.plugins,
113+
[name]: { ...pluginState, api },
114+
},
115+
};
116+
}
114117
});
115118
return state;
116119
};
@@ -123,22 +126,50 @@ export const Flush = (state: State, opts: PluginOpts): State => {
123126
.filter(plugin => plugin.flush !== undefined)
124127
.forEach(plugin => {
125128
const name = plugin.name;
126-
const { api, data } = state.plugins[name];
127-
const newData = plugin.flush({
128-
G: state.G,
129-
ctx: state.ctx,
130-
game: opts.game,
131-
api,
132-
data,
133-
});
129+
const pluginState = state.plugins[name];
134130

135-
state = {
136-
...state,
137-
plugins: {
138-
...state.plugins,
139-
[plugin.name]: { data: newData },
140-
},
141-
};
131+
if (pluginState) {
132+
const newData = plugin.flush({
133+
G: state.G,
134+
ctx: state.ctx,
135+
game: opts.game,
136+
api: pluginState.api,
137+
data: pluginState.data,
138+
});
139+
140+
state = {
141+
...state,
142+
plugins: {
143+
...state.plugins,
144+
[plugin.name]: { data: newData },
145+
},
146+
};
147+
}
142148
});
143149
return state;
144150
};
151+
152+
/**
153+
* Allows plugins to indicate if they should not be materialized on the client.
154+
* This will cause the client to discard the state update and wait for the
155+
* master instead.
156+
*/
157+
export const NoClient = (state: State, opts: PluginOpts): boolean => {
158+
return [...DEFAULT_PLUGINS, ...opts.game.plugins]
159+
.filter(plugin => plugin.noClient !== undefined)
160+
.map(plugin => {
161+
const name = plugin.name;
162+
const pluginState = state.plugins[name];
163+
164+
if (pluginState) {
165+
return plugin.noClient({
166+
G: state.G,
167+
ctx: state.ctx,
168+
game: opts.game,
169+
api: pluginState.api,
170+
data: pluginState.data,
171+
});
172+
}
173+
})
174+
.some(value => value === true);
175+
};

src/plugins/plugin-random.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2018 The boardgame.io Authors
3+
*
4+
* Use of this source code is governed by a MIT-style
5+
* license that can be found in the LICENSE file or at
6+
* https://opensource.org/licenses/MIT.
7+
*/
8+
9+
import { Random } from './random/random';
10+
11+
export default {
12+
name: 'random',
13+
14+
noClient: ({ api }) => {
15+
return api._obj.isUsed();
16+
},
17+
18+
flush: ({ api }) => {
19+
return api._obj.getState();
20+
},
21+
22+
api: ({ data }) => {
23+
const random = new Random(data);
24+
return random.api();
25+
},
26+
27+
setup: ({ game }) => {
28+
let seed = game.seed;
29+
if (seed === undefined) {
30+
seed = Random.seed();
31+
}
32+
return { seed };
33+
},
34+
};
Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,29 @@ export class Random {
2020
* constructor
2121
* @param {object} ctx - The ctx object to initialize from.
2222
*/
23-
constructor(ctx) {
23+
constructor(state) {
2424
// If we are on the client, the seed is not present.
2525
// Just use a temporary seed to execute the move without
2626
// crashing it. The move state itself is discarded,
2727
// so the actual value doesn't matter.
28-
this.state = ctx._random || { seed: '0' };
28+
this.state = state;
29+
this.used = false;
2930
}
3031

31-
/**
32-
* Updates ctx with the PRNG state.
33-
* @param {object} ctx - The ctx object to update.
34-
*/
35-
update(state) {
36-
const ctx = { ...state.ctx, _random: this.state };
37-
return { ...state, ctx };
32+
isUsed() {
33+
return this.used;
3834
}
3935

40-
/**
41-
* Attaches the Random API to ctx.
42-
* @param {object} ctx - The ctx object to attach to.
43-
*/
44-
attach(ctx) {
45-
return { ...ctx, random: this._api() };
36+
getState() {
37+
return this.state;
4638
}
4739

4840
/**
4941
* Generate a random number.
5042
*/
5143
_random() {
44+
this.used = true;
45+
5246
const R = this.state;
5347

5448
let fn;
@@ -69,7 +63,7 @@ export class Random {
6963
return number;
7064
}
7165

72-
_api() {
66+
api() {
7367
const random = this._random.bind(this);
7468

7569
const SpotValue = {
@@ -161,21 +155,12 @@ export class Random {
161155

162156
return shuffled;
163157
},
158+
159+
_obj: this,
164160
};
165161
}
166162
}
167163

168-
/**
169-
* Removes the attached Random api from ctx.
170-
*
171-
* @param {object} ctx - The ctx object with the Random API attached.
172-
* @returns {object} A plain ctx object without the Random API.
173-
*/
174-
Random.detach = function(ctx) {
175-
const { random, ...rest } = ctx; // eslint-disable-line no-unused-vars
176-
return rest;
177-
};
178-
179164
/**
180165
* Generates a new seed from the current date / time.
181166
*/

0 commit comments

Comments
 (0)