Skip to content

Commit

Permalink
introduce namespaced moves that are defined within phases
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolodavis committed Sep 10, 2019
1 parent bb81535 commit 7a411c9
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 7 deletions.
16 changes: 15 additions & 1 deletion src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function createDispatchers(
multiplayer
) {
return innerActionNames.reduce((dispatchers, name) => {
dispatchers[name] = function(...args) {
const fn = function(...args) {
let assumedPlayerID = playerID;

// In singleplayer mode, if the client does not have a playerID
Expand All @@ -50,6 +50,20 @@ function createDispatchers(
)
);
};

if (name.includes('.')) {
const [namespace, moveName] = name.split('.', 2);
if (!(namespace in dispatchers)) {
dispatchers[namespace] = {};
}
if (innerActionNames.indexOf(namespace) != -1) {
error(`namespace ${namespace} clashes with top-level move`);
}
dispatchers[namespace][moveName] = fn;
} else {
dispatchers[name] = fn;
}

return dispatchers;
}, {});
}
Expand Down
58 changes: 58 additions & 0 deletions src/client/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,64 @@ test('move api', () => {
expect(client.getState().G).toEqual({ arg: 42 });
});

describe('namespaced moves', () => {
let client;
beforeAll(() => {
client = Client({
game: Game({
moves: {
A: () => {},
},

flow: {
phases: {
PA: {
moves: {
B: () => {},
C: () => {},
},
},
},
},
}),
});
});

test('top-level moves', () => {
expect(client.moves.A).toBeInstanceOf(Function);
});

test('phase-level moves', () => {
expect(client.moves.PA.B).toBeInstanceOf(Function);
expect(client.moves.PA.C).toBeInstanceOf(Function);
});

test('name clash', () => {
Client({
game: Game({
moves: {
A: () => {},
},

flow: {
phases: {
A: {
moves: {
B: () => {},
C: () => {},
},
},
},
},
}),
});

expect(error).toHaveBeenCalledWith(
'namespace A clashes with top-level move'
);
});
});

test('isActive', () => {
const client = Client({
game: Game({
Expand Down
18 changes: 15 additions & 3 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import * as logging from './logger';
* Helper to create a reducer that manages ctx (with the
* ability to also update G).
*
* You probably want to use FlowWithPhases below, but you might
* need to use this directly if you are creating a very customized
* game flow that it cannot handle.
* This is mostly around for legacy reasons. The original plan
* was to have two flows, one with phases etc. and another
* simpler one like this. The current state is such that this
* is merely an internal function of FlowWithPhases below.
*
* @param {...object} ctx - Function with the signature
* numPlayers => ctx
Expand Down Expand Up @@ -67,6 +68,7 @@ export function Flow({
canMakeMove,
canUndoMove,
redactedMoves,
moveMap,
}) {
if (!ctx) ctx = () => ({});
if (!events) events = {};
Expand Down Expand Up @@ -103,6 +105,7 @@ export function Flow({
init,
canUndoMove,
redactedMoves,
moveMap,

eventNames: Object.getOwnPropertyNames(events),
enabledEventNames: Object.getOwnPropertyNames(enabledEvents),
Expand Down Expand Up @@ -313,9 +316,17 @@ export function FlowWithPhases({

phaseMap['default'] = {};

let moveMap = {};

for (let phase in phaseMap) {
const conf = phaseMap[phase];

if (conf.moves !== undefined) {
for (let move of Object.keys(conf.moves)) {
moveMap[phase + '.' + move] = conf.moves[move];
}
}

if (conf.endPhaseIf === undefined) {
conf.endPhaseIf = () => undefined;
}
Expand Down Expand Up @@ -752,5 +763,6 @@ export function FlowWithPhases({
canMakeMove,
canUndoMove,
redactedMoves,
moveMap,
});
}
20 changes: 18 additions & 2 deletions src/core/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,30 @@ function Game(game) {

return {
...game,
moveNames: Object.getOwnPropertyNames(game.moves),

moveNames: [
...Object.getOwnPropertyNames(game.moves),
...Object.keys(game.flow.moveMap || []),
],

processMove: (G, action, ctx) => {
let moveFn = null;

if (game.moves.hasOwnProperty(action.type)) {
moveFn = game.moves[action.type];
}

if (action.type in game.flow.moveMap) {
moveFn = game.flow.moveMap[action.type];
}

if (moveFn !== null) {
const ctxWithPlayerID = { ...ctx, playerID: action.playerID };
const args = [G, ctxWithPlayerID].concat(action.args);
const fn = FnWrap(game.moves[action.type], game);
const fn = FnWrap(moveFn, game);
return fn(...args);
}

return G;
},
};
Expand Down
13 changes: 12 additions & 1 deletion src/core/game.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@ const game = Game({
A: G => G,
B: () => null,
},

flow: {
phases: {
PA: {
moves: {
A: () => 'PA.A',
},
},
},
},
});

test('basic', () => {
expect(game.moveNames).toEqual(['A', 'B']);
expect(game.moveNames).toEqual(['A', 'B', 'PA.A']);
expect(typeof game.processMove).toEqual('function');
});

Expand All @@ -27,6 +37,7 @@ test('processMove', () => {
expect(game.processMove(testObj, { type: 'A' })).toEqual(testObj);
expect(game.processMove(testObj, { type: 'C' })).toEqual(testObj);
expect(game.processMove(testObj, { type: 'B' })).toEqual(null);
expect(game.processMove(testObj, { type: 'PA.A' })).toEqual('PA.A');
});

test('flow override', () => {
Expand Down

0 comments on commit 7a411c9

Please sign in to comment.