Skip to content

Commit

Permalink
Prep for GameLog stuff: Introduce ContextEnhancer (#261)
Browse files Browse the repository at this point in the history
* make random.update take and return state to make it consistent with events API

* centralize attach/detach APIs from a context

* use ApiContext when processing events

* use ApiContext while processing moves also

* dissolve update() method specialized to the init code

* incorporate review comments

* introduce updateAndDetach since both operations come directly after another

* allow to disable event update

* bring branch coverage back to 100% by removing default value
  • Loading branch information
Stefan-Hanke authored and nicolodavis committed Aug 24, 2018
1 parent 3999db3 commit 510977f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 54 deletions.
5 changes: 3 additions & 2 deletions src/core/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ export class Random {
* Updates ctx with the PRNG state.
* @param {object} ctx - The ctx object to update.
*/
update(ctx) {
return { ...ctx, _random: this.state };
update(state) {
const ctx = { ...state.ctx, _random: this.state };
return { ...state, ctx };
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/core/random.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test('attach / detach / update', () => {
}

{
const t = r.update(ctx);
const t = r.update({ ctx }).ctx;
expect(t._random).toBeDefined();
expect(t.random).toBeDefined();
}
Expand Down
116 changes: 65 additions & 51 deletions src/core/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ import * as Actions from './action-types';
import { Random } from './random';
import { Events } from './events';

/**
* This class is used to attach/detach various utility objects
* onto a ctx, without having to manually attach/detach them
* all separately.
*/
class ContextEnhancer {
constructor(ctx, game, player) {
this.random = new Random(ctx);
this.events = new Events(game.flow, player);
}

attachToContext(ctx) {
let ctxWithAPI = this.random.attach(ctx);
ctxWithAPI = this.events.attach(ctxWithAPI);
return ctxWithAPI;
}

detachFromContext(ctx) {
let ctxWithoutAPI = Random.detach(ctx);
ctxWithoutAPI = Events.detach(ctxWithoutAPI);
return ctxWithoutAPI;
}

update(state, updateEvents) {
let newState = updateEvents ? this.events.update(state) : state;
newState = this.random.update(newState);
return newState;
}

updateAndDetach(state, updateEvents) {
const newState = this.update(state, updateEvents);
newState.ctx = this.detachFromContext(newState.ctx);
return newState;
}
}

/**
* CreateGameReducer
*
Expand All @@ -32,8 +68,8 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {
}
ctx._random = { seed };

const random = new Random(ctx);
let ctxWithAPI = random.attach(ctx);
const apiCtx = new ContextEnhancer(ctx, game, ctx.currentPlayer);
let ctxWithAPI = apiCtx.attachToContext(ctx);

const initial = {
// User managed state.
Expand All @@ -58,18 +94,12 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {
_initial: {},
};

const events = new Events(game.flow, ctx.currentPlayer);
ctxWithAPI = events.attach(ctxWithAPI);

const state = game.flow.init({ G: initial.G, ctx: ctxWithAPI });
let state = game.flow.init({ G: initial.G, ctx: ctxWithAPI });

const { ctx: ctxWithEvents } = events.update(state);
initial.G = state.G;
initial._undo = state._undo;
initial.ctx = ctxWithEvents;
initial.ctx = random.update(initial.ctx);
initial.ctx = Random.detach(initial.ctx);
initial.ctx = Events.detach(initial.ctx);
state = apiCtx.updateAndDetach(state, true);
initial.ctx = state.ctx;

const deepCopy = obj => parse(stringify(obj));
initial._initial = deepCopy(initial);
Expand Down Expand Up @@ -107,27 +137,18 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {

state = { ...state, deltalog: undefined };

// Initialize PRNG from ctx.
const random = new Random(state.ctx);
// Initialize Events API.
const events = new Events(game.flow, action.payload.playerID);
// Attach Random API to ctx.
state = { ...state, ctx: random.attach(state.ctx) };
// Attach Events API to ctx.
state = { ...state, ctx: events.attach(state.ctx) };
const apiCtx = new ContextEnhancer(
state.ctx,
game,
action.payload.playerID
);
apiCtx.attachToContext(state.ctx);

// Update state.
let newState = game.flow.processGameEvent(state, action);
// Trigger any events that were called via the Events API.
newState = events.update(newState);
// Update ctx with PRNG state.
let ctx = random.update(newState.ctx);
// Detach Random API from ctx.
ctx = Random.detach(ctx);
// Detach Events API from ctx.
ctx = Events.detach(ctx);

return { ...newState, ctx, _stateID: state._stateID + 1 };

newState = apiCtx.updateAndDetach(newState, true);

return { ...newState, _stateID: state._stateID + 1 };
}

case Actions.MAKE_MOVE: {
Expand Down Expand Up @@ -156,14 +177,12 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {

state = { ...state, deltalog: undefined };

// Initialize PRNG from ctx.
const random = new Random(state.ctx);
// Initialize Events API.
const events = new Events(game.flow, action.payload.playerID);
// Attach Random API to ctx.
let ctxWithAPI = random.attach(state.ctx);
// Attach Events API to ctx.
ctxWithAPI = events.attach(ctxWithAPI);
const apiCtx = new ContextEnhancer(
state.ctx,
game,
action.payload.playerID
);
let ctxWithAPI = apiCtx.attachToContext(state.ctx);

// Process the move.
let G = game.processMove(state.G, action.payload, ctxWithAPI);
Expand All @@ -172,12 +191,8 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {
return state;
}

// Update ctx with PRNG state.
let ctx = random.update(state.ctx);
// Detach Random API from ctx.
ctx = Random.detach(ctx);
// Detach Events API from ctx.
ctx = Events.detach(ctx);
// don't call into events here
let ctx = apiCtx.updateAndDetach(state, false).ctx;

// Undo changes to G if the move should not run on the client.
if (
Expand All @@ -199,13 +214,12 @@ export function CreateGameReducer({ game, numPlayers, multiplayer }) {
}

// Allow the flow reducer to process any triggers that happen after moves.
state = { ...state, ctx: random.attach(state.ctx) };
state = { ...state, ctx: events.attach(state.ctx) };
state = game.flow.processMove(state, action.payload);
state = events.update(state);
state = { ...state, ctx: random.update(state.ctx) };
state = { ...state, ctx: Random.detach(state.ctx) };
state = { ...state, ctx: Events.detach(state.ctx) };
ctxWithAPI = apiCtx.attachToContext(state.ctx);
state = game.flow.processMove(
{ ...state, ctx: ctxWithAPI },
action.payload
);
state = apiCtx.updateAndDetach(state, true);

return state;
}
Expand Down

0 comments on commit 510977f

Please sign in to comment.