-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
163 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 !== '.') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
}; |