Skip to content

Commit c7dad76

Browse files
committed
add the ability for plugins to define their own actions
1 parent 6a1eadd commit c7dad76

8 files changed

Lines changed: 124 additions & 23 deletions

File tree

src/client/client.js

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,12 @@ function createDispatchers(
5252
}, {});
5353
}
5454

55-
/**
56-
* createEventDispatchers
57-
*
58-
* Creates a set of dispatchers to dispatch game flow events.
59-
* @param {Array} eventNames - A list of event names.
60-
* @param {object} store - The Redux store to create dispatchers for.
61-
* @param {string} playerID - The ID of the player dispatching these events.
62-
* @param {string} credentials - A key indicating that the player is authorized to play.
63-
*/
64-
export const createEventDispatchers = createDispatchers.bind(null, 'gameEvent');
65-
66-
/**
67-
* createMoveDispatchers
68-
*
69-
* Creates a set of dispatchers to make moves.
70-
* @param {Array} moveNames - A list of move names.
71-
* @param {object} store - The Redux store to create dispatchers for.
72-
* @param {string} playerID - The ID of the player dispatching these events.
73-
* @param {string} credentials - A key indicating that the player is authorized to play.
74-
*/
55+
// Creates a set of dispatchers to make moves.
7556
export const createMoveDispatchers = createDispatchers.bind(null, 'makeMove');
57+
// Creates a set of dispatchers to dispatch game flow events.
58+
export const createEventDispatchers = createDispatchers.bind(null, 'gameEvent');
59+
// Creates a set of dispatchers to dispatch actions to plugins.
60+
export const createPluginDispatchers = createDispatchers.bind(null, 'plugin');
7661

7762
/**
7863
* Implementation of Client (see below).
@@ -395,6 +380,14 @@ class _ClientImpl {
395380
this.credentials,
396381
this.multiplayer
397382
);
383+
384+
this.plugins = createPluginDispatchers(
385+
this.game.pluginNames,
386+
this.store,
387+
this.playerID,
388+
this.credentials,
389+
this.multiplayer
390+
);
398391
}
399392

400393
updatePlayerID(playerID) {

src/core/action-creators.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,16 @@ export const undo = () => ({
107107
export const redo = () => ({
108108
type: Actions.REDO as typeof Actions.REDO,
109109
});
110+
111+
/**
112+
* Allows plugins to define their own actions and intercept them.
113+
*/
114+
export const plugin = (
115+
type: string,
116+
args?: any,
117+
playerID?: string | null,
118+
credentials?: string
119+
) => ({
120+
type: Actions.PLUGIN as typeof Actions.PLUGIN,
121+
payload: { type, args, playerID, credentials },
122+
});

src/core/action-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export const RESET = 'RESET';
1313
export const SYNC = 'SYNC';
1414
export const UNDO = 'UNDO';
1515
export const UPDATE = 'UPDATE';
16+
export const PLUGIN = 'PLUGIN';

src/core/game.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as logging from './logger';
1414
type ProcessedGameConfig = GameConfig & {
1515
flow: object;
1616
moveNames: string[];
17+
pluginNames: string[];
1718
processMove: Function;
1819
};
1920

@@ -76,6 +77,8 @@ export function Game(
7677

7778
moveNames: flow.moveNames as string[],
7879

80+
pluginNames: game.plugins.map(p => p.name) as string[],
81+
7982
processMove: (state: State, action: ActionPayload.MakeMove) => {
8083
let moveFn = flow.getMove(state.ctx, action.type, action.playerID);
8184

src/core/reducer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ export function CreateGameReducer({
263263
};
264264
}
265265

266+
case Actions.PLUGIN: {
267+
return plugins.ProcessAction(state, action, { game });
268+
}
269+
266270
default: {
267271
return state;
268272
}

src/plugins/main.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,48 @@ describe('default values', () => {
133133
expect(client.getState().plugins.test.data).toEqual(pluginData);
134134
});
135135
});
136+
137+
describe('actions', () => {
138+
let client;
139+
140+
beforeAll(() => {
141+
const game = {
142+
plugins: [
143+
{
144+
name: 'test',
145+
146+
setup: () => ({
147+
initial: true,
148+
}),
149+
150+
action: (_, payload) => {
151+
return payload.args[0];
152+
},
153+
},
154+
{
155+
name: 'nosetup',
156+
action: () => ({ action: true }),
157+
},
158+
],
159+
};
160+
161+
client = Client({ game });
162+
});
163+
164+
test('setup', () => {
165+
expect(client.getState().plugins.test.data).toEqual({
166+
initial: true,
167+
});
168+
expect(client.getState().plugins.nosetup).toBeUndefined();
169+
});
170+
171+
test('take action', () => {
172+
const payload = { payload: true };
173+
174+
client.plugins.test(payload);
175+
client.plugins.nosetup(payload);
176+
177+
expect(client.getState().plugins.test.data).toEqual(payload);
178+
expect(client.getState().plugins.nosetup.data).toEqual({ action: true });
179+
});
180+
});

