Refactor tic-tac-toe plugin to Sandy architecture

Summary:
CHANGELOG: Refactor tic-tac-toe plugin to Sandy architecture

Sandy architecture unblocks using the plugin in a headless mode. Eventual goal - platy tic-tac-toe form a terminal

Reviewed By: passy

Differential Revision: D36513795

fbshipit-source-id: 5a967a325cfb52cc3ae72840240b22b0a4e8f031
This commit is contained in:
Andrey Goncharov
2022-05-19 09:13:33 -07:00
committed by Facebook GitHub Bot
parent 566f7108c9
commit a7b148fcc2
2 changed files with 111 additions and 103 deletions

View File

@@ -8,20 +8,17 @@
*/ */
import React from 'react'; import React from 'react';
import { import {Typography, Button, Alert, Space} from 'antd';
FlipperPlugin,
RoundedSection,
Button,
produce,
CenteredView,
Info,
colors,
styled,
FlexRow,
Text,
brandColors,
} from 'flipper';
import {Draft} from 'immer'; import {Draft} from 'immer';
import {
createState,
PluginClient,
usePlugin,
useValue,
styled,
theme,
Layout,
} from 'flipper-plugin';
type Player = ' ' | 'X' | 'O'; type Player = ' ' | 'X' | 'O';
@@ -53,13 +50,12 @@ function initialState(): State {
} as const; } as const;
} }
const computeNextState = produce( const computeNextState =
(draft: Draft<State>, cell: number, player: 'X' | 'O') => { (cell: number, player: 'X' | 'O') => (draft: Draft<State>) => {
draft.cells[cell] = player; draft.cells[cell] = player;
draft.turn = player === 'X' ? 'O' : 'X'; draft.turn = player === 'X' ? 'O' : 'X';
draft.winner = computeWinner(draft.cells); draft.winner = computeWinner(draft.cells);
}, };
);
function computeWinner(c: State['cells']): Player { function computeWinner(c: State['cells']): Player {
// check the 2 diagonals // check the 2 diagonals
@@ -79,110 +75,119 @@ function computeWinner(c: State['cells']): Player {
return ' '; return ' ';
} }
export default class ReactNativeTicTacToe extends FlipperPlugin< type Events = {
State, XMove: {move: number};
any, GetState: never;
any };
> {
state = initialState();
componentDidMount() { type Methods = {
this.client.subscribe('XMove', ({move}: {move: number}) => { SetState: (state: State) => Promise<void>;
this.makeMove('X', move); };
export const plugin = (client: PluginClient<Events, Methods>) => {
const state = createState(initialState());
const sendUpdate = () => {
client.send('SetState', state.get());
};
const makeMove = (player: 'X' | 'O', move: number) => {
if (state.get().turn === player && state.get().cells[move] === ' ') {
state.update(computeNextState(move, player));
sendUpdate();
}
};
const reset = () => {
state.set(initialState());
sendUpdate();
};
client.onConnect(() => {
client.onMessage('XMove', ({move}) => {
makeMove('X', move);
}); });
this.client.subscribe('GetState', () => { client.onMessage('GetState', () => {
this.sendUpdate(); sendUpdate();
});
sendUpdate();
}); });
this.sendUpdate();
}
makeMove(player: 'X' | 'O', move: number) { return {
if (this.state.turn === player && this.state.cells[move] === ' ') { makeMove,
this.setState(computeNextState(this.state, move, player), () => reset,
this.sendUpdate(), state,
); };
} };
}
sendUpdate() { const desktopPlayer = 'O';
this.client.call('SetState', this.state);
}
handleCellClick(move: number) { export const Component = () => {
this.makeMove('O', move); const pluginInstance = usePlugin(plugin);
} const {winner, turn, cells} = useValue(pluginInstance.state);
handleReset() {
this.setState(initialState(), () => this.sendUpdate());
}
render() {
const {winner, turn, cells} = this.state;
return ( return (
<CenteredView> <Layout.Container>
<RoundedSection title="React Native Tic-Tac-Toe"> <Space direction="vertical" align="center">
<Info type="info"> <Alert
message={
<>
This plugin demonstrates how to create pure JavaScript Flipper This plugin demonstrates how to create pure JavaScript Flipper
plugins for React Native. Find out how to create a similar plugin at{' '} plugins for React Native. Find out how to create a similar plugin
<a href="https://fbflipper.com/docs/tutorial/intro" target="blank"> at{' '}
<a
href="https://fbflipper.com/docs/tutorial/intro"
target="blank">
fbflipper.com fbflipper.com
</a> </a>
. .
</Info> </>
<Container> }
<Text size={24}>Flipper Tic-Tac-Toe</Text> type="info"
<br /> />
<br /> <Typography.Title>Flipper Tic-Tac-Toe</Typography.Title>
<Text size={18}> <Typography.Text>
{winner !== ' ' {winner !== ' '
? `Winner! ${winner}` ? `Winner! ${winner}`
: turn === 'O' : turn === 'O'
? 'Your turn' ? 'Your turn'
: 'Mobile players turn..'} : 'Mobile players turn..'}
</Text> </Typography.Text>
<GameBoard> <GameBoard>
{cells.map((c, idx) => ( {cells.map((c, idx) => (
<Cell <Cell
key={idx} key={idx}
disabled={c !== ' ' || turn != 'O' || winner !== ' '} disabled={c !== ' ' || turn != desktopPlayer || winner !== ' '}
onClick={() => this.handleCellClick(idx)}> onClick={() => pluginInstance.makeMove(desktopPlayer, idx)}>
{c} {c}
</Cell> </Cell>
))} ))}
</GameBoard> </GameBoard>
<Button onClick={() => this.handleReset()}>Start new game</Button> <Button onClick={() => pluginInstance.reset()}>Start new game</Button>
</Container> </Space>
</RoundedSection> </Layout.Container>
</CenteredView>
); );
} };
}
const Container = styled('div')({ const GameBoard = styled('div')({
border: `4px solid ${brandColors.Flipper}`, display: 'grid',
borderRadius: 4, gridTemplateColumns: 'repeat(3, 80px)',
padding: 20, gridTemplateRows: 'repeat(3, 80px)',
marginTop: 20, justifyContent: 'center',
}); gap: 10,
const GameBoard = styled(FlexRow)({
flexWrap: 'wrap',
justifyContent: 'space-between',
marginTop: 20,
marginBottom: 20,
}); });
const Cell = styled('button')({ const Cell = styled('button')({
padding: 20, padding: 20,
height: 80, width: '100%',
height: '100%',
minWidth: 80, minWidth: 80,
fontSize: 24, fontSize: 24,
margin: 20,
flex: 0, flex: 0,
borderRadius: 4, borderRadius: 4,
backgroundColor: colors.highlight, backgroundColor: theme.backgroundDefault,
color: 'white', color: 'white',
':disabled': { ':disabled': {
backgroundColor: colors.grayTint2, backgroundColor: theme.disabledColor,
}, },
}); });

View File

@@ -14,5 +14,8 @@
"category": "Examples", "category": "Examples",
"bugs": { "bugs": {
"url": "https://github.com/facebook/flipper/issues" "url": "https://github.com/facebook/flipper/issues"
},
"peerDependencies": {
"flipper-plugin": "*"
} }
} }