diff --git a/backend/controller/gameController.ts b/backend/controller/gameController.ts index 4de5f8c..e8e6714 100644 --- a/backend/controller/gameController.ts +++ b/backend/controller/gameController.ts @@ -8,15 +8,12 @@ import { parseGameState, upsertGameState, } from "../repositories/gameRepository"; -import { - serverGame, - serverToClientGame, - type ServerGame, -} from "../../shared/game"; +import { serverToClientGame, type ServerGame } from "../../shared/game"; import crypto from "crypto"; import { game } from "../entities/game"; import { UnauthorizedError } from "../errors/UnauthorizedError"; import { emit } from "../events"; +import { serverGame } from "../../shared/gameType"; export const gameController = createController({ getGameState: createEndpoint(z.string(), async (uuid, ctx) => { diff --git a/bun.lockb b/bun.lockb index 8505b9d..0a3d743 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 97caded..49b3790 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ }, "dependencies": { "@msgpack/msgpack": "^3.0.0-beta2", + "@pixi/canvas-display": "^7.4.2", + "@pixi/canvas-renderer": "^7.4.2", "@pixi/react": "^7.1.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", @@ -32,6 +34,7 @@ "lucide-react": "^0.452.0", "pixi-viewport": "^5.0.3", "pixi.js": "^7.0.0", + "pixi.js-legacy": "^7.4.2", "react": "^18.3.1", "react-confetti-boom": "^1.0.0", "react-dom": "^18.3.1", diff --git a/shared/game.ts b/shared/game.ts index aece234..31beff0 100644 --- a/shared/game.ts +++ b/shared/game.ts @@ -1,38 +1,5 @@ -import { z } from "zod"; - -export const clientGame = z.object({ - user: z.string(), - uuid: z.string(), - width: z.number(), - height: z.number(), - isRevealed: z.array(z.array(z.boolean())), - isFlagged: z.array(z.array(z.boolean())), - isQuestionMark: z.array(z.array(z.boolean())), - values: z.array(z.array(z.number())), - minesCount: z.number(), - lastClick: z.tuple([z.number(), z.number()]), - started: z.number(), - stage: z.number(), -}); - -export const serverGame = z.object({ - user: z.string(), - uuid: z.string(), - width: z.number(), - height: z.number(), - isRevealed: z.array(z.array(z.boolean())), - isFlagged: z.array(z.array(z.boolean())), - isQuestionMark: z.array(z.array(z.boolean())), - mines: z.array(z.array(z.boolean())), - minesCount: z.number(), - lastClick: z.tuple([z.number(), z.number()]), - started: z.number(), - finished: z.number().default(0), - stage: z.number(), -}); - -export type ClientGame = z.infer; -export type ServerGame = z.infer; +import type { ServerGame, ClientGame } from "./gameType"; +export type { ServerGame, ClientGame } from "./gameType"; export const isServerGame = (game: ServerGame | ClientGame) => "mines" in game; export const isClientGame = ( diff --git a/shared/gameType.ts b/shared/gameType.ts new file mode 100644 index 0000000..e92e27c --- /dev/null +++ b/shared/gameType.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; + +export const clientGame = z.object({ + user: z.string(), + uuid: z.string(), + width: z.number(), + height: z.number(), + isRevealed: z.array(z.array(z.boolean())), + isFlagged: z.array(z.array(z.boolean())), + isQuestionMark: z.array(z.array(z.boolean())), + values: z.array(z.array(z.number())), + minesCount: z.number(), + lastClick: z.tuple([z.number(), z.number()]), + started: z.number(), + stage: z.number(), +}); + +export const serverGame = z.object({ + user: z.string(), + uuid: z.string(), + width: z.number(), + height: z.number(), + isRevealed: z.array(z.array(z.boolean())), + isFlagged: z.array(z.array(z.boolean())), + isQuestionMark: z.array(z.array(z.boolean())), + mines: z.array(z.array(z.boolean())), + minesCount: z.number(), + lastClick: z.tuple([z.number(), z.number()]), + started: z.number(), + finished: z.number().default(0), + stage: z.number(), +}); + +export type ClientGame = z.infer; +export type ServerGame = z.infer; diff --git a/shared/testBoard.ts b/shared/testBoard.ts new file mode 100644 index 0000000..fdb2894 --- /dev/null +++ b/shared/testBoard.ts @@ -0,0 +1,41 @@ +import { ServerGame } from "./gameType"; + +const rotate = (arr: boolean[][]) => { + return arr[0].map((_, colIndex) => arr.map((row) => row[colIndex])); +}; + +export const testBoard: ServerGame = { + user: "TestUser", + uuid: "C270D7CD-AF97-42CE-A6C9-CB765102CA17", + width: 11, + height: 4, + isRevealed: rotate([ + [false, false, false, false, false, ...Array(6).fill(true)], + [...Array(11).fill(true)], + [...Array(11).fill(true)], + [...Array(6).fill(true), ...Array(5).fill(false)], + ]), + isFlagged: rotate([ + [true, ...Array(10).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + ]), + finished: 1, + started: 1, + stage: 420, + lastClick: [2, 2], + mines: rotate([ + [false, false, false, false, false, ...Array(6).fill(true)], + [...Array(8).fill(false), true, false, true], + [false, false, ...Array(9).fill(true)], + [...Array(11).fill(false)], + ]), + minesCount: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8, + isQuestionMark: rotate([ + [...Array(11).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + ]), +}; diff --git a/shared/user-settings.ts b/shared/user-settings.ts index 60e23ee..6f114e9 100644 --- a/shared/user-settings.ts +++ b/shared/user-settings.ts @@ -5,9 +5,5 @@ export const userSettings = z.object({ longPressOnDesktop: z.boolean().default(false), }); -export const getDefaultSettings = () => { - return userSettings.parse({}); -}; - export type UserSettings = z.infer; export type UserSettingsInput = z.input; diff --git a/src/Shell.tsx b/src/Shell.tsx index 587fd36..c72aa08 100644 --- a/src/Shell.tsx +++ b/src/Shell.tsx @@ -95,7 +95,7 @@ const Shell: React.FC = ({ children }) => { transition={{ type: "tween" }} layout /> - +
{children} diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 3f969c9..553ff07 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -26,6 +26,16 @@ import { Button } from "./Button"; import { Maximize2, Minimize2, RotateCcw } from "lucide-react"; import useSound from "use-sound"; import explosion from "../sound/explosion.mp3"; +import "@pixi/canvas-display"; +import "@pixi/canvas-extract"; +import "@pixi/canvas-graphics"; +import "@pixi/canvas-mesh"; +import "@pixi/canvas-particle-container"; +import "@pixi/canvas-prepare"; +import "@pixi/canvas-renderer"; +import "@pixi/canvas-sprite-tiling"; +import "@pixi/canvas-sprite"; +import "@pixi/canvas-text"; interface BoardProps { theme: Theme; @@ -33,6 +43,8 @@ interface BoardProps { onLeftClick: (x: number, y: number) => void; onRightClick: (x: number, y: number) => void; restartGame: () => void; + width?: number; + height?: number; } interface ViewportInfo { @@ -88,7 +100,7 @@ const Board: React.FC = (props) => { }); }, []); useEffect(() => { - setTimeout(() => { + setInterval(() => { if (viewportRef.current) onViewportChange(viewportRef.current); }, 200); }, [game.width, game.height, onViewportChange]); @@ -113,14 +125,25 @@ const Board: React.FC = (props) => { const viewportRef = useRef(null); const [zenMode, setZenMode] = useState(false); + useEffect(() => { + if (ref.current) { + ref.current.addEventListener("wheel", (e) => { + e.preventDefault(); + }); + } + }, [ref]); return (
@@ -135,7 +158,7 @@ const Board: React.FC = (props) => { onClick={() => setZenMode(!zenMode)} size="sm" > - {!zenMode ? ( + {props.width || props.height ? undefined : !zenMode ? ( ) : ( @@ -152,7 +175,7 @@ const Board: React.FC = (props) => {
{theme && ( = (props) => { worldHeight={boardHeight} width={width} height={height} - clamp={{ - left: -theme.size, - right: boardWidth + theme.size, - top: -theme.size, - bottom: boardHeight + theme.size, - }} + clamp={ + props.width || props.height + ? { left: 0, right: boardWidth, top: 0, bottom: boardHeight } + : { + left: -theme.size, + right: boardWidth + theme.size, + top: -theme.size, + bottom: boardHeight + theme.size, + } + } clampZoom={{ minScale: 1, }} @@ -204,7 +231,7 @@ const Board: React.FC = (props) => { )}
- + {!props.width && !props.height && }
); }; diff --git a/src/components/pixi/PixiViewport.tsx b/src/components/pixi/PixiViewport.tsx index 6ca428a..2822e25 100644 --- a/src/components/pixi/PixiViewport.tsx +++ b/src/components/pixi/PixiViewport.tsx @@ -1,5 +1,5 @@ import React from "react"; -import * as PIXI from "pixi.js"; +import type { Application } from "pixi.js"; import { IClampZoomOptions, Viewport as PixiViewport } from "pixi-viewport"; import { PixiComponent, useApp } from "@pixi/react"; import { BaseTexture, SCALE_MODES } from "pixi.js"; @@ -23,7 +23,7 @@ export interface ViewportProps { } export interface PixiComponentViewportProps extends ViewportProps { - app: PIXI.Application; + app: Application; } const PixiComponentViewport = PixiComponent("Viewport", { diff --git a/src/hooks.ts b/src/hooks.ts index 0ecb0ae..aacdcfa 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -9,7 +9,7 @@ import { } from "@tanstack/react-query"; import { Routes } from "../backend/router"; import { wsClient } from "./wsClient"; -import { z } from "zod"; +import type { z } from "zod"; export const useWSQuery = < TController extends keyof Routes, diff --git a/src/main.tsx b/src/main.tsx index 56d230d..9a15b60 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -11,6 +11,7 @@ import { queryClient } from "./queryClient.ts"; import Home from "./views/home/Home.tsx"; import Settings from "./views/settings/Settings.tsx"; import MatchHistory from "./views/match-history/MatchHistory.tsx"; +import Collection from "./views/collection/Collection.tsx"; const setup = async () => { const token = localStorage.getItem("loginToken"); @@ -38,6 +39,7 @@ setup().then(() => { + diff --git a/src/views/collection/Collection.tsx b/src/views/collection/Collection.tsx new file mode 100644 index 0000000..363193e --- /dev/null +++ b/src/views/collection/Collection.tsx @@ -0,0 +1,29 @@ +import { testBoard } from "../../../shared/testBoard"; +import Board from "../../components/Board"; +import { themes } from "../../themes"; + +const Collection = () => { + return ( +
+

Collection

+
+ {themes.map((theme) => ( +
+

{theme.name}

+ {}} + restartGame={() => {}} + onRightClick={() => {}} + width={11 * 32} + height={4 * 32} + /> +
+ ))} +
+
+ ); +}; + +export default Collection;