Skip to content
This repository has been archived by the owner on Dec 15, 2020. It is now read-only.

Commit

Permalink
Initial replicate library.
Browse files Browse the repository at this point in the history
Summary:
Demonstrate how alternative consistency policies
can be applied to replicated redux stores by implementing
simple conservative and optimistic consistency policies
as redux middleware in new replicate library.

Reviewed By: gibsnson

Differential Revision: D5241655

fbshipit-source-id: 8813df8
  • Loading branch information
jimpurbrick authored and facebook-github-bot committed Jun 15, 2017
1 parent 2f6ad33 commit b4bef87
Show file tree
Hide file tree
Showing 12 changed files with 512 additions and 163 deletions.
6 changes: 4 additions & 2 deletions Examples/Pairs/README.md
Expand Up @@ -8,10 +8,12 @@ A real-time, multiplayer virtual reality matching pairs game built with
To play pairs on your local host:

1. Run ```npm run-script relay``` to start a wsrelay websocket relay server.
2. Open ```http://localhost:8081/ReactVR/Examples/Pairs/vr/``` in multiple web browsers to play pairs.
2. Run ```npm start``` in a second shell to serve the ReactVR application.
3. Open ```http://localhost:8081/vr/``` in multiple web browsers to play pairs.

To play pairs over the internet:

