Skip to content

Commit

Permalink
allow setting the next player via endTurn
Browse files Browse the repository at this point in the history
  • Loading branch information
darthfiddler committed Mar 27, 2018
1 parent 278b369 commit 9b0324c
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 41 deletions.
35 changes: 30 additions & 5 deletions docs/turn-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ is updated is determined by a particular `TurnOrder`. The default
behavior is to just increment it in a round-robin fashion.
`currentPlayer` is just `playerOrder[playOrderPos]`.

If you need something different, you can customize this behavior
by using the `turnOrder` option. This is passed inside a `flow`
section of the `Game` configuration. The framework comes bundled
with a few turn orders in the `TurnOrder` object, and you can
even provide your own implementation.
If you need something different, you have a few options:

#### turnOrder

You can customize this behavior by using the `turnOrder` option.
This is passed inside a `flow` section of the `Game` configuration.
The framework comes bundled with a few turn orders in the `TurnOrder`
object, and you can even provide your own implementation.

```js
import { Game, TurnOrder } from 'boardgame.io/core';
Expand Down Expand Up @@ -74,3 +77,25 @@ Game({
!> If you would like any player to play, then return `undefined` from
these functions. `TurnOrder.ANY` implements this.
#### endTurn / endTurnIf
You can also specify the next player during the `endTurn` event.
The `endTurn` event takes an additional argument specifying
the next player. If `endTurnIf` returns a string that is a playerID,
that player is made the next player (instead of following the turn
order).
Player '3' is made the new player in both the following examples:
```js
onClickEndTurn() {
this.props.endTurn('3');
}
```
```js
flow: {
endTurnIf: (G, ctx) => '3',
}
```
44 changes: 31 additions & 13 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,13 @@ export function Flow({
* of moves (default: undefined, i.e. the turn does
* not automatically end after a certain number of moves).
*
* @param {...object} endTurnIf - The turn automatically ends if this function
* returns true (checked after each move).
* (G, ctx) => boolean
* @param {...object} endTurnIf - The turn automatically ends if this
* returns anything other than undefined
* (checked after each move).
* If the return value is a playerID,
* that player is the next player
* (instead of following the turn order).
* (G, ctx) => boolean|string
*
* @param {...object} endGameIf - The game automatically ends if this function
* returns anything (checked after each move).
Expand Down Expand Up @@ -209,12 +213,11 @@ export function Flow({
* // Any cleanup code to run after the phase ends.
* onPhaseEnd: (G, ctx) => G,
*
* // The phase ends if this function returns true.
* // If the return value is the name of another phase,
* // The phase ends if this function returns anything other than
* // undefined. If the return value is the name of another phase,
* // that will be chosen as the next phase (as opposed
* // to the next one in round-robin order).
* // The phase can also end when the `endPhase` game event happens.
* endPhaseIf: (G, ctx) => {},
* endPhaseIf: (G, ctx) => boolean|string,
*
* Phase-specific options that override their global equivalents:
*
Expand Down Expand Up @@ -444,7 +447,7 @@ export function FlowWithPhases({
* Ends the current turn.
* Passes the turn to the next turn in a round-robin fashion.
*/
function endTurnEvent(state) {
function endTurnEvent(state, nextPlayer) {
let { G, ctx } = state;

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

// Update current player.
const playOrderPos = conf.turnOrder.next(G, ctx);
const currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
let playOrderPos = ctx.playOrderPos;
let currentPlayer = ctx.currentPlayer;
if (nextPlayer === 'any') {
playOrderPos = undefined;
currentPlayer = nextPlayer;
} else if (ctx.playOrder.includes(nextPlayer)) {
playOrderPos = ctx.playOrder.indexOf(nextPlayer);
currentPlayer = nextPlayer;
} else {
playOrderPos = conf.turnOrder.next(G, ctx);
currentPlayer = getCurrentPlayer(ctx.playOrder, playOrderPos);
}

const actionPlayers = [currentPlayer];
// Update turn.
const turn = ctx.turn + 1;
Expand Down Expand Up @@ -510,10 +524,14 @@ export function FlowWithPhases({

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

// End the turn automatically if endTurnIf is true or if endGameIf returns.
// End the turn automatically if endTurnIf is true or if endGameIf returns.
const endTurn = shouldEndTurn(state);
if (endTurn || gameover !== undefined) {
state = dispatch(state, { type: 'endTurn', playerID: action.playerID });
state = dispatch(state, {
type: 'endTurn',
args: [endTurn],
playerID: action.playerID,
});
}

// End the phase automatically if endPhaseIf is true.
Expand Down Expand Up @@ -582,7 +600,7 @@ export function FlowWithPhases({
turn: 0,
currentPlayer: '0',
currentPlayerMoves: 0,
playOrder: Array.from(Array(numPlayers), (d, i) => i),
playOrder: Array.from(Array(numPlayers), (d, i) => i + ''),
playOrderPos: 0,
phase: phases[0].name,
}),
Expand Down
52 changes: 33 additions & 19 deletions src/core/flow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,6 @@ test('Flow', () => {
expect(flow.optimisticUpdate()).toBe(true);
});

test('FlowWithPhases', () => {
const flow = FlowWithPhases({
phases: [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }],
});

let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processGameEvent(state, { type: 'endTurn' });
expect(state.ctx.turn).toBe(1);

expect(state.ctx.phase).toBe('A');
state = flow.processGameEvent(state, { type: 'endPhase' });
expect(state.ctx.phase).toBe('B');
state = flow.processGameEvent(state, { type: 'endPhase' });
expect(state.ctx.phase).toBe('C');
state = flow.processGameEvent(state, { type: 'endPhase', args: ['A'] });
expect(state.ctx.phase).toBe('A');
});

test('callbacks', () => {
const onPhaseBegin = jest.fn(G => G);
const onPhaseEnd = jest.fn(G => G);
Expand Down Expand Up @@ -530,6 +511,39 @@ test('endGame', () => {
}
});

test('endTurn / endPhase args', () => {
const flow = FlowWithPhases({
phases: [{ name: 'A' }, { name: 'B' }, { name: 'C' }],
});

const state = { ctx: flow.ctx(3) };

{
let t = state;
t = flow.processGameEvent(t, gameEvent('endPhase').payload);
t = flow.processGameEvent(t, gameEvent('endTurn').payload);
expect(t.ctx.playOrderPos).toBe(1);
expect(t.ctx.currentPlayer).toBe('1');
expect(t.ctx.phase).toBe('B');
}

{
let t = state;
t = flow.processGameEvent(t, gameEvent('endPhase', 'C').payload);
t = flow.processGameEvent(t, gameEvent('endTurn', '2').payload);
expect(t.ctx.playOrderPos).toBe(2);
expect(t.ctx.currentPlayer).toBe('2');
expect(t.ctx.phase).toBe('C');
}

{
let t = state;
t = flow.processGameEvent(t, gameEvent('endTurn', 'any').payload);
expect(t.ctx.playOrderPos).toBe(undefined);
expect(t.ctx.currentPlayer).toBe('any');
}
});

test('resetGame', () => {
let game = Game({
moves: {
Expand Down
8 changes: 4 additions & 4 deletions src/server/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ test('action', async () => {
numPlayers: 2,
phase: 'default',
turn: 0,
playOrder: [0, 1],
playOrder: ['0', '1'],
playOrderPos: 0,
},
},
Expand All @@ -196,7 +196,7 @@ test('action', async () => {
numPlayers: 2,
phase: 'default',
turn: 0,
playOrder: [0, 1],
playOrder: ['0', '1'],
playOrderPos: 0,
},
log: [],
Expand All @@ -215,7 +215,7 @@ test('action', async () => {
numPlayers: 2,
phase: 'default',
turn: 1,
playOrder: [0, 1],
playOrder: ['0', '1'],
playOrderPos: 1,
},
},
Expand All @@ -228,7 +228,7 @@ test('action', async () => {
currentPlayerMoves: 0,
numPlayers: 2,
phase: 'default',
playOrder: [0, 1],
playOrder: ['0', '1'],
playOrderPos: 1,
turn: 1,
},
Expand Down

0 comments on commit 9b0324c

Please sign in to comment.