Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prep for GameLog stuff: Introduce ContextEnhancer #261

Merged
merged 12 commits into from
Aug 24, 2018
Merged
6 changes: 4 additions & 2 deletions src/core/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ 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) {
var newCtx = { ...state.ctx, _random: this.state };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be written more concisely as:

const ctx = { ...state.ctx, _random: this.state };
return { ...state, ctx };

var newState = { ...state, ctx: newCtx };
return newState;
}

/**
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
114 changes: 65 additions & 49 deletions src/core/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ 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) {
let newState = this.events.update(state);
newState = this.random.update(newState);
return newState;
}
}

/**
* CreateGameReducer
*
Expand All @@ -32,8 +62,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 +88,17 @@ 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 });

const { ctx: ctxWithEvents } = events.update(state);
initial.G = state.G;
initial._undo = state._undo;

// TODO liked to use apiCtx.update() here also, albeit
// this code is more intricate than it looks.
const { ctx: ctxWithEvents } = apiCtx.events.update(state);
initial.ctx = ctxWithEvents;
initial.ctx = random.update(initial.ctx);
initial.ctx = Random.detach(initial.ctx);
initial.ctx = Events.detach(initial.ctx);
initial.ctx = apiCtx.random.update(initial).ctx;
initial.ctx = apiCtx.detachFromContext(initial.ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  state = apiCtx.update(state);
  initial.ctx = state.ctx;
  initial.ctx = apiCtx.detachFromContext(initial.ctx);

should work here.

A more interesting observation is given that update is always followed by a detachFromContext, shall we combine them into one call detach that accepts state and returns state?


const deepCopy = obj => parse(stringify(obj));
initial._initial = deepCopy(initial);
Expand Down Expand Up @@ -107,27 +136,19 @@ 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.update(newState);
apiCtx.detachFromContext(newState.ctx);

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,9 @@ 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 update here, just update the random state.
let ctx = apiCtx.random.update(state).ctx;
ctx = apiCtx.detachFromContext(ctx);

// Undo changes to G if the move should not run on the client.
if (
Expand All @@ -199,13 +215,13 @@ 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.update(state);
state.ctx = apiCtx.detachFromContext(state.ctx);

return state;
}
Expand Down