Skip to content

Commit 4ec3a61

Browse files
committed
end phase when a turn order runs out
1 parent 0124536 commit 4ec3a61

4 files changed

Lines changed: 87 additions & 24 deletions

File tree

docs/turn-order.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ A `TurnOrder` object has the following structure:
7474
}
7575
```
7676
77-
The implementation above shows the default round-robin order.
78-
If you want to skip over every other player (for example), do
79-
something like this:
77+
!> The phase ends if `next()` returns `undefined`.
78+
79+
The implementation above shows the default round-robin order that
80+
repeats indefinitely. If you want to skip over every other player (for example), do something like this:
8081
8182
```js
8283
import { Game } from 'boardgame.io/core';
@@ -95,7 +96,7 @@ Game({
9596
}
9697
```
9798
98-
You may also set `actionPlayers` from a `TurnOrder` object by
99+
!> You may also set `actionPlayers` from a `TurnOrder` object by
99100
returning an object of type `{ playOrderPos, actionPlayers }`.
100101
101102
```js

src/core/flow.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -459,8 +459,19 @@ export function FlowWithPhases({
459459
return { ...state, G, ctx: { ...ctx, gameover } };
460460
}
461461

462+
let endPhase = false;
463+
462464
// Update turn order state.
463-
ctx = UpdateTurnOrderState(G, ctx, conf.turnOrder, nextPlayer);
465+
{
466+
const { endPhase: a, ctx: b } = UpdateTurnOrderState(
467+
G,
468+
ctx,
469+
conf.turnOrder,
470+
nextPlayer
471+
);
472+
endPhase = a;
473+
ctx = b;
474+
}
464475

465476
// Update turn.
466477
const turn = ctx.turn + 1;
@@ -473,11 +484,15 @@ export function FlowWithPhases({
473484
};
474485

475486
// End phase if condition is met.
476-
const end = shouldEndPhase(state);
477-
if (end) {
487+
const endPhaseArg = shouldEndPhase(state);
488+
if (endPhaseArg) {
489+
endPhase = true;
490+
}
491+
492+
if (endPhase) {
478493
return this.dispatch(
479494
{ ...state, G, ctx },
480-
automaticGameEvent('endPhase', [end], this.playerID)
495+
automaticGameEvent('endPhase', [endPhaseArg], this.playerID)
481496
);
482497
}
483498

src/core/turn-order.js

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export function InitTurnOrderState(G, ctx, turnOrder) {
9595
export function UpdateTurnOrderState(G, ctx, turnOrder, nextPlayer) {
9696
let playOrderPos = ctx.playOrderPos;
9797
let currentPlayer = ctx.currentPlayer;
98-
let actionPlayers;
98+
let actionPlayers = ctx.actionPlayers;
99+
let endPhase = false;
99100

100101
if (ctx.playOrder.includes(nextPlayer)) {
101102
playOrderPos = ctx.playOrder.indexOf(nextPlayer);
@@ -104,27 +105,33 @@ export function UpdateTurnOrderState(G, ctx, turnOrder, nextPlayer) {
104105
} else {
105106
const t = turnOrder.next(G, ctx);
106107

107-
if (t && t.playOrderPos !== undefined) {
108-
playOrderPos = t.playOrderPos;
108+
if (t == undefined) {
109+
endPhase = true;
109110
} else {
110-
playOrderPos = t;
111-
}
111+
if (t.playOrderPos !== undefined) {
112+
playOrderPos = t.playOrderPos;
113+
} else {
114+
playOrderPos = t;
115+
}
112116

113-
currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
117+
currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
114118

115-
if (t && t.actionPlayers !== undefined) {
116-
actionPlayers = t.actionPlayers;
117-
} else {
118-
actionPlayers = [currentPlayer];
119+
if (t.actionPlayers !== undefined) {
120+
actionPlayers = t.actionPlayers;
121+
} else {
122+
actionPlayers = [currentPlayer];
123+
}
119124
}
120125
}
121126

122-
return {
127+
ctx = {
123128
...ctx,
124129
playOrderPos,
125130
currentPlayer,
126131
actionPlayers,
127132
};
133+
134+
return { endPhase, ctx };
128135
}
129136

130137
export const TurnOrder = {
@@ -147,6 +154,8 @@ export const TurnOrder = {
147154
* first / next can also return an object of type
148155
* { playOrderPos, actionPlayers }
149156
* in which case they can also set actionPlayers simultaneously.
157+
*
158+
* The phase ends if next() returns undefined.
150159
*/
151160

152161
/**
@@ -159,6 +168,21 @@ export const TurnOrder = {
159168
next: (G, ctx) => (ctx.playOrderPos + 1) % ctx.playOrder.length,
160169
},
161170

171+
/**
172+
* ONCE
173+
*
174+
* Another round-robin turn order, but goes around just once.
175+
* The phase ends after all players have played.
176+
*/
177+
ONCE: {
178+
first: () => 0,
179+
next: (G, ctx) => {
180+
if (ctx.playOrderPos < ctx.playOrder.length - 1) {
181+
return ctx.playOrderPos + 1;
182+
}
183+
},
184+
},
185+
162186
/**
163187
* ANY
164188
*

src/core/turn-order.test.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,39 @@ import { CreateGameReducer } from './reducer';
1515
describe('turnOrder', () => {
1616
test('default', () => {
1717
const flow = FlowWithPhases({
18-
phases: [{ name: 'A' }],
18+
phases: [{ name: 'A' }, { name: 'B' }],
1919
});
2020

21-
let state = { ctx: flow.ctx(10) };
21+
let state = { ctx: flow.ctx(2) };
22+
state = flow.init(state);
23+
expect(state.ctx.currentPlayer).toBe('0');
24+
expect(state.ctx.actionPlayers).toEqual(['0']);
25+
state = flow.processGameEvent(state, gameEvent('endTurn'));
26+
expect(state.ctx.currentPlayer).toBe('1');
27+
expect(state.ctx.actionPlayers).toEqual(['1']);
28+
state = flow.processGameEvent(state, gameEvent('endTurn'));
29+
expect(state.ctx.currentPlayer).toBe('0');
30+
expect(state.ctx.actionPlayers).toEqual(['0']);
31+
expect(state.ctx.phase).toBe('A');
32+
});
33+
34+
test('once', () => {
35+
const flow = FlowWithPhases({
36+
turnOrder: TurnOrder.ONCE,
37+
phases: [{ name: 'A' }, { name: 'B' }],
38+
});
39+
40+
let state = { ctx: flow.ctx(2) };
2241
state = flow.init(state);
2342
expect(state.ctx.currentPlayer).toBe('0');
2443
expect(state.ctx.actionPlayers).toEqual(['0']);
2544
state = flow.processGameEvent(state, gameEvent('endTurn'));
2645
expect(state.ctx.currentPlayer).toBe('1');
2746
expect(state.ctx.actionPlayers).toEqual(['1']);
47+
state = flow.processGameEvent(state, gameEvent('endTurn'));
48+
expect(state.ctx.currentPlayer).toBe('0');
49+
expect(state.ctx.actionPlayers).toEqual(['0']);
50+
expect(state.ctx.phase).toBe('B');
2851
});
2952

3053
test('custom', () => {
@@ -247,17 +270,17 @@ describe('UpdateTurnOrderState', () => {
247270
};
248271

249272
test('without nextPlayer', () => {
250-
const t = UpdateTurnOrderState(G, ctx, TurnOrder.DEFAULT);
273+
const { ctx: t } = UpdateTurnOrderState(G, ctx, TurnOrder.DEFAULT);
251274
expect(t).toMatchObject({ currentPlayer: '1' });
252275
});
253276

254277
test('with nextPlayer', () => {
255-
const t = UpdateTurnOrderState(G, ctx, TurnOrder.DEFAULT, '2');
278+
const { ctx: t } = UpdateTurnOrderState(G, ctx, TurnOrder.DEFAULT, '2');
256279
expect(t).toMatchObject({ currentPlayer: '2' });
257280
});
258281

259282
test('with actionPlayers', () => {
260-
const t = UpdateTurnOrderState(G, ctx, TurnOrder.ANY);
283+
const { ctx: t } = UpdateTurnOrderState(G, ctx, TurnOrder.ANY);
261284
expect(t).toMatchObject({
262285
currentPlayer: '1',
263286
actionPlayers: ['0', '1', '2'],

0 commit comments

Comments
 (0)