Skip to content

Commit 3a41cf7

Browse files
authored
feat(plugins): Make player plugin a factory function (#604)
* feat(plugins): Make player plugin a factory function This moves the `playerSetup` function out of the game configuration object. Instead `PluginPlayer` should be called with the setup function as its argument and it will return the plugin object boardgame.io expects. BREAKING CHANGE: The `PluginPlayer` is no longer a plain object that looks for game.playerSetup during its setup. Instead, you have to pass your `playerSetup` method directly to the plugin. Closes #603. * refactor(plugins): Use an options object for player plugin
1 parent 21c174e commit 3a41cf7

4 files changed

Lines changed: 50 additions & 22 deletions

File tree

docs/documentation/plugins.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,38 @@ const game = {
7070
!> Plugins are applied one after the other in the order
7171
that they are specified (from left to right).
7272

73+
##### Configuring Plugins
74+
75+
Some plugins may need a user to provide some configuration. The recommended way to do that is to design the plugin as a factory function that takes configuration as its arguments and returns a plugin object.
76+
77+
```js
78+
import { ConfigurablePlugin } from './plugins';
79+
80+
const game = {
81+
name: 'my-game',
82+
plugins: [
83+
ConfigurablePlugin(options),
84+
],
85+
}
86+
```
87+
88+
?> See `PluginPlayer` below for an example of this in practice.
89+
7390
#### Available Plugins
7491

7592
**PluginPlayer**
7693

7794
```js
7895
import { PluginPlayer } from 'boardgame.io/plugins';
7996

97+
// define a function to initialize each player’s state
98+
const playerSetup = (playerID) => ({ ... });
99+
80100
const game = {
81-
playerSetup: (playerID) => ({ ... }),
82-
plugins: [PluginPlayer],
101+
plugins: [
102+
// pass your function to the player plugin
103+
PluginPlayer({ setup: playerSetup }),
104+
],
83105
};
84106
```
85107

@@ -97,7 +119,7 @@ players: {
97119
}
98120
```
99121

100-
The initial values of these states are determined by the `playerSetup` function, which creates the state for a particular `playerID`.
122+
The initial values of these states are determined by the `setup` function in its options object, which creates the state for a particular `playerID`.
101123

102124
The record associated with the current player can be accessed
103125
via `ctx.player.get()`. If this is a 2 player game,

src/plugins/plugin-player.test.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,23 @@ import { Client } from '../client/client';
1111

1212
describe('default values', () => {
1313
test('playerState is not passed', () => {
14+
const plugin = PluginPlayer();
1415
const game = {
15-
plugins: [PluginPlayer],
16+
plugins: [plugin],
1617
};
1718
const client = Client({ game });
18-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
19+
expect(client.getState().plugins[plugin.name].data).toEqual({
1920
players: { '0': {}, '1': {} },
2021
});
2122
});
2223

2324
test('playerState is passed', () => {
25+
const plugin = PluginPlayer({ setup: () => ({ A: 1 }) });
2426
const game = {
25-
playerSetup: () => ({ A: 1 }),
26-
plugins: [PluginPlayer],
27+
plugins: [plugin],
2728
};
2829
const client = Client({ game });
29-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
30+
expect(client.getState().plugins[plugin.name].data).toEqual({
3031
players: { '0': { A: 1 }, '1': { A: 1 } },
3132
});
3233
});
@@ -49,15 +50,15 @@ describe('2 player game', () => {
4950
},
5051
},
5152

52-
plugins: [PluginPlayer],
53+
plugins: [PluginPlayer()],
5354
};
5455

5556
client = Client({ game });
5657
});
5758

5859
test('player 0 turn', () => {
5960
client.moves.A();
60-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
61+
expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
6162
players: {
6263
'0': { field: 'A1' },
6364
'1': { field: 'A2' },
@@ -68,7 +69,7 @@ describe('2 player game', () => {
6869
test('player 1 turn', () => {
6970
client.events.endTurn();
7071
client.moves.A();
71-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
72+
expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
7273
players: {
7374
'0': { field: 'A2' },
7475
'1': { field: 'A1' },
@@ -96,15 +97,15 @@ describe('3 player game', () => {
9697
},
9798
},
9899

99-
plugins: [PluginPlayer],
100+
plugins: [PluginPlayer()],
100101
};
101102

102103
client = Client({ game, numPlayers: 3 });
103104
});
104105

105106
test('G.opponent is not created', () => {
106107
client.moves.A();
107-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
108+
expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
108109
players: {
109110
'0': { field: 'A' },
110111
'1': {},
@@ -119,8 +120,7 @@ describe('game with phases', () => {
119120

120121
beforeAll(() => {
121122
const game = {
122-
playerSetup: id => ({ id }),
123-
plugins: [PluginPlayer],
123+
plugins: [PluginPlayer({ setup: id => ({ id }) })],
124124
phases: {
125125
phase: {},
126126
},
@@ -130,7 +130,7 @@ describe('game with phases', () => {
130130
});
131131

132132
test('includes playerSetup state', () => {
133-
expect(client.getState().plugins[PluginPlayer.name].data).toEqual({
133+
expect(client.getState().plugins[PluginPlayer().name].data).toEqual({
134134
players: {
135135
0: {
136136
id: '0',

src/plugins/plugin-player.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ export interface PlayerAPI {
2222
};
2323
}
2424

25+
interface PluginPlayerOpts {
26+
setup?: (playerID: string) => any;
27+
}
28+
2529
/**
2630
* Plugin that maintains state for each player in G.players.
2731
* During a turn, G.player will contain the object for the current player.
2832
* In two player games, G.opponent will contain the object for the other player.
2933
*
3034
* @param {function} initPlayerState - Function of type (playerID) => playerState.
3135
*/
32-
const PlayerPlugin: Plugin<PlayerAPI, PlayerData> = {
36+
const PlayerPlugin = ({ setup }: PluginPlayerOpts = {}): Plugin<
37+
PlayerAPI,
38+
PlayerData
39+
> => ({
3340
name: 'player',
3441

3542
flush: ({ api }) => {
@@ -63,17 +70,17 @@ const PlayerPlugin: Plugin<PlayerAPI, PlayerData> = {
6370
return result;
6471
},
6572

66-
setup: ({ ctx, game }) => {
73+
setup: ({ ctx }) => {
6774
let players: Record<PlayerID, any> = {};
6875
for (let i = 0; i < ctx.numPlayers; i++) {
6976
let playerState: any = {};
70-
if (game.playerSetup !== undefined) {
71-
playerState = game.playerSetup(i + '');
77+
if (setup !== undefined) {
78+
playerState = setup(i + '');
7279
}
7380
players[i + ''] = playerState;
7481
}
7582
return { players };
7683
},
77-
};
84+
});
7885

7986
export default PlayerPlugin;

src/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ export interface GameConfig {
211211
action: ActionPayload.MakeMove
212212
) => State | typeof INVALID_MOVE;
213213
flow?: ReturnType<typeof Flow>;
214-
[key: string]: any;
215214
}
216215

217216
type Undo = { G: object; ctx: Ctx; moveType?: string };

0 commit comments

Comments
 (0)