Skip to content

Commit 9b0324c

Browse files
committed
allow setting the next player via endTurn
1 parent 278b369 commit 9b0324c

4 files changed

Lines changed: 98 additions & 41 deletions

File tree

docs/turn-order.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ is updated is determined by a particular `TurnOrder`. The default
1515
behavior is to just increment it in a round-robin fashion.
1616
`currentPlayer` is just `playerOrder[playOrderPos]`.
1717

18-
If you need something different, you can customize this behavior
19-
by using the `turnOrder` option. This is passed inside a `flow`
20-
section of the `Game` configuration. The framework comes bundled
21-
with a few turn orders in the `TurnOrder` object, and you can
22-
even provide your own implementation.
18+
If you need something different, you have a few options:
19+
20+
#### turnOrder
21+
22+
You can customize this behavior by using the `turnOrder` option.
23+
This is passed inside a `flow` section of the `Game` configuration.
24+
The framework comes bundled with a few turn orders in the `TurnOrder`
25+
object, and you can even provide your own implementation.
2326

2427
```js
2528
import { Game, TurnOrder } from 'boardgame.io/core';
@@ -74,3 +77,25 @@ Game({
7477
7578
!> If you would like any player to play, then return `undefined` from
7679
these functions. `TurnOrder.ANY` implements this.
80+
81+
#### endTurn / endTurnIf
82+
83+
You can also specify the next player during the `endTurn` event.
84+
The `endTurn` event takes an additional argument specifying
85+
the next player. If `endTurnIf` returns a string that is a playerID,
86+
that player is made the next player (instead of following the turn
87+
order).
88+
89+
Player '3' is made the new player in both the following examples:
90+
91+
```js
92+
onClickEndTurn() {
93+
this.props.endTurn('3');
94+
}
95+
```
96+
97+
```js
98+
flow: {
99+
endTurnIf: (G, ctx) => '3',
100+
}
101+
```

src/core/flow.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,13 @@ export function Flow({
149149
* of moves (default: undefined, i.e. the turn does
150150
* not automatically end after a certain number of moves).
151151
*
152-
* @param {...object} endTurnIf - The turn automatically ends if this function
153-
* returns true (checked after each move).
154-
* (G, ctx) => boolean
152+
* @param {...object} endTurnIf - The turn automatically ends if this
153+
* returns anything other than undefined
154+
* (checked after each move).
155+
* If the return value is a playerID,
156+
* that player is the next player
157+
* (instead of following the turn order).
158+
* (G, ctx) => boolean|string
155159
*
156160
* @param {...object} endGameIf - The game automatically ends if this function
157161
* returns anything (checked after each move).
@@ -209,12 +213,11 @@ export function Flow({
209213
* // Any cleanup code to run after the phase ends.
210214
* onPhaseEnd: (G, ctx) => G,
211215
*
212-
* // The phase ends if this function returns true.
213-
* // If the return value is the name of another phase,
216+
* // The phase ends if this function returns anything other than
217+
* // undefined. If the return value is the name of another phase,
214218
* // that will be chosen as the next phase (as opposed
215219
* // to the next one in round-robin order).
216-
* // The phase can also end when the `endPhase` game event happens.
217-
* endPhaseIf: (G, ctx) => {},
220+
* endPhaseIf: (G, ctx) => boolean|string,
218221
*
219222
* Phase-specific options that override their global equivalents:
220223
*
@@ -444,7 +447,7 @@ export function FlowWithPhases({
444447
* Ends the current turn.
445448
* Passes the turn to the next turn in a round-robin fashion.
446449
*/
447-
function endTurnEvent(state) {
450+
function endTurnEvent(state, nextPlayer) {
448451
let { G, ctx } = state;
449452

450453
const conf = phaseMap[ctx.phase];
@@ -464,8 +467,19 @@ export function FlowWithPhases({
464467
}
465468

466469
// Update current player.
467-
const playOrderPos = conf.turnOrder.next(G, ctx);
468-
const currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
470+
let playOrderPos = ctx.playOrderPos;
471+
let currentPlayer = ctx.currentPlayer;
472+
if (nextPlayer === 'any') {
473+
playOrderPos = undefined;
474+
currentPlayer = nextPlayer;
475+
} else if (ctx.playOrder.includes(nextPlayer)) {
476+
playOrderPos = ctx.playOrder.indexOf(nextPlayer);
477+
currentPlayer = nextPlayer;
478+
} else {
479+
playOrderPos = conf.turnOrder.next(G, ctx);
480+
currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
481+
}
482+
469483
const actionPlayers = [currentPlayer];
470484
// Update turn.
471485
const turn = ctx.turn + 1;
@@ -510,10 +524,14 @@ export function FlowWithPhases({
510524

511525
const gameover = conf.endGameIf(state.G, state.ctx);
512526

513-
// End the turn automatically if endTurnIf is true or if endGameIf returns.
527+
// End the turn automatically if endTurnIf is true or if endGameIf returns.
514528
const endTurn = shouldEndTurn(state);
515529
if (endTurn || gameover !== undefined) {
516-
state = dispatch(state, { type: 'endTurn', playerID: action.playerID });
530+
state = dispatch(state, {
531+
type: 'endTurn',
532+
args: [endTurn],
533+
playerID: action.playerID,
534+
});
517535
}
518536

519537
// End the phase automatically if endPhaseIf is true.
@@ -582,7 +600,7 @@ export function FlowWithPhases({
582600
turn: 0,
583601
currentPlayer: '0',
584602
currentPlayerMoves: 0,
585-
playOrder: Array.from(Array(numPlayers), (d, i) => i),
603+
playOrder: Array.from(Array(numPlayers), (d, i) => i + ''),
586604
playOrderPos: 0,
587605
phase: phases[0].name,
588606
}),

src/core/flow.test.js

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,6 @@ test('Flow', () => {
2626
expect(flow.optimisticUpdate()).toBe(true);
2727
});
2828

29-
test('FlowWithPhases', () => {
30-
const flow = FlowWithPhases({
31-
phases: [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }],
32-
});
33-
34-
let state = { ctx: flow.ctx(2) };
35-
expect(state.ctx.turn).toBe(0);
36-
state = flow.processGameEvent(state, { type: 'endTurn' });
37-
expect(state.ctx.turn).toBe(1);
38-
39-
expect(state.ctx.phase).toBe('A');
40-
state = flow.processGameEvent(state, { type: 'endPhase' });
41-
expect(state.ctx.phase).toBe('B');
42-
state = flow.processGameEvent(state, { type: 'endPhase' });
43-
expect(state.ctx.phase).toBe('C');
44-
state = flow.processGameEvent(state, { type: 'endPhase', args: ['A'] });
45-
expect(state.ctx.phase).toBe('A');
46-
});
47-
4829
test('callbacks', () => {
4930
const onPhaseBegin = jest.fn(G => G);
5031
const onPhaseEnd = jest.fn(G => G);
@@ -530,6 +511,39 @@ test('endGame', () => {
530511
}
531512
});
532513

514+
test('endTurn / endPhase args', () => {
515+
const flow = FlowWithPhases({
516+
phases: [{ name: 'A' }, { name: 'B' }, { name: 'C' }],
517+
});
518+
519+
const state = { ctx: flow.ctx(3) };
520+
521+
{
522+
let t = state;
523+
t = flow.processGameEvent(t, gameEvent('endPhase').payload);
524+
t = flow.processGameEvent(t, gameEvent('endTurn').payload);
525+
expect(t.ctx.playOrderPos).toBe(1);
526+
expect(t.ctx.currentPlayer).toBe('1');
527+
expect(t.ctx.phase).toBe('B');
528+
}
529+
530+
{
531+
let t = state;
532+
t = flow.processGameEvent(t, gameEvent('endPhase', 'C').payload);
533+
t = flow.processGameEvent(t, gameEvent('endTurn', '2').payload);
534+
expect(t.ctx.playOrderPos).toBe(2);
535+
expect(t.ctx.currentPlayer).toBe('2');
536+
expect(t.ctx.phase).toBe('C');
537+
}
538+
539+
{
540+
let t = state;
541+
t = flow.processGameEvent(t, gameEvent('endTurn', 'any').payload);
542+
expect(t.ctx.playOrderPos).toBe(undefined);
543+
expect(t.ctx.currentPlayer).toBe('any');
544+
}
545+
});
546+
533547
test('resetGame', () => {
534548
let game = Game({
535549
moves: {

src/server/index.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ test('action', async () => {
182182
numPlayers: 2,
183183
phase: 'default',
184184
turn: 0,
185-
playOrder: [0, 1],
185+
playOrder: ['0', '1'],
186186
playOrderPos: 0,
187187
},
188188
},
@@ -196,7 +196,7 @@ test('action', async () => {
196196
numPlayers: 2,
197197
phase: 'default',
198198
turn: 0,
199-
playOrder: [0, 1],
199+
playOrder: ['0', '1'],
200200
playOrderPos: 0,
201201
},
202202
log: [],
@@ -215,7 +215,7 @@ test('action', async () => {
215215
numPlayers: 2,
216216
phase: 'default',
217217
turn: 1,
218-
playOrder: [0, 1],
218+
playOrder: ['0', '1'],
219219
playOrderPos: 1,
220220
},
221221
},
@@ -228,7 +228,7 @@ test('action', async () => {
228228
currentPlayerMoves: 0,
229229
numPlayers: 2,
230230
phase: 'default',
231-
playOrder: [0, 1],
231+
playOrder: ['0', '1'],
232232
playOrderPos: 1,
233233
turn: 1,
234234
},

0 commit comments

Comments
 (0)