Skip to content

Commit

Permalink
add turn and phase to log entries
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolodavis committed May 7, 2019
1 parent d5acbda commit e46f195
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 124 deletions.
7 changes: 2 additions & 5 deletions examples/react-native/rn-cli.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ const path = require('path');

module.exports = {
getProjectRoots() {
return [
path.join(__dirname, '..', '..', 'packages'),
__dirname
];
}
return [path.join(__dirname, '..', '..', 'packages'), __dirname];
},
};
14 changes: 12 additions & 2 deletions src/client/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,18 @@ describe('log handling', () => {
client.moves.A();

expect(client.log).toEqual([
{ action: makeMove('A', [], '0'), _stateID: 0 },
{ action: makeMove('A', [], '0'), _stateID: 1 },
{
action: makeMove('A', [], '0'),
_stateID: 0,
phase: 'default',
turn: 0,
},
{
action: makeMove('A', [], '0'),
_stateID: 1,
phase: 'default',
turn: 0,
},
]);
});

Expand Down
119 changes: 58 additions & 61 deletions src/client/log/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,31 @@ export class GameLog extends React.Component {

rewind = logIndex => {
let state = this.props.initialState;
for (let i = 0; i <= logIndex; i++) {
for (let i = 0; i < this.props.log.length; i++) {
const { action } = this.props.log[i];

if (!action.automatic) {
state = this.props.reducer(state, action);
}

if (action.type == MAKE_MOVE) {
if (logIndex == 0) {
break;
}

logIndex--;
}
}
return { G: state.G, ctx: state.ctx };
};

onLogClick = logIndex => {
this.setState(o => {
const state = this.rewind(logIndex);
const metadata = this.props.log[logIndex].action.payload.metadata;
const renderedLogEntries = this.props.log.filter(
e => e.action.type == MAKE_MOVE
);
const metadata = renderedLogEntries[logIndex].action.payload.metadata;

if (o.pinned === logIndex) {
this.props.onHover({ logIndex, state, metadata: undefined });
Expand Down Expand Up @@ -180,72 +192,57 @@ export class GameLog extends React.Component {
let phases = [];
let eventsInCurrentPhase = 0;
let eventsInCurrentTurn = 0;
let state = this.props.initialState;

let lastAction = 0;
for (let i = 0; i < this.props.log.length; i++) {
const { action } = this.props.log[i];
if (action.type == MAKE_MOVE || !action.automatic) {
lastAction = i;
}
}
const renderedLogEntries = this.props.log.filter(
e => e.action.type == MAKE_MOVE
);

for (let i = 0; i < this.props.log.length; i++) {
const { action, payload } = this.props.log[i];
const oldTurn = state.ctx.turn;
const oldPhase = state.ctx.phase;
for (let i = 0; i < renderedLogEntries.length; i++) {
const { action, payload, turn, phase } = renderedLogEntries[i];

if (action.type == MAKE_MOVE) {
log.push(
<LogEvent
key={i}
pinned={i === this.state.pinned}
logIndex={i}
onLogClick={this.onLogClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
action={action}
payload={payload}
payloadComponent={this.props.payloadComponent}
eventsInCurrentPhase++;
eventsInCurrentTurn++;

log.push(
<LogEvent
key={i}
pinned={i === this.state.pinned}
logIndex={i}
onLogClick={this.onLogClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
action={action}
payload={payload}
payloadComponent={this.props.payloadComponent}
/>
);

if (
i == renderedLogEntries.length - 1 ||
renderedLogEntries[i + 1].turn != turn
) {
turns.push(
<TurnMarker
key={turns.length}
turn={turn}
numEvents={eventsInCurrentTurn}
/>
);

eventsInCurrentTurn++;
eventsInCurrentPhase++;
eventsInCurrentTurn = 0;
}

if (!action.automatic) {
state = this.props.reducer(state, action);

if (
state.ctx.turn != oldTurn ||
state.ctx.gameover !== undefined ||
i == lastAction
) {
turns.push(
<TurnMarker
key={turns.length}
turn={oldTurn}
numEvents={eventsInCurrentTurn}
/>
);
eventsInCurrentTurn = 0;
}

if (
state.ctx.phase != oldPhase ||
state.ctx.gameover !== undefined ||
i == lastAction
) {
phases.push(
<PhaseMarker
key={phases.length}
phase={oldPhase}
numEvents={eventsInCurrentPhase}
/>
);
eventsInCurrentPhase = 0;
}
if (
i == renderedLogEntries.length - 1 ||
renderedLogEntries[i + 1].phase != phase
) {
phases.push(
<PhaseMarker
key={phases.length}
phase={phase}
numEvents={eventsInCurrentPhase}
/>
);
eventsInCurrentPhase = 0;
}
}

Expand Down
107 changes: 57 additions & 50 deletions src/client/log/log.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import React from 'react';
import { Client } from '../client';
import { makeMove, gameEvent } from '../../core/action-creators';
import { makeMove, automaticGameEvent } from '../../core/action-creators';
import Game from '../../core/game';
import { GameLog } from './log';
import { InitializeGame, CreateGameReducer } from '../../core/reducer';
Expand All @@ -32,10 +32,10 @@ describe('layout', () => {

test('sanity', () => {
const log = [
{ action: makeMove('moveA') },
{ action: gameEvent('endTurn') },
{ action: makeMove('moveB') },
{ action: gameEvent('endTurn') },
{ action: makeMove('moveA'), turn: 0, phase: 'A' },
{ action: automaticGameEvent('endTurn'), turn: 0, phase: 'A' },
{ action: makeMove('moveB'), turn: 1, phase: 'A' },
{ action: automaticGameEvent('endTurn'), turn: 1, phase: 'A' },
];

const root = Enzyme.mount(
Expand All @@ -47,11 +47,11 @@ describe('layout', () => {

test('multiple moves per turn / phase', () => {
const log = [
{ action: makeMove('moveA') },
{ action: makeMove('moveB') },
{ action: gameEvent('endPhase') },
{ action: makeMove('moveC') },
{ action: gameEvent('endTurn') },
{ action: makeMove('moveA'), turn: 0, phase: 'A' },
{ action: makeMove('moveB'), turn: 0, phase: 'A' },
{ action: automaticGameEvent('endPhase'), turn: 0, phase: 'A' },
{ action: makeMove('moveC'), turn: 0, phase: 'B' },
{ action: automaticGameEvent('endTurn'), turn: 0, phase: 'B' },
];

const root = Enzyme.mount(
Expand All @@ -70,40 +70,46 @@ describe('layout', () => {
});

describe('time travel', () => {
const game = Game({
moves: {
A: (G, ctx, arg) => {
return { arg };
let client;
let root;
let hoverState;

beforeAll(() => {
const game = Game({
moves: {
A: (G, ctx, arg) => {
return { arg };
},
},
},

flow: {
endTurnIf: G => G && G.arg == 42,
},
});
flow: {
endTurnIf: G => G && G.arg == 42,
},
});

client = Client({ game });
const initialState = client.getState()._initial;

const client = Client({ game });
const initialState = client.getState()._initial;

client.moves.A(1);
client.events.endTurn();
// Also ends the turn automatically.
client.moves.A(42);
client.moves.A(2);
client.events.endTurn();

let hoverState = null;

const root = Enzyme.mount(
<GameLog
log={client.log}
initialState={initialState}
onHover={({ state }) => {
hoverState = state;
}}
reducer={client.reducer}
/>
);
client.moves.A(1);
client.events.endTurn();
// Also ends the turn automatically.
client.moves.A(42);
client.moves.A(2);
client.events.endTurn();

hoverState = null;

root = Enzyme.mount(
<GameLog
log={client.log}
initialState={initialState}
onHover={({ state }) => {
hoverState = state;
}}
reducer={client.reducer}
/>
);
});

test('before rewind', () => {
expect(client.getState().G).toMatchObject({ arg: 2 });
Expand Down Expand Up @@ -164,10 +170,10 @@ describe('pinning', () => {
let state = InitializeGame({ game });
const initialState = state;
const log = [
{ action: makeMove('A') },
{ action: gameEvent('endTurn') },
{ action: makeMove('B') },
{ action: gameEvent('endTurn') },
{ action: makeMove('A'), turn: 0, phase: 'default' },
{ action: automaticGameEvent('endTurn'), turn: 0, phase: 'default' },
{ action: makeMove('B'), turn: 1, phase: 'default' },
{ action: automaticGameEvent('endTurn'), turn: 1, phase: 'default' },
];

test('pin', () => {
Expand Down Expand Up @@ -243,7 +249,12 @@ describe('payload', () => {
const state = InitializeGame({ game });

const log = [
{ action: makeMove('moveA'), payload: { test_payload: 'payload123' } },
{
action: makeMove('moveA'),
payload: { test_payload: 'payload123' },
turn: 0,
phase: 'default',
},
];

test('renders custom payload using the default component', () => {
Expand All @@ -255,10 +266,6 @@ describe('payload', () => {
});

test('renders custom payload using a custom component', () => {
const log = [
{ action: makeMove('moveA'), payload: { test_payload: 'payload123' } },
];

const customPayloadComponent = () => {
return <div>ignoring props.payload</div>;
};
Expand Down
9 changes: 7 additions & 2 deletions src/core/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ export function Flow({
const { payload } = action;
if (events.hasOwnProperty(payload.type)) {
const context = { playerID: payload.playerID, dispatch };
const newLogEntry = { action, _stateID: state._stateID };
const deltalog = [...(state.deltalog || []), newLogEntry];
const logEntry = {
action,
_stateID: state._stateID,
turn: state.ctx.turn,
phase: state.ctx.phase,
};
const deltalog = [...(state.deltalog || []), logEntry];
state = { ...state, deltalog };
const args = [state].concat(payload.args);
return events[payload.type].apply(context, args);
Expand Down
10 changes: 9 additions & 1 deletion src/core/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,17 @@ export function CreateGameReducer({ game, multiplayer }) {
return state;
}

// Create a log entry for this move.
const logEntry = {
action,
_stateID: state._stateID,
turn: state.ctx.turn,
phase: state.ctx.phase,
};

// don't call into events here
const newState = apiCtx.updateAndDetach(
{ ...state, deltalog: [{ action, _stateID: state._stateID }] },
{ ...state, deltalog: [logEntry] },
false
);
let ctx = newState.ctx;
Expand Down
Loading

0 comments on commit e46f195

Please sign in to comment.