diff --git a/examples/react-web/src/turnorder/simulator.js b/examples/react-web/src/turnorder/simulator.js index e4e632d40..f0f562877 100644 --- a/examples/react-web/src/turnorder/simulator.js +++ b/examples/react-web/src/turnorder/simulator.js @@ -43,8 +43,8 @@ class Board extends React.Component { let current = false; let onClick = () => {}; - if (this.props.ctx.stage) { - if (this.props.playerID in this.props.ctx.stage) { + if (this.props.ctx.activePlayers) { + if (this.props.playerID in this.props.ctx.activePlayers) { className += ' active'; active = true; } diff --git a/packages/core.js b/packages/core.js index a1d02b49e..441ba4f17 100644 --- a/packages/core.js +++ b/packages/core.js @@ -7,7 +7,7 @@ */ import { INVALID_MOVE } from '../src/core/reducer.js'; -import { Pass, Stage, TurnOrder } from '../src/core/turn-order.js'; +import { Pass, ActivePlayers, TurnOrder } from '../src/core/turn-order.js'; import { PlayerView } from '../src/core/player-view.js'; -export { Stage, TurnOrder, Pass, PlayerView, INVALID_MOVE }; +export { ActivePlayers, TurnOrder, Pass, PlayerView, INVALID_MOVE }; diff --git a/src/ai/bot.js b/src/ai/bot.js index 9449f7fbf..1c8e1dac6 100644 --- a/src/ai/bot.js +++ b/src/ai/bot.js @@ -25,8 +25,8 @@ export function Simulate({ game, bots, state, depth }) { let iter = 0; while (state.ctx.gameover === undefined && iter < depth) { let playerID = state.ctx.currentPlayer; - if (state.ctx.stage) { - playerID = Object.keys(state.ctx.stage)[0]; + if (state.ctx.activePlayers) { + playerID = Object.keys(state.ctx.activePlayers)[0]; } const bot = bots instanceof Bot ? bots : bots[playerID]; @@ -128,8 +128,8 @@ export class MCTSBot extends Bot { if (playerID !== undefined) { actions = this.enumerate(G, ctx, playerID); objectives = this.objectives(G, ctx, playerID); - } else if (ctx.stage) { - for (let playerID in ctx.stage) { + } else if (ctx.activePlayers) { + for (let playerID in ctx.activePlayers) { actions = actions.concat(this.enumerate(G, ctx, playerID)); objectives = objectives.concat(this.objectives(G, ctx, playerID)); } @@ -218,8 +218,8 @@ export class MCTSBot extends Bot { ) { const { G, ctx } = state; let playerID = ctx.currentPlayer; - if (ctx.stage) { - playerID = Object.keys(ctx.stage)[0]; + if (ctx.activePlayers) { + playerID = Object.keys(ctx.activePlayers)[0]; } const moves = this.enumerate(G, ctx, playerID); diff --git a/src/client/client.js b/src/client/client.js index aeb9e1ce2..09b071cf8 100644 --- a/src/client/client.js +++ b/src/client/client.js @@ -114,8 +114,8 @@ class _ClientImpl { const state = this.store.getState(); let playerID = state.ctx.currentPlayer; - if (state.ctx.stage) { - playerID = Object.keys(state.ctx.stage)[0]; + if (state.ctx.activePlayers) { + playerID = Object.keys(state.ctx.activePlayers)[0]; } const { action, metadata } = await bot.play(state, playerID); diff --git a/src/client/client.test.js b/src/client/client.test.js index 4ff0f8c5f..d9be0812f 100644 --- a/src/client/client.test.js +++ b/src/client/client.test.js @@ -310,7 +310,7 @@ describe('event dispatchers', () => { test('default', () => { const game = {}; const client = Client({ game }); - expect(Object.keys(client.events)).toEqual(['endTurn', 'setStage']); + expect(Object.keys(client.events)).toEqual(['endTurn', 'setActivePlayers']); expect(client.getState().ctx.turn).toBe(1); client.events.endTurn(); expect(client.getState().ctx.turn).toBe(2); @@ -328,7 +328,7 @@ describe('event dispatchers', () => { 'endTurn', 'endPhase', 'endGame', - 'setStage', + 'setActivePlayers', ]); expect(client.getState().ctx.turn).toBe(1); client.events.endTurn(); @@ -340,7 +340,7 @@ describe('event dispatchers', () => { events: { endPhase: false, endTurn: false, - setStage: false, + setActivePlayers: false, }, }; const client = Client({ game }); diff --git a/src/core/flow.js b/src/core/flow.js index 07b5cf06b..79cc26353 100644 --- a/src/core/flow.js +++ b/src/core/flow.js @@ -7,8 +7,8 @@ */ import { - SetStageEvent, - SetStage, + SetActivePlayersEvent, + SetActivePlayers, InitTurnOrderState, UpdateTurnOrderState, TurnOrder, @@ -87,8 +87,8 @@ export function FlowInternal({ canPlayerCallEvent: (_G, ctx, playerID) => { const isCurrentPlayer = ctx.currentPlayer == playerID; - if (ctx.stage) { - return isCurrentPlayer && ctx.currentPlayer in ctx.stage; + if (ctx.activePlayers) { + return isCurrentPlayer && ctx.currentPlayer in ctx.activePlayers; } return isCurrentPlayer; }, @@ -101,7 +101,7 @@ export function FlowInternal({ return false; } - if (ctx.stage === null && ctx.currentPlayer !== playerID) { + if (ctx.activePlayers === null && ctx.currentPlayer !== playerID) { return false; } @@ -109,8 +109,8 @@ export function FlowInternal({ }, canPlayerMakeAnyMove: (_G, ctx, playerID) => { - if (ctx.stage) { - return playerID in ctx.stage; + if (ctx.activePlayers) { + return playerID in ctx.activePlayers; } return ctx.currentPlayer === playerID; }, @@ -198,8 +198,8 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { if (events === undefined) { events = {}; } - if (events.setStage === undefined) { - events.setStage = true; + if (events.setActivePlayers === undefined) { + events.setActivePlayers = true; } if (events.endPhase === undefined && phases) { events.endPhase = true; @@ -416,9 +416,9 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { // Initialize the turn order state. if (currentPlayer) { - ctx = { ...ctx, currentPlayer }; - if (conf.turn.setStage) { - ctx = SetStage(ctx, conf.turn.setStage); + ctx = { ...ctx, currentPlayer, activePlayersDone: null }; + if (conf.turn.activePlayers) { + ctx = SetActivePlayers(ctx, conf.turn.activePlayers); } } else { // This is only called at the beginning of the phase @@ -611,8 +611,8 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { next.push({ fn: UpdateTurn, arg, currentPlayer: ctx.currentPlayer }); } - // Reset currentPlayer and stages. - ctx = { ...ctx, currentPlayer: null, stage: null }; + // Reset currentPlayer and activePlayers. + ctx = { ...ctx, currentPlayer: null, activePlayers: null }; // Add log entry. const action = gameEvent('endTurn', arg); @@ -635,7 +635,7 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { /** * Retrieves the relevant move that can be played by playerID. * - * If ctx.stage is set (i.e. one or more players are in some stage), + * If ctx.activePlayers is set (i.e. one or more players are in some stage), * then it attempts to find the move inside the stages config for * that turn. If the stage for a player is '', then the player is * allowed to make a move (as determined by the phase config), but @@ -652,10 +652,11 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { function GetMove(ctx, name, playerID) { const conf = GetPhase(ctx); const stages = conf.turn.stages; + const { activePlayers } = ctx; - if (ctx.stage && stages[ctx.stage[playerID]]) { + if (activePlayers && stages[activePlayers[playerID]]) { // Check if moves are defined for the player's stage. - const stage = stages[ctx.stage[playerID]]; + const stage = stages[activePlayers[playerID]]; if (stage) { const moves = stage.moves; if (name in moves) { @@ -678,18 +679,21 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { function ProcessMove(state, action) { let conf = GetPhase(state.ctx); - let stage = state.ctx.stage; - if (state.ctx._stageOnce) { + let { activePlayers, _activePlayersOnce, activePlayersDone } = state.ctx; + + if (_activePlayersOnce) { const playerID = action.playerID; - stage = Object.keys(stage) + activePlayers = Object.keys(activePlayers) .filter(id => id !== playerID) .reduce((obj, key) => { - obj[key] = stage[key]; + obj[key] = activePlayers[key]; return obj; }, {}); - if (Object.keys(stage).length == 0) { - stage = null; + if (Object.keys(activePlayers).length == 0) { + activePlayers = null; + _activePlayersOnce = false; + activePlayersDone = true; } } @@ -702,7 +706,9 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { ...state, ctx: { ...state.ctx, - stage, + activePlayers, + activePlayersDone, + _activePlayersOnce, numMoves, }, }; @@ -749,7 +755,7 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { endTurn: EndTurnEvent, endPhase: EndPhaseEvent, endGame: EndGameEvent, - setStage: SetStageEvent, + setActivePlayers: SetActivePlayersEvent, }; let enabledEvents = {}; @@ -762,8 +768,8 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { if (events.endGame) { enabledEvents['endGame'] = true; } - if (events.setStage) { - enabledEvents['setStage'] = true; + if (events.setActivePlayers) { + enabledEvents['setActivePlayers'] = true; } return FlowInternal({ @@ -774,7 +780,7 @@ export function Flow({ moves, phases, endIf, turn, events, plugins }) { playOrder: [...new Array(numPlayers)].map((d, i) => i + ''), playOrderPos: 0, phase: startingPhase, - stage: null, + activePlayers: null, }), init: state => { return ProcessEvents(state, [{ fn: StartGame }]); diff --git a/src/core/flow.test.js b/src/core/flow.test.js index 750d81e62..e8c559eff 100644 --- a/src/core/flow.test.js +++ b/src/core/flow.test.js @@ -437,17 +437,19 @@ test('canPlayerMakeAnyMove', () => { let flow = FlowInternal({}); expect(flow.canPlayerMakeAnyMove({}, {}, playerID)).toBe(false); - expect(flow.canPlayerMakeAnyMove({}, { stage: { '1': '' } }, playerID)).toBe( - false - ); - expect(flow.canPlayerMakeAnyMove({}, { stage: { '0': '' } }, playerID)).toBe( - true - ); + expect( + flow.canPlayerMakeAnyMove({}, { activePlayers: { '1': '' } }, playerID) + ).toBe(false); + expect( + flow.canPlayerMakeAnyMove({}, { activePlayers: { '0': '' } }, playerID) + ).toBe(true); // no one can make a move flow = FlowInternal({ canPlayerMakeAnyMove: () => false }); expect(flow.canPlayerMakeAnyMove({}, {}, playerID)).toBe(false); - expect(flow.canPlayerMakeAnyMove({}, { stage: null }, playerID)).toBe(false); + expect(flow.canPlayerMakeAnyMove({}, { activePlayers: null }, playerID)).toBe( + false + ); expect(flow.canPlayerMakeAnyMove({}, {}, '5')).toBe(false); }); @@ -459,7 +461,7 @@ test('canPlayerCallEvent', () => { expect( flow.canPlayerCallEvent( {}, - { currentPlayer: '0', stage: { '1': '' } }, + { currentPlayer: '0', activePlayers: { '1': '' } }, playerID ) ).toBe(false); @@ -662,12 +664,12 @@ describe('infinite loops', () => { }); }); -describe('setStage', () => { - test('sets stages at each turn', () => { +describe('activePlayers', () => { + test('sets activePlayers at each turn', () => { const game = { turn: { stages: { A: {}, B: {} }, - setStage: { + activePlayers: { currentPlayer: 'A', others: 'B', }, @@ -677,7 +679,7 @@ describe('setStage', () => { const client = Client({ game, numPlayers: 3 }); expect(client.getState().ctx.currentPlayer).toBe('0'); - expect(client.getState().ctx.stage).toEqual({ + expect(client.getState().ctx.activePlayers).toEqual({ '0': 'A', '1': 'B', '2': 'B', @@ -686,10 +688,70 @@ describe('setStage', () => { client.events.endTurn(); expect(client.getState().ctx.currentPlayer).toBe('1'); - expect(client.getState().ctx.stage).toEqual({ + expect(client.getState().ctx.activePlayers).toEqual({ '0': 'B', '1': 'A', '2': 'B', }); }); + + test('activePlayersDone', () => { + const spec = { + numPlayers: 3, + multiplayer: { local: true }, + game: { + moves: { + moveA: (G, ctx) => { + ctx.events.setActivePlayers({ all: '', once: true }); + }, + moveB: G => G, + }, + }, + }; + + const p0 = Client({ ...spec, playerID: '0' }); + const p1 = Client({ ...spec, playerID: '1' }); + const p2 = Client({ ...spec, playerID: '2' }); + + p0.connect(); + p1.connect(); + p2.connect(); + + expect(p0.getState().ctx.currentPlayer).toBe('0'); + + expect(p0.getState().ctx.activePlayersDone).toBe(null); + p0.moves.moveA(); + expect(p0.getState().ctx.activePlayersDone).toBe(false); + + expect(p0.getState().ctx.activePlayers).toEqual({ + '0': '', + '1': '', + '2': '', + }); + + p0.moves.moveB(); + + expect(p0.getState().ctx.activePlayersDone).toBe(false); + expect(p0.getState().ctx.activePlayers).toEqual({ + '1': '', + '2': '', + }); + + p1.moves.moveB(); + + expect(p0.getState().ctx.activePlayersDone).toBe(false); + expect(p0.getState().ctx.activePlayers).toEqual({ + '2': '', + }); + + p2.moves.moveB(); + + expect(p0.getState().ctx.activePlayersDone).toBe(true); + expect(p0.getState().ctx.activePlayers).toEqual(null); + + p0.events.endTurn(); + + expect(p0.getState().ctx.activePlayersDone).toBe(null); + expect(p0.getState().ctx.activePlayers).toEqual(null); + }); }); diff --git a/src/core/turn-order.js b/src/core/turn-order.js index b650b3583..11654e808 100644 --- a/src/core/turn-order.js +++ b/src/core/turn-order.js @@ -30,31 +30,32 @@ export const Pass = (G, ctx) => { }; /** - * Event to change the stages of different players in the current turn. + * Event to change the active players (and their stages) in the current turn. * @param {*} state * @param {*} arg */ -export function SetStageEvent(state, arg) { - return { ...state, ctx: SetStage(state.ctx, arg) }; +export function SetActivePlayersEvent(state, arg) { + return { ...state, ctx: SetActivePlayers(state.ctx, arg) }; } -export function SetStage(ctx, arg) { - let stage = ctx.stage || {}; - let _stageOnce = false; +export function SetActivePlayers(ctx, arg) { + let activePlayers = ctx.activePlayers || {}; + let activePlayersDone = null; + let _activePlayersOnce = false; if (arg.value) { - stage = arg.value; + activePlayers = arg.value; } if (arg.currentPlayer !== undefined) { - stage[ctx.currentPlayer] = arg.currentPlayer; + activePlayers[ctx.currentPlayer] = arg.currentPlayer; } if (arg.others !== undefined) { for (let i = 0; i < ctx.playOrder.length; i++) { const playerID = ctx.playOrder[i]; if (playerID !== ctx.currentPlayer) { - stage[playerID] = arg.others; + activePlayers[playerID] = arg.others; } } } @@ -62,19 +63,28 @@ export function SetStage(ctx, arg) { if (arg.all !== undefined) { for (let i = 0; i < ctx.playOrder.length; i++) { const playerID = ctx.playOrder[i]; - stage[playerID] = arg.all; + activePlayers[playerID] = arg.all; } } if (arg.once) { - _stageOnce = true; + _activePlayersOnce = true; } - if (Object.keys(stage).length == 0) { - stage = null; + if (Object.keys(activePlayers).length == 0) { + activePlayers = null; } - return { ...ctx, stage, _stageOnce }; + if (arg.once && Object.keys(activePlayers).length > 0) { + activePlayersDone = false; + } + + return { + ...ctx, + activePlayers, + activePlayersDone, + _activePlayersOnce, + }; } /** @@ -110,7 +120,7 @@ export function InitTurnOrderState(G, ctx, turn) { const currentPlayer = getCurrentPlayer(playOrder, playOrderPos); ctx = { ...ctx, currentPlayer, playOrderPos, playOrder }; - ctx = SetStage(ctx, turn.setStage || {}); + ctx = SetActivePlayers(ctx, turn.activePlayers || {}); return ctx; } @@ -245,23 +255,23 @@ export const TurnOrder = { }, }; -export const Stage = { +export const ActivePlayers = { /** - * ANY + * ALL * * The turn stays with one player, but any player can play (in any order) * until the phase ends. */ - ANY: { all: '' }, + ALL: { all: '' }, /** - * ANY_ONCE + * ALL_ONCE * * The turn stays with one player, but any player can play (once, and in any order). * This is typically used in a phase where you want to elicit a response * from every player in the game. */ - ANY_ONCE: { all: '', once: true }, + ALL_ONCE: { all: '', once: true }, /** * OTHERS diff --git a/src/core/turn-order.test.js b/src/core/turn-order.test.js index 41fe06435..26b43be4c 100644 --- a/src/core/turn-order.test.js +++ b/src/core/turn-order.test.js @@ -7,7 +7,12 @@ */ import { Flow } from './flow'; -import { UpdateTurnOrderState, TurnOrder, Stage, Pass } from './turn-order'; +import { + UpdateTurnOrderState, + TurnOrder, + ActivePlayers, + Pass, +} from './turn-order'; import { makeMove, gameEvent } from './action-creators'; import { CreateGameReducer } from './reducer'; import { InitializeGame } from './initialize'; @@ -77,25 +82,27 @@ describe('turn orders', () => { expect(state.ctx.phase).toBe('B'); }); - test('ANY', () => { + test('ALL', () => { const flow = Flow({ - turn: { setStage: Stage.ANY }, + turn: { activePlayers: ActivePlayers.ALL }, }); let state = { ctx: flow.ctx(2) }; state = flow.init(state); expect(state.ctx.currentPlayer).toBe('0'); - expect(state.ctx.stage).toEqual({ '0': '', '1': '' }); + expect(state.ctx.activePlayers).toEqual({ '0': '', '1': '' }); expect(state.ctx).not.toHaveUndefinedProperties(); state = flow.processGameEvent(state, gameEvent('endTurn')); expect(state.ctx.currentPlayer).toBe('1'); - expect(state.ctx.stage).toEqual({ '0': '', '1': '' }); + expect(state.ctx.activePlayers).toEqual({ '0': '', '1': '' }); }); - test('ANY_ONCE', () => { + test('ALL_ONCE', () => { const flow = Flow({ - phases: { A: { start: true, turn: { setStage: Stage.ANY_ONCE } } }, + phases: { + A: { start: true, turn: { activePlayers: ActivePlayers.ALL_ONCE } }, + }, }); let state = { ctx: flow.ctx(2) }; @@ -103,46 +110,48 @@ describe('turn orders', () => { expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('0'); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '1']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']); expect(state.ctx).not.toHaveUndefinedProperties(); state = flow.processGameEvent(state, gameEvent('endTurn')); expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('1'); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '1']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']); state = flow.processMove(state, makeMove('', null, '0').payload); expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('1'); - expect(Object.keys(state.ctx.stage)).toEqual(['1']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['1']); state = flow.processMove(state, makeMove('', null, '1').payload); expect(state.ctx.currentPlayer).toBe('1'); - expect(state.ctx.stage).toBeNull(); + expect(state.ctx.activePlayers).toBeNull(); + + state = flow.processMove(state, makeMove('', null, '1').payload); }); test('OTHERS', () => { const flow = Flow({ - turn: { setStage: Stage.OTHERS }, + turn: { activePlayers: ActivePlayers.OTHERS }, }); let state = { ctx: flow.ctx(3) }; state = flow.init(state); expect(state.ctx.currentPlayer).toBe('0'); - expect(Object.keys(state.ctx.stage)).toEqual(['1', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']); expect(state.ctx).not.toHaveUndefinedProperties(); state = flow.processGameEvent(state, gameEvent('endTurn')); expect(state.ctx.currentPlayer).toBe('1'); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '2']); }); test('OTHERS_ONCE', () => { const flow = Flow({ - turn: { setStage: Stage.OTHERS_ONCE }, + turn: { activePlayers: ActivePlayers.OTHERS_ONCE }, phases: { A: { start: true } }, }); @@ -151,25 +160,27 @@ describe('turn orders', () => { expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('0'); - expect(Object.keys(state.ctx.stage)).toEqual(['1', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']); expect(state.ctx).not.toHaveUndefinedProperties(); state = flow.processGameEvent(state, gameEvent('endTurn')); expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('1'); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '2']); state = flow.processMove(state, makeMove('', null, '0').payload); expect(state.ctx.phase).toBe('A'); expect(state.ctx.currentPlayer).toBe('1'); - expect(Object.keys(state.ctx.stage)).toEqual(['2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['2']); state = flow.processMove(state, makeMove('', null, '2').payload); expect(state.ctx.currentPlayer).toBe('1'); - expect(state.ctx.stage).toBeNull(); + expect(state.ctx.activePlayers).toBeNull(); + + state = flow.processMove(state, makeMove('', null, '1').payload); }); test('CUSTOM', () => { @@ -274,16 +285,16 @@ test('passing', () => { test('end game after everyone passes', () => { const game = { endIf: G => G.allPassed, - turn: { setStage: Stage.ANY }, + turn: { activePlayers: ActivePlayers.ALL }, moves: { pass: Pass }, }; const reducer = CreateGameReducer({ game }); let state = InitializeGame({ game, numPlayers: 3 }); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '1', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1', '2']); - // Passes can be make in any order with TurnOrder.ANY. + // Passes can be made in any order with ActivePlayers.ALL. state = reducer(state, makeMove('pass', null, '1')); expect(state.ctx.gameover).toBe(undefined); @@ -347,31 +358,35 @@ test('playOrder', () => { expect(state.ctx.currentPlayer).toBe('2'); }); -describe('SetStage', () => { +describe('setActivePlayers', () => { const flow = Flow({}); const state = { ctx: flow.ctx(2) }; test('basic', () => { const newState = flow.processGameEvent( state, - gameEvent('setStage', [{ value: { '1': '' } }]) + gameEvent('setActivePlayers', [{ value: { '1': '' } }]) ); - expect(newState.ctx.stage).toMatchObject({ '1': '' }); + expect(newState.ctx.activePlayers).toMatchObject({ '1': '' }); }); test('all', () => { const newState = flow.processGameEvent( state, - gameEvent('setStage', [{ all: '' }]) + gameEvent('setActivePlayers', [{ all: '' }]) ); - expect(newState.ctx.stage).toMatchObject({ '0': '', '1': '' }); + expect(newState.ctx.activePlayers).toMatchObject({ '0': '', '1': '' }); + expect(newState.ctx.activePlayersDone).toBeNull(); }); test('once', () => { const game = { moves: { B: (G, ctx) => { - ctx.events.setStage({ value: { '0': '', '1': '' }, once: true }); + ctx.events.setActivePlayers({ + value: { '0': '', '1': '' }, + once: true, + }); return G; }, A: G => G, @@ -382,18 +397,21 @@ describe('SetStage', () => { let state = InitializeGame({ game }); state = reducer(state, makeMove('B', null, '0')); - expect(Object.keys(state.ctx.stage)).toEqual(['0', '1']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['0', '1']); + expect(state.ctx.activePlayersDone).toBe(false); state = reducer(state, makeMove('A', null, '0')); - expect(Object.keys(state.ctx.stage)).toEqual(['1']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['1']); + expect(state.ctx.activePlayersDone).toBe(false); state = reducer(state, makeMove('A', null, '1')); - expect(state.ctx.stage).toBeNull(); + expect(state.ctx.activePlayers).toBeNull(); + expect(state.ctx.activePlayersDone).toBe(true); }); test('others', () => { const game = { moves: { B: (G, ctx) => { - ctx.events.setStage({ + ctx.events.setActivePlayers({ once: true, others: '', }); @@ -409,15 +427,15 @@ describe('SetStage', () => { // on move B, control switches from player 0 to players 1 and 2 state = reducer(state, makeMove('B', null, '0')); - expect(Object.keys(state.ctx.stage)).toEqual(['1', '2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['1', '2']); // player 1 makes move state = reducer(state, makeMove('A', null, '1')); - expect(Object.keys(state.ctx.stage)).toEqual(['2']); + expect(Object.keys(state.ctx.activePlayers)).toEqual(['2']); // player 2 makes move state = reducer(state, makeMove('A', null, '2')); - expect(state.ctx.stage).toBeNull(); + expect(state.ctx.activePlayers).toBeNull(); }); describe('militia', () => { @@ -426,13 +444,13 @@ describe('SetStage', () => { beforeAll(() => { const game = { turn: { - setStage: { currentPlayer: 'A' }, + activePlayers: { currentPlayer: 'A' }, stages: { A: { moves: { militia: (G, ctx) => { - ctx.events.setStage({ + ctx.events.setActivePlayers({ currentPlayer: '', others: 'B', once: true, @@ -459,7 +477,7 @@ describe('SetStage', () => { }); test('sanity', () => { - expect(state.ctx.stage).toEqual({ '0': 'A' }); + expect(state.ctx.activePlayers).toEqual({ '0': 'A' }); }); test('player 1 cannot play the militia card', () => { @@ -489,7 +507,7 @@ describe('SetStage', () => { test('player 0 plays militia', () => { state = reducer(state, makeMove('militia', undefined, '0')); - expect(state.ctx.stage).toEqual({ + expect(state.ctx.activePlayers).toEqual({ '0': '', '1': 'B', '2': 'B', @@ -502,9 +520,9 @@ describe('SetStage', () => { test('everyone else discards', () => { state = reducer(state, makeMove('discard', undefined, '1')); - expect(state.ctx.stage).toEqual({ '0': '', '2': 'B' }); + expect(state.ctx.activePlayers).toEqual({ '0': '', '2': 'B' }); state = reducer(state, makeMove('discard', undefined, '2')); - expect(state.ctx.stage).toEqual({ '0': '' }); + expect(state.ctx.activePlayers).toEqual({ '0': '' }); }); }); }); diff --git a/src/master/master.js b/src/master/master.js index 9a7587318..c8bca30b1 100644 --- a/src/master/master.js +++ b/src/master/master.js @@ -165,7 +165,10 @@ export class Master { // that can make moves right now and the person doing the // action is that player. if (action.type == UNDO || action.type == REDO) { - if (state.ctx.currentPlayer !== playerID || state.ctx.stage !== null) { + if ( + state.ctx.currentPlayer !== playerID || + state.ctx.activePlayers !== null + ) { logging.error(`playerID=[${playerID}] cannot undo / redo right now`); return; }