diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1cb3bf --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Protocol + +## Board +`BOARD ` +- `` The character that was assigned to you +- `` The width of the board +- `` The height of the board +- `` The encoded board. Each character is a filed in the board. You can use the formula `x = index / width` and `y = index % height` diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index eb92611..d4acf69 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,55 +1,54 @@ import { Board } from "Board" +import { GameStateContext, GameStateProvider, parseMsg } from "lib/game"; +import { WebSocketProvider, } from "lib/ws"; import { QRCodeSVG } from "qrcode.react" -import { useEffect, useState } from "react"; - -const PLAYERS = [ - { - x: 0, - y: 0, - color: "red" - }, - { - x: 1, - y: 2, - color: "white" - }, - { - x: 2, - y: 2, - color: "green" - }, -]; - -const formatNumber = (n: number) => (n = Math.floor(n)) && Math.abs(n) > 9 ? n.toString() : '0' + n +import { useContext } from "react"; export function App() { - const [time, setTime] = useState(0); - const [timerStart, _] = useState(new Date()); - - const [[w, h], setDim] = useState([10, 10]); - - useEffect(() => { setInterval(() => setTime((new Date().getTime() - timerStart.getTime()) / 1000), 100); }, [timerStart]) - - - return
-
- -
-
- TURN: 2 - SIZE: {w}x{h} - ROUND: 20 - PLAYERS: 20 - RUNNING: {formatNumber(time / 60)}:{formatNumber(time % 60)} -
- - - -
- setDim([parseInt(e.target.value), h])} /> - setDim([w, parseInt(e.target.value)])} /> -
-
+ + + return ( + + + {gameState => + gameState?.setState({ ...gameState, ...parseMsg(msg) })}> + +
+
+ + +
+
+ +
+ } +
+
) + +} + +function BoardWrapper() { + const gameState = useContext(GameStateContext); + + const width = gameState?.width || 3; + const height = gameState?.height || 3; + + return +} + + +function Sidebar() { + const gameState = useContext(GameStateContext); + + return
+
+ TURN: {gameState?.turn || 0} + SIZE: {gameState?.width || 3}x{gameState?.height || 3} +
+ + + +
} diff --git a/frontend/src/Board.tsx b/frontend/src/Board.tsx index 5036170..db64693 100644 --- a/frontend/src/Board.tsx +++ b/frontend/src/Board.tsx @@ -1,3 +1,5 @@ +import { Player } from "lib/game" + type LineProps = { x: number, y: number, @@ -11,19 +13,14 @@ function Line({ x, y, len, dir }: LineProps) { /> } -type Player = { - x: number, - y: number, - color: string -} - type BoardProps = { width: number, height: number, - players?: Player[] + board?: Player[] } export function Board(props: React.PropsWithChildren) { + const padding = 3; const border = padding * 2; @@ -70,7 +67,7 @@ export function Board(props: React.PropsWithChildren) { len={viewboxHeight} />)} - {props.players?.map(p => + {props.board?.map(p => void }; + +export const GameStateContext = createContext(null); + +export function GameStateProvider(props: PropsWithChildren) { + const [gameState, setState] = useState(() => ({ width: 3, height: 3, turn: 0, board: parseBoard('.........', 3) })); + const stateExt: GameStateExt = { ...gameState, setState: (game: GameState) => setState(game) }; + + return + {props.children} + +} + + +export function parseMsg(msg: string): { board: Player[], width: number, height: number } { + const [_, widthStr, heightStr, pieces] = msg.split(" "); + + const width = parseInt(widthStr) + const height = parseInt(heightStr) + + const board = parseBoard(pieces, width); + + return { width, height, board } +} + +export function parseBoard(encBoard: string, width: number): Player[] { + return encBoard.split('') + .map((color, index) => ({ + x: index % width, + y: Math.ceil(index / width), + color + }) + ) + .filter(piece => piece.color !== '.') +} diff --git a/frontend/src/lib/ws.tsx b/frontend/src/lib/ws.tsx new file mode 100644 index 0000000..d3db19f --- /dev/null +++ b/frontend/src/lib/ws.tsx @@ -0,0 +1,53 @@ +import { createContext, useState, useEffect, useRef, PropsWithChildren } from 'react'; + +export interface WebSocketState { + ws: WebSocket | null; + isConnected: boolean; +} + +export interface WebsocketProviderProps { + url: string, + onMsg: (msg: string) => void +} + +export const WebSocketContext = createContext(null); + +export function WebSocketProvider(props: PropsWithChildren) { + const [ws, setWs] = useState(null); + const [isConnected, setIsConnected] = useState(false); + const reconnectInterval = useRef(null); + + const connect = () => { + const newWs = new WebSocket(props.url); + setWs(newWs); + }; + + useEffect(() => { + if (!ws) return; + + ws.onopen = () => setIsConnected(true); + ws.onmessage = (event: MessageEvent) => props.onMsg?.(event.data); + + ws.onclose = () => { + setIsConnected(false); + reconnectInterval.current && clearTimeout(reconnectInterval.current); + reconnectInterval.current = setTimeout(connect, 5000); + }; + + return () => { + ws.close(); + reconnectInterval.current && clearTimeout(reconnectInterval.current); + }; + + }, [ws]); + + useEffect(() => { + if (!isConnected) { + connect(); + } + }, [isConnected]); + + return + {props.children} + +};