Skip to content

Commit

Permalink
fabricate playerID in singleplayer mode
Browse files Browse the repository at this point in the history
this is set to currentPlayer if playerID is not provided
  • Loading branch information
darthfiddler committed Jul 31, 2018
1 parent 5ab482e commit bd8208a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 35 deletions.
25 changes: 21 additions & 4 deletions src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,27 @@ function createDispatchers(
innerActionNames,
store,
playerID,
credentials
credentials,
multiplayer
) {
return innerActionNames.reduce((dispatchers, name) => {
dispatchers[name] = function(...args) {
let assumedPlayerID = playerID;

// In singleplayer mode, if the client does not have a playerID
// associated with it, we attach the currentPlayer as playerID.
if (!multiplayer && (playerID === null || playerID === undefined)) {
const state = store.getState();
assumedPlayerID = state.ctx.currentPlayer;
}

store.dispatch(
ActionCreators[storeActionType](name, args, playerID, credentials)
ActionCreators[storeActionType](
name,
args,
assumedPlayerID,
credentials
)
);
};
return dispatchers;
Expand Down Expand Up @@ -209,14 +224,16 @@ class _ClientImpl {
this.game.moveNames,
this.store,
this.playerID,
this.credentials
this.credentials,
this.multiplayer
);

this.events = createEventDispatchers(
this.game.flow.eventNames,
this.store,
this.playerID,
this.credentials
this.credentials,
this.multiplayer
);
}

Expand Down
70 changes: 56 additions & 14 deletions src/client/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
createEventDispatchers,
createMoveDispatchers,
} from './client';
import { gameEvent } from '../core/action-creators';
import Game from '../core/game';
import { RandomBot } from '../ai/bot';

Expand Down Expand Up @@ -197,32 +198,73 @@ test('event dispatchers', () => {
}
});

