Skip to content

Commit

Permalink
Differentiate automatic game events in the log
Browse files Browse the repository at this point in the history
This allows the log to not replay automatic / synthetic game events
that are generated as side-effects of moves (either through the
Events API or through a trigger).
  • Loading branch information
nicolodavis committed Jul 24, 2018
1 parent 7fdeb54 commit edd1df0
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 21 deletions.
4 changes: 3 additions & 1 deletion src/client/log/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export class GameLog extends React.Component {
let state = this.props.initialState;
for (let i = 0; i <= logIndex; i++) {
const action = this.props.log[i];
state = this.props.reducer(state, action);
if (!action.automatic) {
state = this.props.reducer(state, action);
}
}
return { G: state.G, ctx: state.ctx };
};
Expand Down
52 changes: 40 additions & 12 deletions src/client/log/log.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,34 +53,62 @@ describe('time travel', () => {
state = reducer(state, makeMove('A', [2]));
state = reducer(state, gameEvent('endTurn'));

test('basic', () => {
const root = Enzyme.mount(
<GameLog
log={state.log}
initialState={initialState}
onHover={({ state: t }) => {
state = t;
}}
reducer={reducer}
/>
);

const root = Enzyme.mount(
<GameLog
log={state.log}
initialState={initialState}
onHover={({ state: t }) => {
state = t;
}}
reducer={reducer}
/>
);

test('before rewind', () => {
expect(state.G).toMatchObject({ arg: 2 });
});

test('0 - regular move', () => {
root
.find('.log-event')
.at(0)
.simulate('mouseenter');

expect(state.G).toMatchObject({ arg: 1 });
expect(state.ctx.currentPlayer).toBe('0');
});

test('1 - regular event', () => {
root
.find('.log-event')
.at(1)
.simulate('mouseenter');

expect(state.G).toMatchObject({ arg: 1 });
expect(state.ctx.currentPlayer).toBe('1');
});

test('2 - move with automatic event', () => {
root
.find('.log-event')
.at(2)
.simulate('mouseenter');

expect(state.G).toMatchObject({ arg: 42 });
expect(state.ctx.currentPlayer).toBe('0');
});

test('3 - no replaying automatic event', () => {
root
.find('.log-event')
.at(3)
.simulate('mouseenter');

expect(state.G).toMatchObject({ arg: 42 });
expect(state.ctx.currentPlayer).toBe('0');
});

test('mouseleave', () => {
root
.find('.log-event')
.at(0)
Expand Down
13 changes: 13 additions & 0 deletions src/core/action-creators.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ export const gameEvent = (type, args, playerID, credentials) => ({
payload: { type, args, playerID, credentials },
});

/**
* Generate an automatic game event that is a side-effect of a move.
* @param {string} type - The event type.
* @param {Array} args - Additional arguments.
* @param {string} playerID - The ID of the player making this action.
* @param {string} credentials - (optional) The credentials for the player making this action.
*/
export const automaticGameEvent = (type, args, playerID, credentials) => ({
type: Actions.GAME_EVENT,
payload: { type, args, playerID, credentials },
automatic: true,
});

/**
* Used to reset the Redux store's state.
* @param {object} state - The state to restore.
Expand Down
4 changes: 2 additions & 2 deletions src/core/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* https://opensource.org/licenses/MIT.
*/

import { gameEvent } from './action-creators';
import { automaticGameEvent } from './action-creators';

/**
* Events
Expand Down Expand Up @@ -40,7 +40,7 @@ export class Events {
*/
update(state) {
for (const item of this.dispatch) {
const action = gameEvent(item.key, item.args, this.playerID);
const action = automaticGameEvent(item.key, item.args, this.playerID);
state = {
...state,
...this.flow.processGameEvent(state, action),
Expand Down
15 changes: 9 additions & 6 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { TurnOrder } from './turn-order';
import { Random } from './random';
import { Events } from './events';
import { gameEvent } from './action-creators';
import { automaticGameEvent } from './action-creators';

/**
* Helper to create a reducer that manages ctx (with the
Expand Down Expand Up @@ -419,7 +419,7 @@ export function FlowWithPhases({
if (end) {
state = this.dispatch(
state,
gameEvent('endPhase', [end, cascadeDepth + 1], this.playerID)
automaticGameEvent('endPhase', [end, cascadeDepth + 1], this.playerID)
);
}
}
Expand All @@ -430,7 +430,7 @@ export function FlowWithPhases({
if (endTurn && state.ctx.turn == origTurn) {
state = this.dispatch(
state,
gameEvent('endTurn', [endTurn], this.playerID)
automaticGameEvent('endTurn', [endTurn], this.playerID)
);
}

Expand Down Expand Up @@ -494,7 +494,7 @@ export function FlowWithPhases({
if (end) {
return this.dispatch(
{ ...state, G, ctx },
gameEvent('endPhase', [end], this.playerID)
automaticGameEvent('endPhase', [end], this.playerID)
);
}

Expand Down Expand Up @@ -536,7 +536,7 @@ export function FlowWithPhases({
if (endPhase || gameover !== undefined) {
state = dispatch(
state,
gameEvent('endPhase', [endPhase], action.playerID)
automaticGameEvent('endPhase', [endPhase], action.playerID)
);
// Update to the new phase configuration
conf = phaseMap[state.ctx.phase];
Expand All @@ -546,7 +546,10 @@ export function FlowWithPhases({
// (but not if endPhase above already ends the turn).
const endTurn = shouldEndTurn(state);
if (state.ctx.turn == origTurn && (endTurn || gameover !== undefined)) {
state = dispatch(state, gameEvent('endTurn', [endTurn], action.playerID));
state = dispatch(
state,
automaticGameEvent('endTurn', [endTurn], action.playerID)
);
}

// End the game automatically if endGameIf returns.
Expand Down

0 comments on commit edd1df0

Please sign in to comment.