Skip to content

Commit cff284b

Browse files
committed
convert startingPhase into boolean option
1 parent b9ce7f1 commit cff284b

9 files changed

Lines changed: 69 additions & 117 deletions

File tree

src/client/client.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ describe('multiplayer', () => {
175175

176176
test('onAction called', () => {
177177
jest.spyOn(client.transport, 'onAction');
178-
client.store.dispatch(sync({ G: {}, ctx: { phase: 'default' } }, []));
178+
client.store.dispatch(sync({ G: {}, ctx: { phase: '' } }, []));
179179
client.moves.A();
180180
expect(client.transport.onAction).toHaveBeenCalled();
181181
});
@@ -439,13 +439,13 @@ describe('log handling', () => {
439439
{
440440
action: makeMove('A', [], '0'),
441441
_stateID: 0,
442-
phase: 'default',
442+
phase: '',
443443
turn: 0,
444444
},
445445
{
446446
action: makeMove('A', [], '0'),
447447
_stateID: 1,
448-
phase: 'default',
448+
phase: '',
449449
turn: 0,
450450
},
451451
]);

src/client/log/log.test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ Enzyme.configure({ adapter: new Adapter() });
1919

2020
describe('layout', () => {
2121
const game = {
22-
startingPhase: 'A',
2322
phases: {
24-
A: { next: 'B' },
23+
A: { next: 'B', start: true },
2524
B: { next: 'A' },
2625
},
2726
};

src/core/flow.js

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,13 @@ export function Flow({
174174
*
175175
* // A phase-specific turn structure that overrides the global.
176176
* turn: { ... },
177+
*
178+
* // Set to true to begin the game in this phase. Only one phase
179+
* // can have this set to true.
180+
* start: false,
177181
* }
178182
*/
179-
export function FlowWithPhases({
180-
phases,
181-
startingPhase,
182-
endIf,
183-
turn,
184-
events,
185-
plugins,
186-
}) {
183+
export function FlowWithPhases({ phases, endIf, turn, events, plugins }) {
187184
// Attach defaults.
188185
if (events === undefined) {
189186
events = {};
@@ -203,23 +200,28 @@ export function FlowWithPhases({
203200
if (plugins === undefined) {
204201
plugins = [];
205202
}
206-
if (!startingPhase) startingPhase = 'default';
203+
207204
if (!endIf) endIf = () => undefined;
208205
if (!turn) turn = {};
209206

210207
const phaseMap = phases || {};
211208

212-
if ('default' in phaseMap) {
213-
logging.error('cannot specify phase with name "default"');
209+
if ('' in phaseMap) {
210+
logging.error('cannot specify phase with empty name');
214211
}
215212

216-
phaseMap['default'] = {};
213+
phaseMap[''] = {};
217214

218215
let moveMap = {};
216+
let startingPhase = '';
219217

220218
for (let phase in phaseMap) {
221219
const conf = phaseMap[phase];
222220

221+
if (conf.start === true) {
222+
startingPhase = phase;
223+
}
224+
223225
if (conf.moves !== undefined) {
224226
for (let move of Object.keys(conf.moves)) {
225227
moveMap[phase + '.' + move] = conf.moves[move];
@@ -258,13 +260,17 @@ export function FlowWithPhases({
258260
conf.turn.onEnd = plugin.FnWrap(conf.turn.onEnd, plugins);
259261
}
260262

263+
function GetPhase(ctx) {
264+
return phaseMap[ctx.phase];
265+
}
266+
261267
const shouldEndPhase = ({ G, ctx }) => {
262-
const conf = phaseMap[ctx.phase];
268+
const conf = GetPhase(ctx);
263269
return conf.endIf(G, ctx);
264270
};
265271

266272
const shouldEndTurn = ({ G, ctx }) => {
267-
const conf = phaseMap[ctx.phase];
273+
const conf = GetPhase(ctx);
268274

269275
const currentPlayerMoves = ctx.stats.turn.numMoves[ctx.currentPlayer] || 0;
270276
if (conf.turn.moveLimit && currentPlayerMoves >= conf.turn.moveLimit) {
@@ -317,12 +323,7 @@ export function FlowWithPhases({
317323
};
318324

319325
const startGame = function(state) {
320-
if (!(state.ctx.phase in phaseMap)) {
321-
logging.error('invalid startingPhase: ' + state.ctx.phase);
322-
return state;
323-
}
324-
325-
const conf = phaseMap[state.ctx.phase];
326+
const conf = GetPhase(state.ctx);
326327
state = startPhase(state, conf);
327328
state = startTurn(state, conf);
328329
return state;
@@ -346,31 +347,29 @@ export function FlowWithPhases({
346347
let ctx = state.ctx;
347348

348349
// Run any cleanup code for the phase that is about to end.
349-
const conf = phaseMap[ctx.phase];
350+
const conf = GetPhase(ctx);
350351
G = conf.onEnd(G, ctx);
351352

352353
const gameover = endIf(G, ctx);
353354
if (gameover !== undefined) {
354355
return { ...state, G, ctx: { ...ctx, gameover } };
355356
}
356357

357-
const prevPhase = ctx.phase;
358-
359358
// Update the phase.
360359
if (arg && arg !== true) {
361360
if (arg.next in phaseMap) {
362-
ctx = { ...ctx, phase: arg.next, prevPhase };
361+
ctx = { ...ctx, phase: arg.next };
363362
} else {
364363
logging.error('invalid argument to endPhase: ' + arg);
365364
}
366365
} else if (conf.next !== undefined) {
367-
ctx = { ...ctx, phase: conf.next, prevPhase };
366+
ctx = { ...ctx, phase: conf.next };
368367
} else {
369-
ctx = { ...ctx, phase: ctx.prevPhase, prevPhase };
368+
ctx = { ...ctx, phase: '' };
370369
}
371370

372371
// Run any setup code for the new phase.
373-
state = startPhase({ ...state, G, ctx }, phaseMap[ctx.phase]);
372+
state = startPhase({ ...state, G, ctx }, GetPhase(ctx));
374373

375374
const origTurn = state.ctx.turn;
376375

@@ -387,7 +386,7 @@ export function FlowWithPhases({
387386
state,
388387
automaticGameEvent(
389388
'endPhase',
390-
[{ next: 'default' }, visitedPhases],
389+
[{ next: '' }, visitedPhases],
391390
this.playerID
392391
)
393392
);
@@ -424,7 +423,7 @@ export function FlowWithPhases({
424423
function endTurnEvent(state, arg) {
425424
let { G, ctx } = state;
426425

427-
const conf = phaseMap[ctx.phase];
426+
const conf = GetPhase(ctx);
428427

429428
// Prevent ending the turn if moveLimit haven't been made.
430429
const currentPlayerMoves = ctx.stats.turn.numMoves[ctx.currentPlayer] || 0;
@@ -503,7 +502,7 @@ export function FlowWithPhases({
503502
}
504503

505504
function processMove(state, action, dispatch) {
506-
let conf = phaseMap[state.ctx.phase];
505+
let conf = GetPhase(state.ctx);
507506

508507
state = updateStats(state, 'turn', action.playerID);
509508
state = updateStats(state, 'phase', action.playerID);
@@ -542,7 +541,7 @@ export function FlowWithPhases({
542541
automaticGameEvent('endPhase', [endPhase], action.playerID)
543542
);
544543
// Update to the new phase configuration
545-
conf = phaseMap[state.ctx.phase];
544+
conf = GetPhase(state.ctx);
546545
}
547546

548547
// End the turn automatically if turn.endIf is true or if endIf returns.
@@ -610,7 +609,6 @@ export function FlowWithPhases({
610609
stats: { turn: { numMoves: {} }, phase: { numMoves: {} } },
611610
allPlayed: false,
612611
phase: startingPhase,
613-
prevPhase: 'default',
614612
stage: {},
615613
}),
616614
init: state => {

src/core/flow.test.js

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,19 @@ test('Flow', () => {
2929
});
3030

3131
describe('phases', () => {
32-
test('invalid startingPhase', () => {
33-
const flow = FlowWithPhases({
34-
startingPhase: 'A',
35-
phases: { B: {} },
36-
});
37-
flow.init({ ctx: flow.ctx(2) });
38-
expect(error).toHaveBeenCalledWith(`invalid startingPhase: A`);
39-
});
40-
4132
test('invalid phase name', () => {
4233
const flow = FlowWithPhases({
43-
startingPhase: 'A',
44-
phases: { default: {} },
34+
phases: { '': {} },
4535
});
4636
flow.init({ ctx: flow.ctx(2) });
47-
expect(error).toHaveBeenCalledWith(
48-
`cannot specify phase with name "default"`
49-
);
37+
expect(error).toHaveBeenCalledWith('cannot specify phase with empty name');
5038
});
5139

5240
test('onBegin / onEnd', () => {
5341
const flow = FlowWithPhases({
54-
startingPhase: 'A',
55-
5642
phases: {
5743
A: {
44+
start: true,
5845
onBegin: s => ({ ...s, setupA: true }),
5946
onEnd: s => ({ ...s, cleanupA: true }),
6047
next: 'B',
@@ -101,8 +88,7 @@ describe('phases', () => {
10188

10289
test('endIf', () => {
10390
const flow = FlowWithPhases({
104-
startingPhase: 'A',
105-
phases: { A: { endIf: () => true, next: 'B' }, B: {} },
91+
phases: { A: { start: true, endIf: () => true, next: 'B' }, B: {} },
10692
});
10793

10894
const state = { ctx: flow.ctx(2) };
@@ -126,9 +112,8 @@ describe('phases', () => {
126112
test('infinite loop', () => {
127113
const endIf = () => true;
128114
const flow = FlowWithPhases({
129-
startingPhase: 'A',
130115
phases: {
131-
A: { endIf, next: 'B' },
116+
A: { endIf, next: 'B', start: true },
132117
B: { endIf, next: 'A' },
133118
},
134119
});
@@ -138,7 +123,7 @@ describe('phases', () => {
138123

139124
expect(state.ctx.phase).toBe('A');
140125
state = flow.processGameEvent(state, gameEvent('endPhase'));
141-
expect(state.ctx.phase).toBe('default');
126+
expect(state.ctx.phase).toBe('');
142127
});
143128

144129
test('end phase on move', () => {
@@ -147,9 +132,9 @@ describe('phases', () => {
147132
const onMove = () => ({ A: true });
148133

149134
const flow = FlowWithPhases({
150-
startingPhase: 'A',
151135
phases: {
152136
A: {
137+
start: true,
153138
turn: { endIf: () => true, onMove },
154139
endIf: () => true,
155140
onEnd: () => ++endPhaseACount,
@@ -175,8 +160,7 @@ describe('phases', () => {
175160
test('end turn when final phase is reached', () => {
176161
const flow = FlowWithPhases({
177162
turn: { endIf: (G, ctx) => ctx.phase === 'C' },
178-
startingPhase: 'A',
179-
phases: { A: { next: 'B' }, B: { next: 'C' }, C: {} },
163+
phases: { A: { start: true, next: 'B' }, B: { next: 'C' }, C: {} },
180164
});
181165

182166
let state = { G: {}, ctx: flow.ctx(2) };
@@ -292,8 +276,7 @@ test('onMove', () => {
292276

293277
test('init', () => {
294278
let flow = FlowWithPhases({
295-
startingPhase: 'A',
296-
phases: { A: { onEnd: () => ({ done: true }) } },
279+
phases: { A: { start: true, onEnd: () => ({ done: true }) } },
297280
});
298281

299282
const orig = flow.ctx(2);
@@ -302,8 +285,7 @@ test('init', () => {
302285
expect(state).toEqual({ G: {}, ctx: orig });
303286

304287
flow = FlowWithPhases({
305-
startingPhase: 'A',
306-
phases: { A: { onBegin: () => ({ done: true }) } },
288+
phases: { A: { start: true, onBegin: () => ({ done: true }) } },
307289
});
308290

309291
state = { ctx: orig };
@@ -381,9 +363,8 @@ describe('turn.endIf', () => {
381363
A: () => ({ endTurn: true }),
382364
B: G => G,
383365
},
384-
startingPhase: 'A',
385366
phases: {
386-
A: { turn: { endIf: G => G.endTurn } },
367+
A: { start: true, turn: { endIf: G => G.endTurn } },
387368
},
388369
};
389370
const client = Client({ game });
@@ -471,8 +452,7 @@ test('endGame', () => {
471452

472453
describe('endTurn / endPhase args', () => {
473454
const flow = FlowWithPhases({
474-
startingPhase: 'A',
475-
phases: { A: { next: 'B' }, B: {}, C: {} },
455+
phases: { A: { start: true, next: 'B' }, B: {}, C: {} },
476456
});
477457

478458
const state = { ctx: flow.ctx(3) };
@@ -530,9 +510,8 @@ test('undoable moves', () => {
530510
C: () => ({ C: true }),
531511
},
532512

533-
startingPhase: 'A',
534513
phases: {
535-
A: {},
514+
A: { start: true },
536515
B: {},
537516
},
538517
};
@@ -574,8 +553,7 @@ test('undoable moves', () => {
574553
test('endTurn is not called twice in one move', () => {
575554
const flow = FlowWithPhases({
576555
turn: { endIf: () => true },
577-
startingPhase: 'A',
578-
phases: { A: { endIf: G => G.endPhase, next: 'B' }, B: {} },
556+
phases: { A: { start: true, endIf: G => G.endPhase, next: 'B' }, B: {} },
579557
});
580558

581559
let state = flow.init({ G: {}, ctx: flow.ctx(2) });
@@ -628,8 +606,7 @@ test('allPlayed', () => {
628606
describe('endPhase returns to previous phase', () => {
629607
let state;
630608
const flow = FlowWithPhases({
631-
startingPhase: 'A',
632-
phases: { A: {}, B: {}, C: {} },
609+
phases: { A: { start: true }, B: {}, C: {} },
633610
});
634611

635612
beforeEach(() => {
@@ -640,19 +617,6 @@ describe('endPhase returns to previous phase', () => {
640617
test('returns to default', () => {
641618
expect(state.ctx.phase).toBe('A');
642619
state = flow.processGameEvent(state, gameEvent('endPhase'));
643-
expect(state.ctx.phase).toBe('default');
644-
});
645-
646-
test('returns to previous', () => {
647-
expect(state.ctx.phase).toBe('A');
648-
state = flow.processGameEvent(
649-
state,
650-
gameEvent('endPhase', [{ next: 'B' }])
651-
);
652-
expect(state.ctx.phase).toBe('B');
653-
state = flow.processGameEvent(state, gameEvent('endPhase'));
654-
expect(state.ctx.phase).toBe('A');
655-
state = flow.processGameEvent(state, gameEvent('endPhase'));
656-
expect(state.ctx.phase).toBe('B');
620+
expect(state.ctx.phase).toBe('');
657621
});
658622
});

0 commit comments

Comments
 (0)