Skip to content

Commit

Permalink
move Random code into plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolodavis committed Mar 17, 2020
1 parent 88a386d commit 4b1c135
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 131 deletions.
2 changes: 1 addition & 1 deletion src/ai/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

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

/**
* Base class that bots can extend.
Expand Down
11 changes: 3 additions & 8 deletions src/core/context-enhancer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Random } from './random';
import { Events } from './events';

/**
Expand Down Expand Up @@ -49,29 +48,25 @@ export class GameLoggerCtxAPI {
* all separately.
*/
export class ContextEnhancer {
constructor(ctx, game, player) {
this.random = new Random(ctx);
constructor(_, game, player) {
this.events = new Events(game.flow, player);
this.log = new GameLoggerCtxAPI();
}

attachToContext(ctx) {
let ctxWithAPI = this.random.attach(ctx);
ctxWithAPI = this.events.attach(ctxWithAPI);
let ctxWithAPI = this.events.attach(ctx);
ctxWithAPI = this.log.attach(ctxWithAPI);
return ctxWithAPI;
}

static detachAllFromContext(ctx) {
let ctxWithoutAPI = Random.detach(ctx);
ctxWithoutAPI = Events.detach(ctxWithoutAPI);
let ctxWithoutAPI = Events.detach(ctx);
ctxWithoutAPI = GameLoggerCtxAPI.detach(ctxWithoutAPI);
return ctxWithoutAPI;
}

_update(state, updateEvents) {
let newState = updateEvents ? this.events.update(state) : state;
newState = this.random.update(newState);
newState = this.log.update(newState);
return newState;
}
Expand Down
11 changes: 1 addition & 10 deletions src/core/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { parse, stringify } from 'flatted';
import { Random } from './random';
import { Game } from './game';
import { GameConfig } from '../types';
import * as plugins from '../plugins/main';
Expand Down Expand Up @@ -30,15 +29,7 @@ export function InitializeGame({
numPlayers = 2;
}

let seed = game.seed;
if (seed === undefined) {
seed = Random.seed();
}

let ctx: Ctx = {
...game.flow.ctx(numPlayers),
_random: { seed },
};
let ctx: Ctx = game.flow.ctx(numPlayers);

let state: GameState = {
// User managed state.
Expand Down
9 changes: 0 additions & 9 deletions src/core/reducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,6 @@ describe('Random inside setup()', () => {
setup: ctx => ({ n: ctx.random.D6() }),
};

const game4 = {
setup: ctx => ({ n: ctx.random.D6() }),
};

test('setting seed', () => {
const state1 = InitializeGame({ game: game1 });
const state2 = InitializeGame({ game: game2 });
Expand All @@ -296,11 +292,6 @@ describe('Random inside setup()', () => {
expect(state1.G.n).not.toBe(state2.G.n);
expect(state2.G.n).toBe(state3.G.n);
});

test('not setting seed sets a default', () => {
const state = InitializeGame({ game: game4 });
expect(state.ctx._random.seed).toBeDefined();
});
});

test('undo / redo', () => {
Expand Down
11 changes: 4 additions & 7 deletions src/core/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,10 @@ export function CreateGameReducer({
);
let ctx = newState.ctx;

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

Expand Down
2 changes: 0 additions & 2 deletions src/master/master.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ describe('update', () => {
_stateID: 0,
_undo: [],
ctx: {
_random: { seed: 0 },
currentPlayer: '0',
numPlayers: 2,
phase: null,
Expand All @@ -136,7 +135,6 @@ describe('update', () => {
_stateID: 1,
_undo: [],
ctx: {
_random: undefined,
currentPlayer: '1',
numPlayers: 2,
phase: null,
Expand Down
89 changes: 60 additions & 29 deletions src/plugins/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

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

interface PluginOpts {
Expand All @@ -17,7 +18,7 @@ interface PluginOpts {
/**
* List of plugins that are always added.
*/
const DEFAULT_PLUGINS = [PluginImmer];
const DEFAULT_PLUGINS = [PluginImmer, PluginRandom];

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

const api = plugin.api({
G: state.G,
ctx: state.ctx,
data: pluginState.data,
game: opts.game,
});
if (pluginState) {
const api = plugin.api({
G: state.G,
ctx: state.ctx,
data: pluginState.data,
game: opts.game,
});

state = {
...state,
plugins: {
...state.plugins,
[name]: { ...pluginState, api },
},
};
state = {
...state,
plugins: {
...state.plugins,
[name]: { ...pluginState, api },
},
};
}
});
return state;
};
Expand All @@ -123,22 +126,50 @@ export const Flush = (state: State, opts: PluginOpts): State => {
.filter(plugin => plugin.flush !== undefined)
.forEach(plugin => {
const name = plugin.name;
const { api, data } = state.plugins[name];
const newData = plugin.flush({
G: state.G,
ctx: state.ctx,
game: opts.game,
api,
data,
});
const pluginState = state.plugins[name];

state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data: newData },
},
};
if (pluginState) {
const newData = plugin.flush({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});

state = {
...state,
plugins: {
...state.plugins,
[plugin.name]: { data: newData },
},
};
}
});
return state;
};

/**
* Allows plugins to indicate if they should not be materialized on the client.
* This will cause the client to discard the state update and wait for the
* master instead.
*/
export const NoClient = (state: State, opts: PluginOpts): boolean => {
return [...DEFAULT_PLUGINS, ...opts.game.plugins]
.filter(plugin => plugin.noClient !== undefined)
.map(plugin => {
const name = plugin.name;
const pluginState = state.plugins[name];

if (pluginState) {
return plugin.noClient({
G: state.G,
ctx: state.ctx,
game: opts.game,
api: pluginState.api,
data: pluginState.data,
});
}
})
.some(value => value === true);
};
34 changes: 34 additions & 0 deletions src/plugins/plugin-random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2018 The boardgame.io Authors
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

import { Random } from './random/random';

export default {
name: 'random',

noClient: ({ api }) => {
return api._obj.isUsed();
},

flush: ({ api }) => {
return api._obj.getState();
},

api: ({ data }) => {
const random = new Random(data);
return random.api();
},

setup: ({ game }) => {
let seed = game.seed;
if (seed === undefined) {
seed = Random.seed();
}
return { seed };
},
};
File renamed without changes.
39 changes: 12 additions & 27 deletions src/core/random.js → src/plugins/random/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,29 @@ export class Random {
* constructor
* @param {object} ctx - The ctx object to initialize from.
*/
constructor(ctx) {
constructor(state) {
// 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 = ctx._random || { seed: '0' };
this.state = state;
this.used = false;
}

/**
* Updates ctx with the PRNG state.
* @param {object} ctx - The ctx object to update.
*/
update(state) {
const ctx = { ...state.ctx, _random: this.state };
return { ...state, ctx };
isUsed() {
return this.used;
}

/**
* Attaches the Random API to ctx.
* @param {object} ctx - The ctx object to attach to.
*/
attach(ctx) {
return { ...ctx, random: this._api() };
getState() {
return this.state;
}

/**
* Generate a random number.
*/
_random() {
this.used = true;

const R = this.state;

let fn;
Expand All @@ -69,7 +63,7 @@ export class Random {
return number;
}

_api() {
api() {
const random = this._random.bind(this);

const SpotValue = {
Expand Down Expand Up @@ -161,21 +155,12 @@ export class Random {

return shuffled;
},

_obj: this,
};
}
}

/**
* Removes the attached Random api from ctx.
*
* @param {object} ctx - The ctx object with the Random API attached.
* @returns {object} A plain ctx object without the Random API.
*/
Random.detach = function(ctx) {
const { random, ...rest } = ctx; // eslint-disable-line no-unused-vars
return rest;
};

/**
* Generates a new seed from the current date / time.
*/
Expand Down

0 comments on commit 4b1c135

Please sign in to comment.