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:
Michel Weststrate
2020-01-16 04:45:03 -08:00
committed by Facebook Github Bot
parent 08e2d54f62
commit db9c41303d
4 changed files with 193 additions and 59 deletions

View File

@@ -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>
);
}
}

View 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,
},
});

View File

@@ -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"
} }