1. Run ```npm run-script relay``` to start a wsrelay websocket relay server.
2. Run ```ngrok http 4000``` to create an [ngrok](https://ngrok.com/) tunnel to your relay server.
3. Open ```http://localhost:8081/vr/#ws://...ngrok.io``` in a web browser on each machine (set the hash fragment to the forwarding address from ngrok) to play pairs.
3. Run ```npm start``` in a shell on each machine to serve the ReactVR application.
4. Open ```http://localhost:8081/vr/#ws://...ngrok.io``` in a web browser on each machine (set the hash fragment to the forwarding address from ngrok) to play pairs.
131 changes: 108 additions & 23 deletions Examples/Pairs/components/Board.js
Expand Up @@ -16,56 +16,141 @@ import React from 'react';
import {Text, View, VrButton} from 'react-vr';
import {connect} from 'react-redux';
import styles from './styles';
import {initialState} from '../reducers/board';
import {syncState} from '../index.vr';

const renderSquare = (value, rowIndex, columnIndex, onSquareClick) => (
const renderSquare = (value, rowIndex, columnIndex, state, onSquareClick) =>
<VrButton
onClick={() => onSquareClick(rowIndex, columnIndex, value)}
onClick={() => onSquareClick(rowIndex, columnIndex, value, state)}
key={rowIndex + ':' + columnIndex}>
<View style={styles.square}>
<Text style={styles.text}>
{value > 0 ? value : ''}
</Text>
</View>
</VrButton>
);
</VrButton>;

const renderRow = (row, rowIndex, board, onSquareClick) => (
const renderRow = (row, rowIndex, state, onSquareClick) =>
<View style={styles.row} key={row}>
{row.map((column, columnIndex) =>
renderSquare(board[rowIndex][columnIndex], rowIndex, columnIndex, onSquareClick)
renderSquare(state.board[rowIndex][columnIndex], rowIndex, columnIndex, state, onSquareClick)
)}
</View>
);
</View>;

const renderBoard = ({board, onSquareClick}) => (
const renderBoard = ({state, onSquareClick}) =>
<View style={styles.board}>
{board.map((row, rowIndex) => renderRow(row, rowIndex, board, onSquareClick))}
</View>
);
{state.board.map((row, rowIndex) => renderRow(row, rowIndex, state, onSquareClick))}
</View>;

const mapStateToProps = state => ({
board: state.board,
state: state,
});

const showSquare = (rowIndex, columnIndex, client) => ({
export const showSquare = (rowIndex, columnIndex, client) => ({
type: 'SHOW',
square: {row: rowIndex, column: columnIndex},
client: client,
});

const restart = () => ({
type: 'RESTART',
export const scoreSquare = (client, value) => ({
type: 'SCORE',
client: client,
value: value,
});

export const hideSquare = showAction => ({
type: 'HIDE',
square: showAction.square,
client: showAction.client,
});

export const countValues = (state, test) => {
return state.reduce((acc, row) => {
return row.reduce((acc, value) => {
if (test(value)) {
return acc + 1;
}
return acc;
}, acc);
}, 0);
};

export const countEquals = (state, value) => {
return countValues(state, x => {
return x === value;
});
};

const zeroScores = scores => {
return Object.keys(scores).reduce((acc, key) => {
acc[key] = [];
return acc;
}, {});
};

const scorer = (value, scores) => {
let result = null;
Object.keys(scores).some(key => {
if (scores[key].indexOf(value) >= 0) {
result = key;
return true;
}
return false;
});
return result;
};

const gameOver = board => {
return !board.some(row => row.some(value => value < 0));
};

// true if action can be reduced given state
// TODO(jimp): pass store instead of state to allow dispatch during validation?
export const isValid = (action, state) => {
switch (action.type) {
case 'SCORE':
// score action can be reduced if state and action agree on scorer
const currentScorer = scorer(action.value, state.scores);
return currentScorer === null || currentScorer === action.client;
case 'SHOW':
// show action is idempotent, so can always be reduced
return true;
case 'HIDE':
// hide action can be reduced if value not scored
const value = state.board[action.square.row][action.square.column];
return scorer(value, state.scores) === null;
}
return null;
};

export const dispatchShowActions = (row, column, client, state, dispatch, delay) => {
// dispatch show action
const showAction = showSquare(row, column, client);
dispatch(showAction);

// dispatch score action if one half of pair currently shown
const value = state.board[row][column];
if (countEquals(state.board, Math.abs(value)) === 1) {
dispatch(scoreSquare(client, Math.abs(value)));
}

// dispatch hide after delay
setTimeout(() => {
dispatch(hideSquare(showAction));
}, delay);
};

const mapDispatchToProps = (dispatch, ownProps) => ({
onSquareClick: (rowIndex, columnIndex, value) => {
if (value > 0) {
dispatch(restart());
onSquareClick: (row, column, value, state) => {
// reset game if game over
if (gameOver(state.board)) {
dispatch(syncState({board: initialState(), scores: zeroScores(state.scores)}));
return;
}
dispatch(showSquare(rowIndex, columnIndex, ownProps.client));

// otherwise dispatch show, score and hide actions
dispatchShowActions(row, column, ownProps.client, state, dispatch, 1000);
},
});

const Board = connect(mapStateToProps, mapDispatchToProps)(renderBoard);

export default Board;
export const Board = connect(mapStateToProps, mapDispatchToProps)(renderBoard);
12 changes: 5 additions & 7 deletions Examples/Pairs/components/Scores.js
Expand Up @@ -17,25 +17,23 @@ import {Text, View} from 'react-vr';
import {connect} from 'react-redux';
import styles from './styles';

const renderScore = (player, key, score, local) => (
const renderScore = (key, score, local) =>
<View style={styles.square} key={key}>
<Text style={local ? styles.text : styles.grayText}>
{score}
</Text>
</View>
);
</View>;

// TODO(jimp): fix layout to show all scores rather than first 4.
const renderScores = ({scores, id}) => (
const renderScores = ({scores, id}) =>
<View style={styles.board}>
<View style={styles.row}>
{Object.keys(scores)
.sort()
.slice(0, 4)
.map((key, index) => renderScore(index + 1, key, scores[key], key === id))}
.map((key, index) => renderScore(key, scores[key].length, key === id))}
</View>
</View>
);
</View>;

const mapStateToProps = (state, ownProps) => ({
scores: state.scores,
Expand Down
2 changes: 1 addition & 1 deletion Examples/Pairs/index.js
Expand Up @@ -22,7 +22,7 @@ const reducers = combineReducers({
});

const app = (state, action) => {
if (action.type === 'SET_STATE') {
if (action.type === 'SYNC_STATE') {
return {...action.state};
}
return reducers(state, action);
Expand Down

0 comments on commit b4bef87

Please sign in to comment.