test('move dispatchers', () => {
describe('move dispatchers', () => {
const game = Game({
moves: {
A: G => G,
B: () => ({ moved: true }),
B: (G, ctx) => ({ moved: ctx.playerID }),
C: () => ({ victory: true }),
},
flow: {
endGameIf: (G, ctx) => (G.victory ? ctx.currentPlayer : undefined),
},
});

const reducer = CreateGameReducer({ game });
const store = createStore(reducer);
const api = createMoveDispatchers(game.moveNames, store);

expect(Object.getOwnPropertyNames(api)).toEqual(['A', 'B', 'C']);
expect(api.unknown).toBe(undefined);
test('basic', () => {
const store = createStore(reducer);
const api = createMoveDispatchers(game.moveNames, store);

expect(Object.getOwnPropertyNames(api)).toEqual(['A', 'B', 'C']);
expect(api.unknown).toBe(undefined);

api.A();
expect(store.getState().G).not.toMatchObject({ moved: true });
expect(store.getState().G).not.toMatchObject({ victory: true });

api.B();
expect(store.getState().G).toMatchObject({ moved: '0' });

store.dispatch(gameEvent('endTurn', null, '0'));

api.B();
expect(store.getState().G).toMatchObject({ moved: '1' });

api.C();
expect(store.getState().G).toMatchObject({ victory: true });
});

test('with undefined playerID - singleplayer mode', () => {
const store = createStore(reducer);
const api = createMoveDispatchers(game.moveNames, store);
api.B();
expect(store.getState().G).toMatchObject({ moved: '0' });
});

api.A();
expect(store.getState().G).not.toMatchObject({ moved: true });
expect(store.getState().G).not.toMatchObject({ victory: true });
test('with undefined playerID - multiplayer mode', () => {
const store = createStore(reducer);
const api = createMoveDispatchers(
game.moveNames,
store,
undefined,
null,
true
);
api.B();
expect(store.getState().G).toMatchObject({ moved: undefined });
});

api.B();
expect(store.getState().G).toMatchObject({ moved: true });
test('with null playerID - singleplayer mode', () => {
const store = createStore(reducer);
const api = createMoveDispatchers(game.moveNames, store, null);
api.B();
expect(store.getState().G).toMatchObject({ moved: '0' });
});

api.C();
expect(store.getState().G).toMatchObject({ victory: true });
test('with null playerID - multiplayer mode', () => {
const store = createStore(reducer);
const api = createMoveDispatchers(game.moveNames, store, null, null, true);
api.B();
expect(store.getState().G).toMatchObject({ moved: null });
});
});
8 changes: 4 additions & 4 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,15 +512,15 @@ export function FlowWithPhases({

const currentPlayerMoves = state.ctx.currentPlayerMoves + 1;
let _played = state.ctx._played;
if (action.payload && !_played.includes(action.payload.playerID)) {
_played = [..._played, action.payload.playerID];
if (!_played.includes(action.playerID)) {
_played = [..._played, action.playerID];
}
const allPlayed = _played.length == state.ctx.numPlayers;

// Update actionPlayers if _actionPlayersOnce is set.
let actionPlayers = state.ctx.actionPlayers;
if (state.ctx._actionPlayersOnce == true) {
const playerID = action.payload.playerID;
const playerID = action.playerID;
actionPlayers = actionPlayers.filter(id => id !== playerID);
}

Expand Down Expand Up @@ -574,7 +574,7 @@ export function FlowWithPhases({
// Update undo / redo state.
if (!endTurn) {
const undo = state._undo || [];
const moveType = action.payload.type;
const moveType = action.type;

let plainCtx = state.ctx;
plainCtx = Random.detach(plainCtx);
Expand Down
24 changes: 12 additions & 12 deletions src/core/flow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ test('movesPerTurn', () => {
let flow = FlowWithPhases({ movesPerTurn: 2 });
let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {}, payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.turn).toBe(0);
state = flow.processGameEvent(state, gameEvent('endTurn'));
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {}, payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.turn).toBe(1);
}

Expand All @@ -71,17 +71,17 @@ test('movesPerTurn', () => {
});
let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {}, payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.turn).toBe(0);
state = flow.processGameEvent(state, gameEvent('endTurn'));
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {}, payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.turn).toBe(1);

state = flow.processGameEvent(state, gameEvent('endPhase'));

expect(state.ctx.turn).toBe(1);
state = flow.processMove(state, { move: {}, payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.turn).toBe(2);
}
});
Expand Down Expand Up @@ -169,7 +169,7 @@ test('onMove', () => {
{
let flow = FlowWithPhases({ onMove });
let state = { G: {}, ctx: flow.ctx(2) };
state = flow.processMove(state, { payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.G).toEqual({ A: true });
}

Expand All @@ -179,10 +179,10 @@ test('onMove', () => {
phases: [{ name: 'A' }, { name: 'B', onMove: () => ({ B: true }) }],
});
let state = { G: {}, ctx: flow.ctx(2) };
state = flow.processMove(state, { payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.G).toEqual({ A: true });
state = flow.processGameEvent(state, gameEvent('endPhase'));
state = flow.processMove(state, { payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.G).toEqual({ B: true });
}
});
Expand Down Expand Up @@ -254,7 +254,7 @@ test('endPhaseIf', () => {
}

{
const t = flow.processMove(state, { type: 'move', payload: {} });
const t = flow.processMove(state, makeMove().payload);
expect(t.ctx.phase).toBe('B');
}

Expand Down Expand Up @@ -290,7 +290,7 @@ test('endGameIf', () => {
}

{
const t = flow.processMove(state, { type: 'move' });
const t = flow.processMove(state, makeMove('move').payload);
expect(t.ctx.gameover).toBe('A');
}
}
Expand All @@ -317,7 +317,7 @@ test('endGameIf', () => {
}

{
const t = flow.processMove(state, { type: 'move' });
const t = flow.processMove(state, makeMove('move').payload);
expect(t.ctx.gameover).toBe('A');
}
}
Expand Down Expand Up @@ -697,7 +697,7 @@ test('endPhaseOnMove', () => {
let state = { G: {}, ctx: flow.ctx(2) };

expect(state.ctx.phase).toBe('A');
state = flow.processMove(state, { payload: {} });
state = flow.processMove(state, makeMove().payload);
expect(state.ctx.phase).toBe('B');

expect(endPhaseACount).toEqual(1);
Expand Down
2 changes: 1 addition & 1 deletion src/core/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {
// Allow the flow reducer to process any triggers that happen after moves.
state = { ...state, ctx: random.attach(state.ctx) };
state = { ...state, ctx: events.attach(state.ctx) };
state = game.flow.processMove(state, action);
state = game.flow.processMove(state, action.payload);
state = events.update(state);
state = { ...state, ctx: random.update(state.ctx) };
state = { ...state, ctx: Random.detach(state.ctx) };
Expand Down

0 comments on commit bd8208a

Please sign in to comment.