added collection
This commit is contained in:
parent
538750b691
commit
3e0ace5230
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<typeof clientGame>;
|
||||
export type ServerGame = z.infer<typeof serverGame>;
|
||||
import type { ServerGame, ClientGame } from "./gameType";
|
||||
export type { ServerGame, ClientGame } from "./gameType";
|
||||
|
||||
export const isServerGame = (game: ServerGame | ClientGame) => "mines" in game;
|
||||
export const isClientGame = (
|
||||
|
|
|
|||
|
|
@ -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<typeof clientGame>;
|
||||
export type ServerGame = z.infer<typeof serverGame>;
|
||||
|
|
@ -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<boolean>(6).fill(true)],
|
||||
[...Array<boolean>(11).fill(true)],
|
||||
[...Array<boolean>(11).fill(true)],
|
||||
[...Array<boolean>(6).fill(true), ...Array<boolean>(5).fill(false)],
|
||||
]),
|
||||
isFlagged: rotate([
|
||||
[true, ...Array<boolean>(10).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
]),
|
||||
finished: 1,
|
||||
started: 1,
|
||||
stage: 420,
|
||||
lastClick: [2, 2],
|
||||
mines: rotate([
|
||||
[false, false, false, false, false, ...Array<boolean>(6).fill(true)],
|
||||
[...Array<boolean>(8).fill(false), true, false, true],
|
||||
[false, false, ...Array<boolean>(9).fill(true)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
]),
|
||||
minesCount: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8,
|
||||
isQuestionMark: rotate([
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
[...Array<boolean>(11).fill(false)],
|
||||
]),
|
||||
};
|
||||
|
|
@ -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<typeof userSettings>;
|
||||
export type UserSettingsInput = z.input<typeof userSettings>;
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ const Shell: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
transition={{ type: "tween" }}
|
||||
layout
|
||||
/>
|
||||
<motion.div className="flex flex-col gap-4 grow max-w-6xl mx-auto w-[calc(100vw-256px)]">
|
||||
<motion.div className="flex flex-col gap-4 grow max-w-7xl mx-auto w-[calc(100vw-256px)]">
|
||||
<div className="flex flex-col justify-center gap-4 sm:mx-16 mt-16 sm:mt-2 mx-2">
|
||||
<Header />
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -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<BoardProps> = (props) => {
|
|||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setInterval(() => {
|
||||
if (viewportRef.current) onViewportChange(viewportRef.current);
|
||||
}, 200);
|
||||
}, [game.width, game.height, onViewportChange]);
|
||||
|
|
@ -113,14 +125,25 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
|
||||
const viewportRef = useRef<PixiViewport>(null);
|
||||
const [zenMode, setZenMode] = useState(false);
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}, [ref]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div
|
||||
className={cn(
|
||||
"w-full h-[70vh] overflow-hidden border-white/40 border-2 flex flex-col",
|
||||
"w-full h-[70vh] overflow-hidden outline-white/40 outline-2 flex flex-col",
|
||||
zenMode && "fixed top-0 left-0 z-50 right-0 bottom-0 h-[100vh]",
|
||||
)}
|
||||
style={{
|
||||
width: props.width ? `${props.width}px` : undefined,
|
||||
height: props.height ? `${props.height}px` : undefined,
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<div className="relative">
|
||||
|
|
@ -135,7 +158,7 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
onClick={() => setZenMode(!zenMode)}
|
||||
size="sm"
|
||||
>
|
||||
{!zenMode ? (
|
||||
{props.width || props.height ? undefined : !zenMode ? (
|
||||
<Maximize2 className="size-4" />
|
||||
) : (
|
||||
<Minimize2 className="size-4" />
|
||||
|
|
@ -152,7 +175,7 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
</div>
|
||||
{theme && (
|
||||
<Stage
|
||||
options={{ hello: true }}
|
||||
options={{ hello: true, forceCanvas: !!props.width }}
|
||||
width={width}
|
||||
height={height}
|
||||
className="select-none"
|
||||
|
|
@ -163,12 +186,16 @@ const Board: React.FC<BoardProps> = (props) => {
|
|||
worldHeight={boardHeight}
|
||||
width={width}
|
||||
height={height}
|
||||
clamp={{
|
||||
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<BoardProps> = (props) => {
|
|||
</Stage>
|
||||
)}
|
||||
</div>
|
||||
<Coords />
|
||||
{!props.width && !props.height && <Coords />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
|||
</Route>
|
||||
<Route path="/history" component={MatchHistory} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/collection" component={Collection} />
|
||||
</Switch>
|
||||
</Shell>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { testBoard } from "../../../shared/testBoard";
|
||||
import Board from "../../components/Board";
|
||||
import { themes } from "../../themes";
|
||||
|
||||
const Collection = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<h2 className="text-white/90 text-xl">Collection</h2>
|
||||
<div className="flex flex-row gap-y-4 gap-x-8 items-center w-full flex-wrap justify-center">
|
||||
{themes.map((theme) => (
|
||||
<div key={theme.id}>
|
||||
<h3 className="text-white/90 text-lg">{theme.name}</h3>
|
||||
<Board
|
||||
game={testBoard}
|
||||
theme={theme.theme}
|
||||
onLeftClick={() => {}}
|
||||
restartGame={() => {}}
|
||||
onRightClick={() => {}}
|
||||
width={11 * 32}
|
||||
height={4 * 32}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Collection;
|
||||
Loading…
Reference in New Issue