src/plugins/main.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
import PluginImmer from './plugin-immer';
1010
import PluginRandom from './plugin-random';
1111
import PluginEvents from './plugin-events';
12-
import { PartialGameState, State, GameConfig, Plugin, Ctx } from '../types';
12+
import {
13+
PartialGameState,
14+
State,
15+
GameConfig,
16+
Plugin,
17+
Ctx,
18+
ActionShape,
19+
} from '../types';
1320

1421
interface PluginOpts {
1522
game: GameConfig;
@@ -21,6 +28,33 @@ interface PluginOpts {
2128
*/
2229
const DEFAULT_PLUGINS = [PluginImmer, PluginRandom, PluginEvents];
2330

31+
/**
32+
* Allow plugins to intercept actions and process them.
33+
*/
34+
export const ProcessAction = (
35+
state: State,
36+
action: ActionShape.Plugin,
37+
opts: PluginOpts
38+
): State => {
39+
opts.game.plugins
40+
.filter(plugin => plugin.action !== undefined)
41+
.filter(plugin => plugin.name === action.payload.type)
42+
.forEach(plugin => {
43+
const name = plugin.name;
44+
const pluginState = state.plugins[name] || { data: {} };
45+
const data = plugin.action(pluginState.data, action.payload);
46+
47+
state = {
48+
...state,
49+
plugins: {
50+
...state.plugins,
51+
[name]: { ...pluginState, data },
52+
},
53+
};
54+
});
55+
return state;
56+
};
57+
2458
/**
2559
* The API's created by various plugins are stored in the plugins
2660
* section of the state object:

src/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export interface LogEntry {
6060

6161
export interface Plugin {
6262
name: string;
63+
setup?: Function;
64+
action?: Function;
65+
api?: Function;
66+
flush?: Function;
6367
}
6468

6569
export interface LongFormMove {
@@ -173,6 +177,7 @@ export namespace Server {
173177
export namespace CredentialedActionShape {
174178
export type MakeMove = ReturnType<typeof ActionCreators.makeMove>;
175179
export type GameEvent = ReturnType<typeof ActionCreators.gameEvent>;
180+
export type Plugin = ReturnType<typeof ActionCreators.plugin>;
176181
export type AutomaticGameEvent = ReturnType<
177182
typeof ActionCreators.automaticGameEvent
178183
>;
@@ -184,7 +189,8 @@ export namespace CredentialedActionShape {
184189
| ActionShape.Update
185190
| ActionShape.Reset
186191
| ActionShape.Undo
187-
| ActionShape.Redo;
192+
| ActionShape.Redo
193+
| ActionShape.Plugin;
188194
}
189195

190196
export namespace ActionShape {
@@ -194,6 +200,7 @@ export namespace ActionShape {
194200
>;
195201
export type MakeMove = StripCredentials<CredentialedActionShape.MakeMove>;
196202
export type GameEvent = StripCredentials<CredentialedActionShape.GameEvent>;
203+
export type Plugin = StripCredentials<CredentialedActionShape.Plugin>;
197204
export type AutomaticGameEvent = StripCredentials<
198205
CredentialedActionShape.AutomaticGameEvent
199206
>;
@@ -210,7 +217,8 @@ export namespace ActionShape {
210217
| Update
211218
| Reset
212219
| Undo
213-
| Redo;
220+
| Redo
221+
| Plugin;
214222
}
215223

216224
export namespace ActionPayload {

0 commit comments

Comments
 (0)