Skip to content

Commit c115087

Browse files
feat(game): add disableUndo flag to game config (#742)
Co-authored-by: Chris Swithinbank <[email protected]>
1 parent 3078266 commit c115087

9 files changed

Lines changed: 95 additions & 5 deletions

File tree

docs/documentation/api/Game.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,8 @@
102102
// Called at the end of the game.
103103
// `ctx.gameover` is available at this point.
104104
onEnd: (G, ctx) => G,
105+
106+
// Disable undo feature for all the moves in the game
107+
disableUndo: true,
105108
}
106109
```

docs/documentation/undo.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ moves in the current turn. This is a common pattern in
55
games that allow a player to make multiple moves per turn,
66
and can be a useful feature to allow the player to experiment
77
with different move combinations (and seeing what they do)
8-
before committing to one.
8+
before committing to one. You can disable this feature by
9+
setting `disableUndo` to true in the game config.
910

1011
### Usage
1112

src/core/flow.test.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,32 @@ describe('phases', () => {
201201
});
202202

203203
describe('turn', () => {
204-
test('onBegin', () => {
204+
test('onBegin with undoable feature', () => {
205205
const onBegin = jest.fn(G => G);
206206
const flow = Flow({
207207
turn: { onBegin },
208+
disableUndo: false,
208209
});
209210
const state = { ctx: flow.ctx(2) };
210211

211212
expect(onBegin).not.toHaveBeenCalled();
212-
flow.init(state);
213+
const updatedState = flow.init(state);
214+
expect(onBegin).toHaveBeenCalled();
215+
expect(updatedState._undo).toHaveLength(1);
216+
});
217+
218+
test('onBegin without undoable feature', () => {
219+
const onBegin = jest.fn(G => G);
220+
const flow = Flow({
221+
turn: { onBegin },
222+
disableUndo: true,
223+
});
224+
const state = { ctx: flow.ctx(2) };
225+
226+
expect(onBegin).not.toHaveBeenCalled();
227+
const updatedState = flow.init(state);
213228
expect(onBegin).toHaveBeenCalled();
229+
expect(updatedState._undo).toHaveLength(0);
214230
});
215231

216232
test('onEnd', () => {

src/core/flow.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function Flow({
4343
turn,
4444
events,
4545
plugins,
46+
disableUndo,
4647
}: Game) {
4748
// Attach defaults.
4849
if (moves === undefined) {
@@ -296,7 +297,7 @@ export function Flow({
296297

297298
G = conf.turn.wrapped.onBegin({ ...state, G, ctx });
298299

299-
const _undo = [{ G, ctx }];
300+
const _undo = disableUndo ? [] : [{ G, ctx }];
300301

301302
return { ...state, G, ctx, _undo, _redo: [] };
302303
}

src/core/game.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,21 @@ describe('config errors', () => {
265265
);
266266
});
267267
});
268+
269+
describe('disableUndo', () => {
270+
test('set disableUndo to false by default', () => {
271+
const game = ProcessGameConfig({
272+
moves: {},
273+
});
274+
expect(game.disableUndo).toBeFalsy();
275+
});
276+
277+
test('set disableUndo to true', () => {
278+
const game = ProcessGameConfig({
279+
moves: {},
280+
disableUndo: true,
281+
});
282+
283+
expect(game.disableUndo).toBeTruthy();
284+
});
285+
});

src/core/game.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function ProcessGameConfig(game: Game | ProcessedGame): ProcessedGame {
4848
}
4949

5050
if (game.name === undefined) game.name = 'default';
51+
if (game.disableUndo === undefined) game.disableUndo = false;
5152
if (game.setup === undefined) game.setup = () => ({});
5253
if (game.moves === undefined) game.moves = {};
5354
if (game.playerView === undefined) game.playerView = G => G;

src/core/reducer.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,45 @@ test('undo / redo', () => {
364364
expect(state.G).toMatchObject({ roll: 4 });
365365
});
366366

367+
test('disable undo / redo', () => {
368+
const game: Game = {
369+
seed: 0,
370+
disableUndo: true,
371+
moves: {
372+
move: (G, ctx, arg) => ({ ...G, [arg]: true }),
373+
},
374+
};
375+
376+
const reducer = CreateGameReducer({ game });
377+
378+
let state = InitializeGame({ game });
379+
380+
state = reducer(state, makeMove('move', 'A'));
381+
expect(state.G).toMatchObject({ A: true });
382+
expect(state._undo).toEqual([]);
383+
expect(state._redo).toEqual([]);
384+
385+
state = reducer(state, makeMove('move', 'B'));
386+
expect(state.G).toMatchObject({ A: true, B: true });
387+
expect(state._undo).toEqual([]);
388+
expect(state._redo).toEqual([]);
389+
390+
state = reducer(state, undo());
391+
expect(state.G).toMatchObject({ A: true, B: true });
392+
expect(state._undo).toEqual([]);
393+
expect(state._redo).toEqual([]);
394+
395+
state = reducer(state, undo());
396+
expect(state.G).toMatchObject({ A: true, B: true });
397+
expect(state._undo).toEqual([]);
398+
expect(state._redo).toEqual([]);
399+
400+
state = reducer(state, redo());
401+
expect(state.G).toMatchObject({ A: true, B: true });
402+
expect(state._undo).toEqual([]);
403+
expect(state._redo).toEqual([]);
404+
});
405+
367406
describe('undo stack', () => {
368407
const game: Game = {
369408
moves: {

src/core/reducer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export function CreateGameReducer({
213213

214214
// Update undo / redo state.
215215
// Only update undo stack if the turn has not been ended
216-
if (state.ctx.turn === prevTurnCount) {
216+
if (state.ctx.turn === prevTurnCount && !game.disableUndo) {
217217
state._undo = state._undo.concat({
218218
G: state.G,
219219
ctx: state.ctx,
@@ -233,6 +233,11 @@ export function CreateGameReducer({
233233
}
234234

235235
case Actions.UNDO: {
236+
if (game.disableUndo) {
237+
error("Undo is not enabled");
238+
return state;
239+
}
240+
236241
const { _undo, _redo } = state;
237242

238243
if (_undo.length < 2) {
@@ -264,6 +269,11 @@ export function CreateGameReducer({
264269
case Actions.REDO: {
265270
const { _undo, _redo } = state;
266271

272+
if (game.disableUndo) {
273+
error("Redo is not enabled");
274+
return state;
275+
}
276+
267277
if (_redo.length == 0) {
268278
return state;
269279
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ interface PhaseMap<G extends any = any, CtxWithPlugins extends Ctx = Ctx> {
229229

230230
export interface Game<G extends any = any, CtxWithPlugins extends Ctx = Ctx> {
231231
name?: string;
232+
disableUndo?: boolean;
232233
seed?: string | number;
233234
setup?: (ctx: CtxWithPlugins, setupData?: any) => any;
234235
moves?: MoveMap<G, CtxWithPlugins>;

0 commit comments

Comments
 (0)