Skip to content

Commit

Permalink
movesPerTurn
Browse files Browse the repository at this point in the history
A mechanism to end a turn automatically after a certain number of moves have been played.
  • Loading branch information
darthfiddler committed Jan 10, 2018
1 parent 774e540 commit 73d5b73
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docs/api/Game.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ game state and the moves. The moves are converted to a
`{ condition: (G, ctx) => boolean, action: (G, ctx) => G }`
At the end of each move, if `condition` is `true`, then the corresponding
`action` is executed.
- `flow.movesPerTurn` (*number*): Ends the turn automatically if a certain number
of moves have been made.
- `flow.phases` (*array*): Optional list of game phases. See
[Phases](/phases) for more information.

Expand Down
47 changes: 42 additions & 5 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export function Flow({ ctx, events, init, validator, endTurnIf, endGameIf, trigg
eventNames: Object.getOwnPropertyNames(events),

processMove: (state, action) => {
// Update currentPlayerMoves.
const currentPlayerMoves = state.ctx.currentPlayerMoves + 1;
state = { ...state, ctx: { ...state.ctx, currentPlayerMoves }};

// Process triggers.
for (const trigger of triggers) {
if (trigger.condition(state.G, state.ctx)) {
Expand Down Expand Up @@ -109,6 +113,10 @@ export function Flow({ ctx, events, init, validator, endTurnIf, endGameIf, trigg
* Simple game flow that just passes the turn around in
* round-robin fashion without any game phases.
*
* @param {...object} movesPerTurn - End the turn automatically after a certain number
* 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
Expand All @@ -127,11 +135,18 @@ export function Flow({ ctx, events, init, validator, endTurnIf, endGameIf, trigg
* Triggers are processed one after the other in the
* order they are defined at the end of each move.
*/
export function SimpleFlow({ endTurnIf, endGameIf, onTurnEnd, triggers }) {
export function SimpleFlow({ movesPerTurn, endTurnIf, endGameIf, onTurnEnd, triggers }) {
if (!endTurnIf) endTurnIf = () => false;
if (!endGameIf) endGameIf = () => undefined;
if (!onTurnEnd) onTurnEnd = G => G;

const endTurnIfWrap = (G, ctx) => {
if (movesPerTurn && ctx.currentPlayerMoves >= movesPerTurn) {
return true;
}
return endTurnIf(G, ctx);
};

/**
* endTurn (game event)
*
Expand All @@ -153,17 +168,18 @@ export function SimpleFlow({ endTurnIf, endGameIf, onTurnEnd, triggers }) {
// Update turn.
const turn = state.ctx.turn + 1;
// Return new state.
return { G: state.G, ctx: { ...state.ctx, currentPlayer, turn } };
return { G: state.G, ctx: { ...state.ctx, currentPlayer, turn, currentPlayerMoves: 0 } };
};

return Flow({
ctx: numPlayers => ({
numPlayers,
turn: 0,
currentPlayer: '0',
currentPlayerMoves: 0,
}),
events: { endTurn },
endTurnIf,
endTurnIf: endTurnIfWrap,
endGameIf,
triggers,
});
Expand Down Expand Up @@ -209,6 +225,9 @@ export function SimpleFlow({ endTurnIf, endGameIf, onTurnEnd, triggers }) {
* // A phase-specific endGameIf.
* endGameIf: (G, ctx) => {},
*
* // A phase-specific movesPerTurn.
* movesPerTurn: integer,
*
* // Called when `endTurn` is processed, and returns the next player.
* // If not specified, TurnOrder.DEFAULT is used.
* turnOrder: {
Expand All @@ -227,6 +246,10 @@ export function SimpleFlow({ endTurnIf, endGameIf, onTurnEnd, triggers }) {
* Global options (not associated with any phase):
* Most of these can be overriden on a per-phase basis (except triggers).
*
* @param {...object} movesPerTurn - End the turn automatically after a certain number
* 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
Expand All @@ -248,7 +271,14 @@ export function SimpleFlow({ endTurnIf, endGameIf, onTurnEnd, triggers }) {
* Triggers are processed one after the other in the
* order they are defined at the end of each move.
*/
export function FlowWithPhases({ phases, endTurnIf, endGameIf, onTurnEnd, triggers }) {
export function FlowWithPhases({
phases,
movesPerTurn,
endTurnIf,
endGameIf,
onTurnEnd,
triggers,
}) {
// Attach defaults.
if (!phases) phases = [{ name: 'default' }];
if (!endTurnIf) endTurnIf = () => false;
Expand Down Expand Up @@ -282,9 +312,15 @@ export function FlowWithPhases({ phases, endTurnIf, endGameIf, onTurnEnd, trigge

const endTurnIfWrap = (G, ctx) => {
const conf = phaseMap[ctx.phase];
if (conf.movesPerTurn && ctx.currentPlayerMoves >= conf.movesPerTurn) {
return true;
}
if (conf.endTurnIf) {
return conf.endTurnIf(G, ctx);
}
if (movesPerTurn && ctx.currentPlayerMoves >= movesPerTurn) {
return true;
}
return endTurnIf(G, ctx);
};

Expand Down Expand Up @@ -370,7 +406,7 @@ export function FlowWithPhases({ phases, endTurnIf, endGameIf, onTurnEnd, trigge
// Update turn.
const turn = ctx.turn + 1;
// Update state.
ctx = { ...ctx, currentPlayer, turn };
ctx = { ...ctx, currentPlayer, turn, currentPlayerMoves: 0 };

// End phase if condition is met.
const end = conf.endPhaseIf(G, ctx);
Expand Down Expand Up @@ -422,6 +458,7 @@ export function FlowWithPhases({ phases, endTurnIf, endGameIf, onTurnEnd, trigge
numPlayers,
turn: 0,
currentPlayer: '0',
currentPlayerMoves: 0,
phase: phases[0].name,
passMap: {},
allPassed: false,
Expand Down
44 changes: 44 additions & 0 deletions src/core/flow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,50 @@ test('callbacks', () => {
expect(onPhaseEnd).toHaveBeenCalled();
});

test('movesPerTurn', () => {
{
let flow = SimpleFlow({ movesPerTurn: 2 });
let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(1);
}

{
let flow = FlowWithPhases({ movesPerTurn: 2 });
let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(1);
}

{
let flow = FlowWithPhases({
movesPerTurn: 2,
phases: [
{ name: 'A' },
{ name: 'B', movesPerTurn: 1 },
]
});
let state = { ctx: flow.ctx(2) };
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(0);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(1);

state = flow.processGameEvent(state, { type: 'endPhase' });

expect(state.ctx.turn).toBe(1);
state = flow.processMove(state, { move: {} });
expect(state.ctx.turn).toBe(2);
}
});

test('onTurnEnd', () => {
{
const onTurnEnd = jest.fn(G => G);
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 @@ -104,12 +104,12 @@ test('action', () => {
expect(io.socket.emit).lastCalledWith(
'sync', 'gameID', {
G: {},
ctx: {currentPlayer: '1', numPlayers: 2, turn: 1},
ctx: {currentPlayer: '1', currentPlayerMoves: 0, numPlayers: 2, turn: 1},
log: [{type: "GAME_EVENT", e: {type: "endTurn"}}],
_id: 1,
_initial: {
G: {}, _id: 0, _initial: {},
ctx: {currentPlayer: '0', numPlayers: 2, turn: 0},
ctx: {currentPlayer: '0', currentPlayerMoves: 0, numPlayers: 2, turn: 0},
log: []
}
});
Expand Down Expand Up @@ -146,12 +146,12 @@ test('playerView', () => {
io.socket.receive('sync', 'gameID', 0);
expect(io.socket.emit).lastCalledWith('sync', 'gameID', {
G: {player: 0},
ctx: {currentPlayer: '0', numPlayers: 2, turn: 0},
ctx: {currentPlayer: '0', currentPlayerMoves: 0, numPlayers: 2, turn: 0},
log: [],
_id: 0,
_initial: {
G: {}, _id: 0, _initial: {},
ctx: {currentPlayer: '0', numPlayers: 2, turn: 0},
ctx: {currentPlayer: '0', currentPlayerMoves: 0, numPlayers: 2, turn: 0},
log: []
}
});
Expand Down

0 comments on commit 73d5b73

Please sign in to comment.