Introduce Flipper Tic Tac Toe example
Summary:
This Diff introduces an example for how to develop a React Native pure JS plugin and will be used in the docs. See the attached project as demo. The RN sources for the plugin component are:
```
import React, {useState, useEffect} from "react";
import {
StyleSheet,
View,
Text,
Button,
} from 'react-native';
import {addPlugin} from "react-native-flipper";
const initialState = {
cells: [" ", " ", " "," ", " ", " "," ", " ", " ",],
turn: ' ',
winner: ' ',
}
export default function FlipperTicTacToe() {
const [status, setStatus] = useState("Waiting for Flipper Desktop Player...")
const [gameState, setGameState] = useState(initialState);
const [connection, setConnection] = useState(null);
useEffect(() => {
addPlugin({
getId() {
return 'ReactNativeTicTacToe';
},
onConnect(connection) {
setStatus("Desktop player present");
setConnection(connection);
// listen to updates
connection.receive('SetState', (gameState, responder) => {
if (gameState.winner !== " ") {
setStatus(`Winner is ${gameState.winner}! Waiting for a new game...`);
} else {
setStatus(gameState.turn === "X" ? "Your turn...": "Awaiting desktop players turn...");
}
setGameState(gameState);
responder.success();
})
// request initial state
connection.send('GetState');
},
onDisconnect() {
setConnection(null);
setStatus("Desktop player gone...");
}
})
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Flipper Tic-Tac-Toe</Text>
<Text>{status}</Text>
<View style={styles.board}>
{gameState.cells.map((state, idx) =>
<View
key={idx}
style={styles.cell}>
<Button
title={state}
disabled={gameState.turn !== 'X' || state !== ' '}
onPress={() => {
connection.send('XMove', { move: idx });
}}
/>
</View>
)}
</View>
</View>
)
}
// Omitted styling
```
Reviewed By: passy
Differential Revision: D19410138
fbshipit-source-id: 93266a1ef7b86dcf043a744c3563dab0c585c8fd
This commit is contained in:
committed by
Facebook Github Bot
parent
08e2d54f62
commit
db9c41303d
@@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {View, FlipperPlugin, Button} from 'flipper';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
type State = {};
|
|
||||||
|
|
||||||
type PersistedState = {
|
|
||||||
count: number;
|
|
||||||
last: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class RnExamplePlugin extends FlipperPlugin<
|
|
||||||
State,
|
|
||||||
any,
|
|
||||||
PersistedState
|
|
||||||
> {
|
|
||||||
static defaultPersistedState = {count: 0, last: null};
|
|
||||||
|
|
||||||
static persistedStateReducer(
|
|
||||||
persistedState: PersistedState,
|
|
||||||
method: string,
|
|
||||||
payload: any,
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
count: persistedState.count + 1,
|
|
||||||
last: payload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {persistedState} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View>
|
|
||||||
<Button
|
|
||||||
onClick={async () => {
|
|
||||||
const result = await this.client.call('FromDesktop', {test: 123});
|
|
||||||
window.alert(result.test);
|
|
||||||
}}>
|
|
||||||
Send message
|
|
||||||
</Button>
|
|
||||||
<pre>{JSON.stringify(persistedState, null, 2)}</pre>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
190
src/plugins/rn-tic-tac-toe/index.tsx
Normal file
190
src/plugins/rn-tic-tac-toe/index.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
FlipperPlugin,
|
||||||
|
RoundedSection,
|
||||||
|
Button,
|
||||||
|
produce,
|
||||||
|
CenteredView,
|
||||||
|
Info,
|
||||||
|
colors,
|
||||||
|
styled,
|
||||||
|
FlexRow,
|
||||||
|
Text,
|
||||||
|
brandColors,
|
||||||
|
} from 'flipper';
|
||||||
|
import {Draft} from 'immer';
|
||||||
|
|
||||||
|
type Player = ' ' | 'X' | 'O';
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
cells: readonly [
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
Player,
|
||||||
|
];
|
||||||
|
winner: Player;
|
||||||
|
turn: 'X' | 'O';
|
||||||
|
};
|
||||||
|
|
||||||
|
function initialState(): State {
|
||||||
|
return {
|
||||||
|
// Cells
|
||||||
|
// 0 1 2
|
||||||
|
// 3 4 5
|
||||||
|
// 6 7 8
|
||||||
|
cells: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] as const,
|
||||||
|
turn: Math.random() < 0.5 ? 'O' : 'X',
|
||||||
|
winner: ' ',
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeNextState = produce(
|
||||||
|
(draft: Draft<State>, cell: number, player: 'X' | 'O') => {
|
||||||
|
draft.cells[cell] = player;
|
||||||
|
draft.turn = player === 'X' ? 'O' : 'X';
|
||||||
|
draft.winner = computeWinner(draft.cells);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function computeWinner(c: State['cells']): Player {
|
||||||
|
// check the 2 diagonals
|
||||||
|
if ((c[0] === c[4] && c[0] === c[8]) || (c[2] === c[4] && c[2] === c[6])) {
|
||||||
|
return c[4];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
// check vertical
|
||||||
|
if (c[i] === c[3 + i] && c[i] === c[6 + i]) {
|
||||||
|
return c[i];
|
||||||
|
}
|
||||||
|
// check horizontal
|
||||||
|
if (c[i * 3] === c[i * 3 + 1] && c[i * 3] === c[i * 3 + 2]) {
|
||||||
|
return c[i * 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ReactNativeTicTacToe extends FlipperPlugin<
|
||||||
|
State,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
> {
|
||||||
|
state = initialState();
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.client.subscribe('XMove', ({move}: {move: number}) => {
|
||||||
|
this.makeMove('X', move);
|
||||||
|
});
|
||||||
|
this.client.subscribe('GetState', () => {
|
||||||
|
this.sendUpdate();
|
||||||
|
});
|
||||||
|
this.sendUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMove(player: 'X' | 'O', move: number) {
|
||||||
|
if (this.state.turn === player && this.state.cells[move] === ' ') {
|
||||||
|
this.setState(computeNextState(this.state, move, player), () =>
|
||||||
|
this.sendUpdate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendUpdate() {
|
||||||
|
this.client.call('SetState', this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCellClick(move: number) {
|
||||||
|
this.makeMove('O', move);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset() {
|
||||||
|
this.setState(initialState(), () => this.sendUpdate());
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {winner, turn, cells} = this.state;
|
||||||
|
return (
|
||||||
|
<CenteredView>
|
||||||
|
<RoundedSection title="React Native Tic-Tac-Toe">
|
||||||
|
<Info type="info">
|
||||||
|
This plugin demonstrates how to create pure JavaScript Flipper
|
||||||
|
plugins for React Native. Find out how to create a similar plugin at{' '}
|
||||||
|
<a
|
||||||
|
href="https://fbflipper.com/docs/tutorial/intro.html"
|
||||||
|
target="blank">
|
||||||
|
fbflipper.com
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</Info>
|
||||||
|
<Container>
|
||||||
|
<Text size={24}>Flipper Tic-Tac-Toe</Text>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Text size={18}>
|
||||||
|
{winner !== ' '
|
||||||
|
? `Winner! ${winner}`
|
||||||
|
: turn === 'O'
|
||||||
|
? 'Your turn'
|
||||||
|
: 'Mobile players turn..'}
|
||||||
|
</Text>
|
||||||
|
<GameBoard>
|
||||||
|
{cells.map((c, idx) => (
|
||||||
|
<Cell
|
||||||
|
key={idx}
|
||||||
|
disabled={c !== ' ' || turn != 'O' || winner !== ' '}
|
||||||
|
onClick={() => this.handleCellClick(idx)}>
|
||||||
|
{c}
|
||||||
|
</Cell>
|
||||||
|
))}
|
||||||
|
</GameBoard>
|
||||||
|
<Button onClick={() => this.handleReset()}>Start new game</Button>
|
||||||
|
</Container>
|
||||||
|
</RoundedSection>
|
||||||
|
</CenteredView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container = styled('div')({
|
||||||
|
border: `4px solid ${brandColors.Flipper}`,
|
||||||
|
borderRadius: 4,
|
||||||
|
padding: 20,
|
||||||
|
marginTop: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const GameBoard = styled(FlexRow)({
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Cell = styled('button')({
|
||||||
|
padding: 20,
|
||||||
|
height: 80,
|
||||||
|
minWidth: 80,
|
||||||
|
fontSize: 24,
|
||||||
|
margin: 20,
|
||||||
|
flex: 0,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: colors.highlight,
|
||||||
|
color: 'white',
|
||||||
|
':disabled': {
|
||||||
|
backgroundColor: colors.greyTint2,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ReactNativeExamplePlugin",
|
"name": "ReactNativeTicTacToe",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.tsx",
|
"main": "index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": ["flipper-plugin"],
|
"keywords": ["flipper-plugin"],
|
||||||
"icon": "apps",
|
"icon": "apps",
|
||||||
"title": "React Native Example Plugin",
|
"title": "React Native Tic Tac Toe",
|
||||||
"category": "Example Plugin",
|
"category": "Examples",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"email": "mweststrate@fb.com"
|
"email": "mweststrate@fb.com"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user