Skip to content

Commit

Permalink
Merge branch 'master' into delucis/refactor/more-types
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolodavis committed Mar 31, 2020
2 parents 2bf3442 + 1877268 commit c01d196
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 49 deletions.
14 changes: 10 additions & 4 deletions docs/documentation/plugins.md
Expand Up @@ -16,30 +16,36 @@ A plugin is an object that contains the following fields.
// Initialize the plugin's data.
// This is stored in a special area of the state object
// and not exposed to the move functions.
setup: ({ ctx }) => object,
setup: ({ G, ctx, game }) => data object,

// Create an object that becomes available in `ctx`
// under `ctx['plugin-name']`.
// This is called at the beginning of a move or event.
// This object will be held in memory until flush (below)
// is called.
api: ({ G, ctx, data }) => object,
api: ({ G, ctx, game, data, playerID }) => api object,

// Return an updated version of data that is persisted
// in the game's state object.
flush: ({ G, ctx, data, api }) => object,
flush: ({ G, ctx, game, data, api }) => data object,

// Function that accepts a move / trigger function
// and returns another function that wraps it. This
// wrapper can modify G before passing it down to
// the wrapped function. It is a good practice to
// undo the change at the end of the call.
fnWrap: (fn, game) => (G, ctx, ...args) => {
fnWrap: (fn) => (G, ctx, ...args) => {
G = preprocess(G);
G = fn(G, ctx, ...args);
G = postprocess(G);
return G;
},

// Function that allows the plugin to indicate that it
// should not be run on the client. If it returns true,
// the client will discard the state update and wait
// for the master instead.
noClient: ({ G, ctx, game, data, api }) => boolean,
}
```

Expand Down
4 changes: 2 additions & 2 deletions src/core/game.ts
Expand Up @@ -96,11 +96,11 @@ export function Game(
...plugins.EnhanceCtx(state),
playerID: action.playerID,
};
let args = [state.G, ctxWithAPI];
let args = [];
if (action.args !== undefined) {
args = args.concat(action.args);
}
return fn(...args);
return fn(state.G, ctxWithAPI, ...args);
}

logging.error(`invalid move object: ${action.type}`);
Expand Down
2 changes: 1 addition & 1 deletion src/core/initialize.ts
Expand Up @@ -42,7 +42,7 @@ export function InitializeGame({

// Run plugins over initial state.
state = plugins.Setup(state, { game });
state = plugins.Enhance(state as State, { game });
state = plugins.Enhance(state as State, { game, playerID: undefined });

const enhancedCtx = plugins.EnhanceCtx(state);
state.G = game.setup(enhancedCtx, setupData);
Expand Down
7 changes: 6 additions & 1 deletion src/core/reducer.ts
Expand Up @@ -103,7 +103,11 @@ export function CreateGameReducer({
}

// Execute plugins.
state = plugins.Enhance(state, { game, isClient: false });
state = plugins.Enhance(state, {
game,
isClient: false,
playerID: action.payload.playerID,
});

// Process event.
let newState = game.flow.processEvent(state, action);
Expand Down Expand Up @@ -153,6 +157,7 @@ export function CreateGameReducer({
state = plugins.Enhance(state, {
game,
isClient,
playerID: action.payload.playerID,
});

// Process the move.
Expand Down
42 changes: 35 additions & 7 deletions src/plugins/events/events.js → src/plugins/events/events.ts
Expand Up @@ -6,13 +6,41 @@
* https://opensource.org/licenses/MIT.
*/

import { State, Ctx, PlayerID, GameConfig } from '../../types';
import { automaticGameEvent } from '../../core/action-creators';

export interface EventsAPI {
endGame?(...args: any[]): void;
endPhase?(...args: any[]): void;
endStage?(...args: any[]): void;
endTurn?(...args: any[]): void;
pass?(...args: any[]): void;
setActivePlayers?(...args: any[]): void;
setPhase?(...args: any[]): void;
setStage?(...args: any[]): void;
}

export interface PrivateEventsAPI {
_obj: {
isUsed(): boolean;
update(state: State): State;
};
}

/**
* Events
*/
export class Events {
constructor(flow, playerID) {
flow: GameConfig['flow'];
playerID: PlayerID | undefined;
dispatch: Array<{
key: string;
args: any[];
phase: string;
turn: number;
}>;

constructor(flow: GameConfig['flow'], playerID?: PlayerID) {
this.flow = flow;
this.playerID = playerID;
this.dispatch = [];
Expand All @@ -22,18 +50,18 @@ export class Events {
* Attaches the Events API to ctx.
* @param {object} ctx - The ctx object to attach to.
*/
api(ctx) {
const events = {};
api(ctx: Ctx) {
const events: EventsAPI & PrivateEventsAPI = {
_obj: this,
};
const { phase, turn } = ctx;

for (const key of this.flow.eventNames) {
events[key] = (...args) => {
events[key] = (...args: any[]) => {
this.dispatch.push({ key, args, phase, turn });
};
}

events._obj = this;

return events;
}

Expand All @@ -45,7 +73,7 @@ export class Events {
* Updates ctx with the triggered events.
* @param {object} state - The state object { G, ctx }.
*/
update(state) {
update(state: State) {
for (let i = 0; i < this.dispatch.length; i++) {
const item = this.dispatch[i];

Expand Down
16 changes: 11 additions & 5 deletions src/plugins/main.ts
Expand Up @@ -10,12 +10,14 @@ import PluginImmer from './plugin-immer';
import PluginRandom from './plugin-random';
import PluginEvents from './plugin-events';
import {
AnyFn,
PartialGameState,
State,
GameConfig,
Plugin,
Ctx,
ActionShape,
PlayerID,
} from '../types';

interface PluginOpts {
Expand Down Expand Up @@ -88,8 +90,8 @@ export const EnhanceCtx = (state: PartialGameState): Ctx => {
* @param {function} fn - The move function or trigger to apply the plugins to.
* @param {object} plugins - The list of plugins.
*/
export const FnWrap = (fn: (...args: any[]) => any, plugins: Plugin[]) => {
const reducer = (acc, { fnWrap }) => fnWrap(acc, plugins);
export const FnWrap = (fn: AnyFn, plugins: Plugin[]) => {
const reducer = (acc: AnyFn, { fnWrap }: Plugin) => fnWrap(acc);
return [...DEFAULT_PLUGINS, ...plugins]
.filter(plugin => plugin.fnWrap !== undefined)
.reduce(reducer, fn);
Expand Down Expand Up @@ -129,7 +131,10 @@ export const Setup = (
* the `plugins` section of the state (which is subsequently
* merged into ctx).
*/
export const Enhance = (state: State, opts: PluginOpts): State => {
export const Enhance = (
state: State,
opts: PluginOpts & { playerID: PlayerID }
): State => {
[...DEFAULT_PLUGINS, ...opts.game.plugins]
.filter(plugin => plugin.api !== undefined)
.forEach(plugin => {
Expand All @@ -141,6 +146,7 @@ export const Enhance = (state: State, opts: PluginOpts): State => {
ctx: state.ctx,
data: pluginState.data,
game: opts.game,
playerID: opts.playerID,
});

state = {
Expand Down Expand Up @@ -178,8 +184,8 @@ export const Flush = (state: State, opts: PluginOpts): State => {
[plugin.name]: { data: newData },
},
};
} else if (plugin.flushRaw) {
state = plugin.flushRaw({
} else if (plugin.dangerouslyFlushRawState) {
state = plugin.dangerouslyFlushRawState({
state,
game: opts.game,
api: pluginState.api,
Expand Down
20 changes: 7 additions & 13 deletions src/plugins/plugin-events.ts
Expand Up @@ -6,31 +6,25 @@
* https://opensource.org/licenses/MIT.
*/

import { Events } from './events/events';
import { Plugin } from '../types';
import { Events, EventsAPI, PrivateEventsAPI } from './events/events';

export interface EventsAPI {
endGame?(...args: any[]): void;
endPhase?(...args: any[]): void;
endStage?(...args: any[]): void;
endTurn?(...args: any[]): void;
pass?(...args: any[]): void;
setActivePlayers?(...args: any[]): void;
setPhase?(...args: any[]): void;
setStage?(...args: any[]): void;
}
export { EventsAPI };

export default {
const EventsPlugin: Plugin<EventsAPI & PrivateEventsAPI> = {
name: 'events',

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

flushRaw: ({ state, api }) => {
dangerouslyFlushRawState: ({ state, api }) => {
return api._obj.update(state);
},

api: ({ game, playerID, ctx }) => {
return new Events(game.flow, playerID).api(ctx);
},
};

export default EventsPlugin;
5 changes: 4 additions & 1 deletion src/plugins/plugin-immer.js → src/plugins/plugin-immer.ts
Expand Up @@ -7,12 +7,15 @@
*/

import produce from 'immer';
import { Plugin } from '../types';

/**
* Plugin that allows using Immer to make immutable changes
* to G by just mutating it.
*/
export default {
const ImmerPlugin: Plugin = {
name: 'plugin-immer',
fnWrap: move => produce(move),
};

export default ImmerPlugin;
18 changes: 12 additions & 6 deletions src/plugins/plugin-player.ts
Expand Up @@ -6,10 +6,14 @@
* https://opensource.org/licenses/MIT.
*/

import { Plugin, PlayerID } from '../types';

interface PlayerData {
players: Record<PlayerID, any>;
}

export interface PlayerAPI {
state: {
[playerId: string]: object;
};
state: Record<PlayerID, any>;
get(): any;
set(value: any): any;
opponent?: {
Expand All @@ -25,7 +29,7 @@ export interface PlayerAPI {
*
* @param {function} initPlayerState - Function of type (playerID) => playerState.
*/
export default {
const PlayerPlugin: Plugin<PlayerAPI, PlayerData> = {
name: 'player',

flush: ({ api }) => {
Expand Down Expand Up @@ -60,9 +64,9 @@ export default {
},

setup: ({ ctx, game }) => {
let players = {};
let players: Record<PlayerID, any> = {};
for (let i = 0; i < ctx.numPlayers; i++) {
let playerState = {};
let playerState: any = {};
if (game.playerSetup !== undefined) {
playerState = game.playerSetup(i + '');
}
Expand All @@ -71,3 +75,5 @@ export default {
return { players };
},
};

export default PlayerPlugin;
12 changes: 11 additions & 1 deletion src/plugins/plugin-random.ts
Expand Up @@ -6,6 +6,7 @@
* https://opensource.org/licenses/MIT.
*/

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

export interface RandomAPI {
Expand All @@ -25,7 +26,14 @@ export interface RandomAPI {
Shuffle<T>(deck: T[]): T[];
}

export default {
interface PrivateRandomAPI {
_obj: {
isUsed(): boolean;
getState(): any;
};
}

const RandomPlugin: Plugin<RandomAPI & PrivateRandomAPI> = {
name: 'random',

noClient: ({ api }) => {
Expand All @@ -49,3 +57,5 @@ export default {
return { seed };
},
};

export default RandomPlugin;

0 comments on commit c01d196

Please sign in to comment.