diff --git a/src/client/client.js b/src/client/client.js index 540f81e83..ee9ddb98a 100644 --- a/src/client/client.js +++ b/src/client/client.js @@ -70,6 +70,10 @@ class _ClientImpl { multiplayer, }); + this.reset = () => { + this.store.dispatch(ActionCreators.reset()); + }; + this.store = null; if (multiplayer) { diff --git a/src/client/react.js b/src/client/react.js index be8c182bf..dbe69ed7b 100644 --- a/src/client/react.js +++ b/src/client/react.js @@ -28,7 +28,7 @@ import { Client as RawClient } from './client'; * Returns: * A React component that wraps board and provides an * API through props for it to interact with the framework - * and dispatch actions such as MAKE_MOVE and END_TURN. + * and dispatch actions such as MAKE_MOVE, GAME_EVENT and RESET. */ export function Client({ game, @@ -108,6 +108,7 @@ export function Client({ events: this.client.events, gameID: this.props.gameID, playerID: this.props.playerID, + reset: this.client.reset, }); } @@ -120,6 +121,7 @@ export function Client({ events: this.client.events, gameID: this.props.gameID, playerID: this.props.playerID, + reset: this.client.reset, }); } diff --git a/src/client/react.test.js b/src/client/react.test.js index 5416934f6..0cf6ecd33 100644 --- a/src/client/react.test.js +++ b/src/client/react.test.js @@ -192,3 +192,26 @@ test('local playerView', () => { expect(board.props.G).toEqual({ stripped: '1' }); } }); + +test('reset Game', () => { + const Board = Client({ + game: Game({ + moves: { + A: (G, ctx, arg) => ({ arg }), + }, + }), + board: TestBoard, + }); + + const game = Enzyme.mount(); + const board = game.find('TestBoard').instance(); + + const initial = { G: { ...board.props.G }, ctx: { ...board.props.ctx } }; + + expect(board.props.G).toEqual({}); + board.props.moves.A(42); + expect(board.props.G).toEqual({ arg: 42 }); + board.props.reset(); + expect(board.props.G).toEqual(initial.G); + expect(board.props.ctx).toEqual(initial.ctx); +}); diff --git a/src/core/action-creators.js b/src/core/action-creators.js index f0638e7c9..75d8126be 100644 --- a/src/core/action-creators.js +++ b/src/core/action-creators.js @@ -40,3 +40,10 @@ export const restore = state => ({ type: Actions.RESTORE, state, }); + +/** + * Used to reset the game state. + */ +export const reset = () => ({ + type: Actions.RESET, +}); diff --git a/src/core/action-types.js b/src/core/action-types.js index fca3ce31b..0f3dfc07a 100644 --- a/src/core/action-types.js +++ b/src/core/action-types.js @@ -9,3 +9,4 @@ export const MAKE_MOVE = 'MAKE_MOVE'; export const GAME_EVENT = 'GAME_EVENT'; export const RESTORE = 'RESTORE'; +export const RESET = 'RESET'; diff --git a/src/core/flow.test.js b/src/core/flow.test.js index 6d8d7bc65..18846e165 100644 --- a/src/core/flow.test.js +++ b/src/core/flow.test.js @@ -8,7 +8,7 @@ import Game from './game'; import { createGameReducer } from './reducer'; -import { makeMove, gameEvent } from './action-creators'; +import { makeMove, gameEvent, reset } from './action-creators'; import { Flow, FlowWithPhases } from './flow'; test('Flow', () => { @@ -618,6 +618,44 @@ test('endGame', () => { } }); +test('resetGame', () => { + let game = Game({ + moves: { + move: (G, ctx, arg) => ({ ...G, [arg]: true }), + }, + }); + + const reducer = createGameReducer({ game, numPlayers: 2 }); + + let state = reducer(undefined, { type: 'init' }); + + const originalState = state; + + state = reducer(state, makeMove('move', 'A')); + expect(state.G).toEqual({ A: true }); + + state = reducer(state, makeMove('move', 'B')); + expect(state.G).toEqual({ A: true, B: true }); + + state = reducer(state, gameEvent('endTurn')); + expect(state.ctx.turn).toEqual(1); + + state = reducer(state, reset()); + expect(state).toEqual(originalState); + + state = reducer(state, makeMove('move', 'C')); + expect(state.G).toEqual({ C: true }); + + state = reducer(state, gameEvent('undo')); + expect(state.G).toEqual({}); + + state = reducer(state, gameEvent('redo')); + expect(state.G).toEqual({ C: true }); + + state = reducer(state, reset()); + expect(state).toEqual(originalState); +}); + test('change action players', () => { const flow = FlowWithPhases({}); const state = { ctx: {} }; diff --git a/src/core/reducer.js b/src/core/reducer.js index 7c1c29281..18be6511e 100644 --- a/src/core/reducer.js +++ b/src/core/reducer.js @@ -178,6 +178,10 @@ export function createGameReducer({ game, numPlayers, multiplayer }) { return action.state; } + case Actions.RESET: { + return initial; + } + default: { return state; } diff --git a/src/core/reducer.test.js b/src/core/reducer.test.js index e5da909bb..69a52e021 100644 --- a/src/core/reducer.test.js +++ b/src/core/reducer.test.js @@ -8,7 +8,7 @@ import Game from './game'; import { createGameReducer } from './reducer'; -import { makeMove, gameEvent, restore } from './action-creators'; +import { makeMove, gameEvent, restore, reset } from './action-creators'; const game = Game({ moves: { @@ -68,6 +68,16 @@ test('restore', () => { expect(state).toEqual({ G: 'restored' }); }); +test('reset', () => { + const reducer = createGameReducer({ game }); + let state = reducer(undefined, makeMove('A')); + const initialState = { ...state._initial, _initial: { ...state._initial } }; + + expect(state).not.toEqual(initialState); + state = reducer(state, reset()); + expect(state).toEqual(initialState); +}); + test('victory', () => { const reducer = createGameReducer({ game });