Skip to content

Commit edd1df0

Browse files
committed
Differentiate automatic game events in the log
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).
1 parent 7fdeb54 commit edd1df0

5 files changed

Lines changed: 67 additions & 21 deletions

File tree

src/client/log/log.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ export class GameLog extends React.Component {
7979
let state = this.props.initialState;
8080
for (let i = 0; i <= logIndex; i++) {
8181
const action = this.props.log[i];
82-
state = this.props.reducer(state, action);
82+
if (!action.automatic) {
83+
state = this.props.reducer(state, action);
84+
}
8385
}
8486
return { G: state.G, ctx: state.ctx };
8587
};

src/client/log/log.test.js

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,62 @@ describe('time travel', () => {
5353
state = reducer(state, makeMove('A', [2]));
5454
state = reducer(state, gameEvent('endTurn'));
5555

56-
test('basic', () => {
57-
const root = Enzyme.mount(
58-
<GameLog
59-
log={state.log}
60-
initialState={initialState}
61-
onHover={({ state: t }) => {
62-
state = t;
63-
}}
64-
reducer={reducer}
65-
/>
66-
);
67-
56+
const root = Enzyme.mount(
57+
<GameLog
58+
log={state.log}
59+
initialState={initialState}
60+
onHover={({ state: t }) => {
61+
state = t;
62+
}}
63+
reducer={reducer}
64+
/>
65+
);
66+
67+
test('before rewind', () => {
6868
expect(state.G).toMatchObject({ arg: 2 });
69+
});
6970

71+
test('0 - regular move', () => {
7072
root
7173
.find('.log-event')
7274
.at(0)
7375
.simulate('mouseenter');
7476

7577
expect(state.G).toMatchObject({ arg: 1 });
78+
expect(state.ctx.currentPlayer).toBe('0');
79+
});
7680

81+
test('1 - regular event', () => {
82+
root
83+
.find('.log-event')
84+
.at(1)
85+
.simulate('mouseenter');
86+
87+
expect(state.G).toMatchObject({ arg: 1 });
88+
expect(state.ctx.currentPlayer).toBe('1');
89+
});
90+
91+
test('2 - move with automatic event', () => {
7792
root
7893
.find('.log-event')
7994
.at(2)
8095
.simulate('mouseenter');
8196

8297
expect(state.G).toMatchObject({ arg: 42 });
98+
expect(state.ctx.currentPlayer).toBe('0');
99+
});
100+
101+
test('3 - no replaying automatic event', () => {
102+
root
103+
.find('.log-event')
104+
.at(3)
105+
.simulate('mouseenter');
106+
107+
expect(state.G).toMatchObject({ arg: 42 });
108+
expect(state.ctx.currentPlayer).toBe('0');
109+
});
83110

111+
test('mouseleave', () => {
84112
root
85113
.find('.log-event')
86114
.at(0)

src/core/action-creators.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ export const gameEvent = (type, args, playerID, credentials) => ({
3434
payload: { type, args, playerID, credentials },
3535
});
3636

37+
/**
38+
* Generate an automatic game event that is a side-effect of a move.
39+
* @param {string} type - The event type.
40+
* @param {Array} args - Additional arguments.
41+
* @param {string} playerID - The ID of the player making this action.
42+
* @param {string} credentials - (optional) The credentials for the player making this action.
43+
*/
44+
export const automaticGameEvent = (type, args, playerID, credentials) => ({
45+
type: Actions.GAME_EVENT,
46+
payload: { type, args, playerID, credentials },
47+
automatic: true,
48+
});
49+
3750
/**
3851
* Used to reset the Redux store's state.
3952
* @param {object} state - The state to restore.

src/core/events.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* https://opensource.org/licenses/MIT.
77
*/
88

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

1111
/**
1212
* Events
@@ -40,7 +40,7 @@ export class Events {
4040
*/
4141
update(state) {
4242
for (const item of this.dispatch) {
43-
const action = gameEvent(item.key, item.args, this.playerID);
43+
const action = automaticGameEvent(item.key, item.args, this.playerID);
4444
state = {
4545
...state,
4646
...this.flow.processGameEvent(state, action),

src/core/flow.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { TurnOrder } from './turn-order';
1010
import { Random } from './random';
1111
import { Events } from './events';
12-
import { gameEvent } from './action-creators';
12+
import { automaticGameEvent } from './action-creators';
1313

1414
/**
1515
* Helper to create a reducer that manages ctx (with the
@@ -419,7 +419,7 @@ export function FlowWithPhases({
419419
if (end) {
420420
state = this.dispatch(
421421
state,
422-
gameEvent('endPhase', [end, cascadeDepth + 1], this.playerID)
422+
automaticGameEvent('endPhase', [end, cascadeDepth + 1], this.playerID)
423423
);
424424
}
425425
}
@@ -430,7 +430,7 @@ export function FlowWithPhases({
430430
if (endTurn && state.ctx.turn == origTurn) {
431431
state = this.dispatch(
432432
state,
433-
gameEvent('endTurn', [endTurn], this.playerID)
433+
automaticGameEvent('endTurn', [endTurn], this.playerID)
434434
);
435435
}
436436

@@ -494,7 +494,7 @@ export function FlowWithPhases({
494494
if (end) {
495495
return this.dispatch(
496496
{ ...state, G, ctx },
497-
gameEvent('endPhase', [end], this.playerID)
497+
automaticGameEvent('endPhase', [end], this.playerID)
498498
);
499499
}
500500

@@ -536,7 +536,7 @@ export function FlowWithPhases({
536536
if (endPhase || gameover !== undefined) {
537537
state = dispatch(
538538
state,
539-
gameEvent('endPhase', [endPhase], action.playerID)
539+
automaticGameEvent('endPhase', [endPhase], action.playerID)
540540
);
541541
// Update to the new phase configuration
542542
conf = phaseMap[state.ctx.phase];
@@ -546,7 +546,10 @@ export function FlowWithPhases({
546546
// (but not if endPhase above already ends the turn).
547547
const endTurn = shouldEndTurn(state);
548548
if (state.ctx.turn == origTurn && (endTurn || gameover !== undefined)) {
549-
state = dispatch(state, gameEvent('endTurn', [endTurn], action.playerID));
549+
state = dispatch(
550+
state,
551+
automaticGameEvent('endTurn', [endTurn], action.playerID)
552+
);
550553
}
551554

552555
// End the game automatically if endGameIf returns.

0 commit comments

Comments
 (0)