import { ReactNode, useEffect, useRef, useState } from "react"; import { LoadedTheme, Theme, useTheme } from "../themes/Theme"; import { Container, Sprite, Stage } from "@pixi/react"; import Viewport from "./pixi/PixiViewport"; import { ClientGame, getValue, isServerGame, ServerGame, } from "../../shared/game"; import { useWSQuery } from "../hooks"; interface BoardProps { theme: Theme; game: ServerGame | ClientGame; onLeftClick: (x: number, y: number) => void; onRightClick: (x: number, y: number) => void; } const Board: React.FC = (props) => { const { game } = props; const { data: user } = useWSQuery("user.getSelf", null); const ref = useRef(null); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const showLastPos = game.user !== user || isServerGame(game); useEffect(() => { if (!ref.current) return; setWidth(ref.current.clientWidth); setHeight(ref.current.clientHeight); const resizeObserver = new ResizeObserver(() => { if (ref.current) { setWidth(ref.current.clientWidth); setHeight(ref.current.clientHeight); } }); resizeObserver.observe(ref.current); return () => resizeObserver.disconnect(); }, []); const theme = useTheme(props.theme); const boardWidth = game.width * (theme?.size || 0); const boardHeight = game.height * (theme?.size || 0); return (
{theme && ( {game.isRevealed.map((_, i) => { return game.isRevealed[0].map((_, j) => { return ( ); }); })} )}
); }; interface TileProps { x: number; y: number; game: ServerGame | ClientGame; theme: LoadedTheme; showLastPos: boolean; onLeftClick: (x: number, y: number) => void; onRightClick: (x: number, y: number) => void; } const Tile = ({ game, x, y, theme, showLastPos, onRightClick, onLeftClick, }: TileProps) => { const i = x; const j = y; const isRevealed = game.isRevealed[i][j]; const value = isServerGame(game) ? getValue(game.mines, i, j) : game.values[i][j]; const isMine = isServerGame(game) ? game.mines[i][j] : false; const isLastPos = showLastPos ? game.lastClick[0] === i && game.lastClick[1] === j : false; const isFlagged = game.isFlagged[i][j]; const isQuestionMark = game.isQuestionMark[i][j]; const base = isRevealed ? ( ) : ( ); let content: ReactNode = null; if (isMine) { content = ; } else if (value !== -1 && isRevealed) { const img = theme[value.toString() as keyof Theme] as string; content = img ? : null; } else if (isFlagged) { content = ; } else if (isQuestionMark) { content = ; } const extra = isLastPos ? : null; const touchStart = useRef(0); const isMove = useRef(false); const startX = useRef(0); const startY = useRef(0); return ( { onRightClick(i, j); }} onpointerup={(e) => { if (e.button !== 0) return; if (isMove.current) return; if (Date.now() - touchStart.current > 300) { onRightClick(i, j); } else { onLeftClick(i, j); } }} onpointerdown={(e) => { isMove.current = false; touchStart.current = Date.now(); startX.current = e.global.x; startY.current = e.global.y; }} onpointermove={(e) => { if ( Math.abs(startX.current - e.global.x) > 10 || Math.abs(startY.current - e.global.y) > 10 ) { isMove.current = true; } }} > {base} {content} {extra} ); }; export default Board;