Skip to content

Commit

Permalink
Connect backend to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Comeza committed May 22, 2024
1 parent fccfcb0 commit 926ed8c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 56 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Protocol

## Board
`BOARD <you> <width> <height> <board>`
- `<you>` The character that was assigned to you
- `<width>` The width of the board
- `<height>` The height of the board
- `<board>` The encoded board. Each character is a filed in the board. You can use the formula `x = index / width` and `y = index % height`
95 changes: 47 additions & 48 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 <div className="flex justify-center items-center w-dvw h-dvh">
<div className="flex flex-row h-dvh py-10">
<Board width={w} height={h} players={PLAYERS} />
<div className="px-5 flex">
<div className="h-full whitespace-nowrap font-mono font-bold text-xl flex flex-col leading-5">
<span>TURN: 2</span>
<span>SIZE: {w}x{h}</span>
<span>ROUND: 20</span>
<span>PLAYERS: 20</span>
<span>RUNNING: {formatNumber(time / 60)}:{formatNumber(time % 60)}</span>
<hr className="bg-black h-[3px] py-2 mb-4" />
<a href="https://github.com/Lila-Kuhlt/mmgo" rel="noreferrer" target="_blank" >
<QRCodeSVG value="https://github.com/Lila-Kuhlt/mmgo" />
</a>
<hr className="bg-black h-[3px] py-2 my-4" />
<input type="range" min="3" value={w} onChange={(e) => setDim([parseInt(e.target.value), h])} />
<input type="range" min="3" value={h} onChange={(e) => setDim([w, parseInt(e.target.value)])} />
</div>
</div>


return (<GameStateProvider>

<GameStateContext.Consumer>
{gameState =>
<WebSocketProvider url="ws://localhost:1213" onMsg={(msg) => gameState?.setState({ ...gameState, ...parseMsg(msg) })}>

<div className="flex justify-center items-center w-dvw h-dvh">
<div className="flex flex-row h-dvh py-10">
<BoardWrapper />
<Sidebar />
</div >
</div>

</WebSocketProvider>
}
</GameStateContext.Consumer>
</GameStateProvider>)

}

function BoardWrapper() {
const gameState = useContext(GameStateContext);

const width = gameState?.width || 3;
const height = gameState?.height || 3;

return <Board width={width} height={height} board={gameState?.board || undefined} />
}


function Sidebar() {
const gameState = useContext(GameStateContext);

return <div className="px-5 flex">
<div className="h-full whitespace-nowrap font-mono font-bold text-xl flex flex-col leading-5">
<span>TURN: {gameState?.turn || 0}</span>
<span>SIZE: {gameState?.width || 3}x{gameState?.height || 3}</span>
<hr className="bg-black h-[3px] py-2 mb-4" />
<a href="https://github.com/Lila-Kuhlt/mmgo" rel="noreferrer" target="_blank" >
<QRCodeSVG value="https://github.com/Lila-Kuhlt/mmgo" />
</a>
<hr className="bg-black h-[3px] py-2 my-4" />
</div>
</div>
}
13 changes: 5 additions & 8 deletions frontend/src/Board.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Player } from "lib/game"

type LineProps = {
x: number,
y: number,
Expand All @@ -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<BoardProps>) {

const padding = 3;
const border = padding * 2;

Expand Down Expand Up @@ -70,7 +67,7 @@ export function Board(props: React.PropsWithChildren<BoardProps>) {
len={viewboxHeight}
/>)}

{props.players?.map(p =>
{props.board?.map(p =>
<circle
key={p.y * height + p.x}
cx={p.x * spaceH + padding}
Expand Down
50 changes: 50 additions & 0 deletions frontend/src/lib/game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { PropsWithChildren, createContext, useState } from "react";

export type Player = {
x: number,
y: number,
color: string
}

export interface GameState {
board: Player[],
width: number,
height: number,
turn: number,
}

export type GameStateExt = GameState & { setState: (state: GameState) => void };

export const GameStateContext = createContext<GameStateExt | null>(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 <GameStateContext.Provider value={stateExt}>
{props.children}
</GameStateContext.Provider>
}


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 !== '.')
}
53 changes: 53 additions & 0 deletions frontend/src/lib/ws.tsx
Original file line number Diff line number Diff line change
@@ -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<WebSocketState | null>(null);

export function WebSocketProvider(props: PropsWithChildren<WebsocketProviderProps>) {
const [ws, setWs] = useState<WebSocket | null>(null);
const [isConnected, setIsConnected] = useState<boolean>(false);
const reconnectInterval = useRef<number | null>(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 <WebSocketContext.Provider value={{ ws, isConnected }}>
{props.children}
</WebSocketContext.Provider>
};

0 comments on commit 926ed8c

Please sign in to comment.