Skip to content

Commit

Permalink
Merge 957e385 into b312c28
Browse files Browse the repository at this point in the history
  • Loading branch information
senritsu committed Aug 22, 2021
2 parents b312c28 + 957e385 commit a6e4f85
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 22 deletions.
187 changes: 180 additions & 7 deletions src/core/flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,51 @@ describe('turn', () => {
}
});

describe('moveLimit', () => {
describe('minMoves', () => {
test('without phases', () => {
const flow = Flow({
turn: {
moveLimit: 2,
minMoves: 2,
},
});

let state = flow.init({ ctx: flow.ctx(2) } as State);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 0 could not end their turn because minMoves have not been made yet

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

// player 0 can now end their turn, having made another move to reach minMoves total

state = flow.processEvent(state, gameEvent('endTurn'));

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');
});

test('with phases', () => {
const flow = Flow({
turn: { moveLimit: 2 },
turn: { minMoves: 2 },
phases: {
B: {
turn: {
moveLimit: 1,
minMoves: 1,
},
},
},
Expand All @@ -281,23 +302,133 @@ describe('turn', () => {

state = flow.processEvent(state, gameEvent('endTurn'));

// player 0 could not end their turn because minMoves have not been made yet

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processEvent(state, gameEvent('endTurn'));

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processEvent(state, gameEvent('setPhase', 'B'));

expect(state.ctx.turn).toBe(3);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 0 could not end their turn because minMoves have not been made yet

expect(state.ctx.turn).toBe(3);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(3);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 0 could end their turn after only one move because minMoves in phase B is only 1

expect(state.ctx.turn).toBe(4);
expect(state.ctx.currentPlayer).toBe('1');
});
});

describe('moveLimit', () => {
test('without phases', () => {
const flow = Flow({
turn: {
moveLimit: 2,
},
});
let state = flow.init({ ctx: flow.ctx(2) } as State);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

// player 0 reached moveLimit, turn ended automatically

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 1 ended their turn manually, having made less moves than the moveLimit would allow

expect(state.ctx.turn).toBe(3);
expect(state.ctx.currentPlayer).toBe('0');
});

test('with phases', () => {
const flow = Flow({
turn: { moveLimit: 2 },
phases: {
B: {
turn: {
moveLimit: 1,
},
},
},
});
let state = flow.init({ ctx: flow.ctx(2) } as State);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(1);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 0 ended their turn manually, having made less moves than the moveLimit would allow

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(2);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processEvent(state, gameEvent('setPhase', 'B'));

// player 1 still had moves left, but the phase change automatically ended their turn

expect(state.ctx.phase).toBe('B');
expect(state.ctx.turn).toBe(3);
expect(state.ctx.currentPlayer).toBe('0');

state = flow.processMove(state, makeMove('move', null, '0').payload);

expect(state.ctx.turn).toBe(4);
expect(state.ctx.currentPlayer).toBe('1');

state = flow.processEvent(state, gameEvent('endTurn'));

// player 1 ended their turn without using any move

expect(state.ctx.turn).toBe(5);
expect(state.ctx.currentPlayer).toBe('0');
});

test('with noLimit moves', () => {
Expand Down Expand Up @@ -569,7 +700,7 @@ describe('stage events', () => {
test('with multiple active players', () => {
const flow = Flow({
turn: {
activePlayers: { all: 'A', moveLimit: 5 },
activePlayers: { all: 'A', minMoves: 2, moveLimit: 5 },
},
});
let state = { G: {}, ctx: flow.ctx(3) } as State;
Expand All @@ -578,7 +709,7 @@ describe('stage events', () => {
expect(state.ctx.activePlayers).toEqual({ '0': 'A', '1': 'A', '2': 'A' });
state = flow.processEvent(
state,
gameEvent('setStage', { stage: 'B', moveLimit: 1 })
gameEvent('setStage', { stage: 'B', minMoves: 1 })
);
expect(state.ctx.activePlayers).toEqual({ '0': 'B', '1': 'A', '2': 'A' });

Expand Down Expand Up @@ -606,6 +737,19 @@ describe('stage events', () => {
expect(state.ctx._activePlayersNumMoves).toMatchObject({ '0': 0 });
});

test('with min moves', () => {
const flow = Flow({});
let state = { G: {}, ctx: flow.ctx(2) } as State;
state = flow.init(state);

expect(state.ctx._activePlayersMinMoves).toBeNull();
state = flow.processEvent(
state,
gameEvent('setStage', { stage: 'A', minMoves: 1 })
);
expect(state.ctx._activePlayersMinMoves).toEqual({ '0': 1 });
});

test('with move limit', () => {
const flow = Flow({});
let state = { G: {}, ctx: flow.ctx(2) } as State;
Expand Down Expand Up @@ -727,6 +871,35 @@ describe('stage events', () => {
expect(state.ctx.activePlayers).toEqual({ '1': 'A', '2': 'A' });
});

test('with min moves', () => {
const flow = Flow({
turn: {
activePlayers: { all: 'A', minMoves: 2 },
},
});
let state = { G: {}, ctx: flow.ctx(2) } as State;
state = flow.init(state);

expect(state.ctx.activePlayers).toEqual({ '0': 'A', '1': 'A' });

state = flow.processEvent(state, gameEvent('endStage'));

// player 0 is not allowed to end the stage, they haven't made any move yet
expect(state.ctx.activePlayers).toEqual({ '0': 'A', '1': 'A' });

state = flow.processMove(state, makeMove('move', null, '0').payload);
state = flow.processEvent(state, gameEvent('endStage'));

// player 0 is still not allowed to end the stage, they haven't made the minimum number of moves
expect(state.ctx.activePlayers).toEqual({ '0': 'A', '1': 'A' });

state = flow.processMove(state, makeMove('move', null, '0').payload);
state = flow.processEvent(state, gameEvent('endStage'));

// having made 2 moves, player 0 was allowed to end the stage
expect(state.ctx.activePlayers).toEqual({ '1': 'A' });
});

test('maintains move count', () => {
const flow = Flow({
moves: { A: () => {} },
Expand Down Expand Up @@ -1805,7 +1978,7 @@ describe('hook execution order', () => {
calls.push('moves.endStage');
},
setActivePlayers: (G, ctx) => {
ctx.events.setActivePlayers({ all: 'A', moveLimit: 1 });
ctx.events.setActivePlayers({ all: 'A', minMoves: 1, moveLimit: 1 });
calls.push('moves.setActivePlayers');
},
},
Expand Down
56 changes: 48 additions & 8 deletions src/core/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,12 @@ export function Flow({
if (typeof arg !== 'object') return state;

let { ctx } = state;
let { activePlayers, _activePlayersMoveLimit, _activePlayersNumMoves } =
ctx;
let {
activePlayers,
_activePlayersMinMoves,
_activePlayersMoveLimit,
_activePlayersNumMoves,
} = ctx;

// Checking if stage is valid, even Stage.NULL
if (arg.stage !== undefined) {
Expand All @@ -378,6 +382,13 @@ export function Flow({
activePlayers[playerID] = arg.stage;
_activePlayersNumMoves[playerID] = 0;

if (arg.minMoves) {
if (_activePlayersMinMoves === null) {
_activePlayersMinMoves = {};
}
_activePlayersMinMoves[playerID] = arg.minMoves;
}

if (arg.moveLimit) {
if (_activePlayersMoveLimit === null) {
_activePlayersMoveLimit = {};
Expand All @@ -389,6 +400,7 @@ export function Flow({
ctx = {
...ctx,
activePlayers,
_activePlayersMinMoves,
_activePlayersMoveLimit,
_activePlayersNumMoves,
};
Expand Down Expand Up @@ -496,15 +508,15 @@ export function Flow({
const { currentPlayer, numMoves, phase, turn } = state.ctx;
const phaseConfig = GetPhase(state.ctx);

// Prevent ending the turn if moveLimit hasn't been reached.
// Prevent ending the turn if minMoves haven't been reached.
const currentPlayerMoves = numMoves || 0;
if (
!force &&
phaseConfig.turn.moveLimit &&
currentPlayerMoves < phaseConfig.turn.moveLimit
phaseConfig.turn.minMoves &&
currentPlayerMoves < phaseConfig.turn.minMoves
) {
logging.info(
`cannot end turn before making ${phaseConfig.turn.moveLimit} moves`
`cannot end turn before making ${phaseConfig.turn.minMoves} moves`
);
return state;
}
Expand Down Expand Up @@ -554,12 +566,20 @@ export function Flow({
playerID = playerID || state.ctx.currentPlayer;

let { ctx, _stateID } = state;
let { activePlayers, _activePlayersMoveLimit, phase, turn } = ctx;
let {
activePlayers,
_activePlayersNumMoves,
_activePlayersMinMoves,
_activePlayersMoveLimit,
phase,
turn,
} = ctx;

const playerInStage = activePlayers !== null && playerID in activePlayers;

const phaseConfig = GetPhase(ctx);

if (!arg && playerInStage) {
const phaseConfig = GetPhase(ctx);
const stage = phaseConfig.turn.stages[activePlayers[playerID]];
if (stage && stage.next) arg = stage.next;
}
Expand All @@ -572,10 +592,29 @@ export function Flow({
// If player isn’t in a stage, there is nothing else to do.
if (!playerInStage) return state;

// Prevent ending the stage if minMoves haven't been reached.
const currentPlayerMoves = _activePlayersNumMoves[playerID] || 0;
if (
_activePlayersMinMoves &&
_activePlayersMinMoves[playerID] &&
currentPlayerMoves < _activePlayersMinMoves[playerID]
) {
logging.info(
`cannot end stage before making ${_activePlayersMinMoves[playerID]} moves`
);
return state;
}

// Remove player from activePlayers.
activePlayers = { ...activePlayers };
delete activePlayers[playerID];

if (_activePlayersMinMoves) {
// Remove player from _activePlayersMinMoves.
_activePlayersMinMoves = { ..._activePlayersMinMoves };
delete _activePlayersMinMoves[playerID];
}

if (_activePlayersMoveLimit) {
// Remove player from _activePlayersMoveLimit.
_activePlayersMoveLimit = { ..._activePlayersMoveLimit };
Expand All @@ -585,6 +624,7 @@ export function Flow({
ctx = UpdateActivePlayersOnceEmpty({
...ctx,
activePlayers,
_activePlayersMinMoves,
_activePlayersMoveLimit,
});

Expand Down
Loading

0 comments on commit a6e4f85

Please sign in to comment.