Merge branch 'main' of https://github.com/MasterGordon/minesweeper into main
This commit is contained in:
commit
2efb88787f
|
|
@ -15,6 +15,7 @@ const requestDuration = new Histogram({
|
|||
help: "Request duration",
|
||||
labelNames: ["action"],
|
||||
});
|
||||
// promClient.register.registerMetric(requestDuration);
|
||||
|
||||
const metricsUser = process.env.METRICS_USER;
|
||||
const metricsPassword = process.env.METRICS_PASSWORD;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,19 @@ export const pickRandom = <T>(arr: T[]) => {
|
|||
return arr[index];
|
||||
};
|
||||
|
||||
function bashHashStr(str: string) {
|
||||
let hash = 5381,
|
||||
i = str.length;
|
||||
|
||||
while (i) {
|
||||
hash = (hash * 33) ^ str.charCodeAt(--i);
|
||||
}
|
||||
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
export const hashStr = (str: string) => {
|
||||
return [...str].reduce(
|
||||
(hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0,
|
||||
0,
|
||||
);
|
||||
return Number(`0.${bashHashStr(str)}`);
|
||||
};
|
||||
|
||||
export const weightedPickRandom = <T>(
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
import {
|
||||
ReactNode,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { LoadedTheme, Theme, useTheme } from "../themes/Theme";
|
||||
import {
|
||||
type LoadedTexture,
|
||||
type LoadedTheme,
|
||||
type Theme,
|
||||
useTheme,
|
||||
} from "../themes/Theme";
|
||||
import { Container, Sprite, Stage, useTick } from "@pixi/react";
|
||||
import Viewport from "./pixi/PixiViewport";
|
||||
import type { Viewport as PixiViewport } from "pixi-viewport";
|
||||
import {
|
||||
ClientGame,
|
||||
type ClientGame,
|
||||
getValue,
|
||||
isServerGame,
|
||||
ServerGame,
|
||||
type ServerGame,
|
||||
} from "../../shared/game";
|
||||
import { useWSQuery } from "../hooks";
|
||||
import { Texture } from "pixi.js";
|
||||
|
|
@ -37,6 +42,7 @@ import "@pixi/canvas-sprite-tiling";
|
|||
import "@pixi/canvas-sprite";
|
||||
import "@pixi/canvas-text";
|
||||
import { themes } from "../themes";
|
||||
import { hashStr, weightedPickRandom } from "../../shared/utils";
|
||||
|
||||
interface BoardProps {
|
||||
className?: string;
|
||||
|
|
@ -271,6 +277,20 @@ const Tile = ({
|
|||
onRightClick,
|
||||
onLeftClick,
|
||||
}: TileProps) => {
|
||||
const resolveSprite = useCallback(
|
||||
(lt: LoadedTexture) => {
|
||||
if (Array.isArray(lt)) {
|
||||
console.log("hash:", hashStr(game.uuid + ";" + x + ";" + y));
|
||||
return weightedPickRandom(
|
||||
lt,
|
||||
(i) => i.weight,
|
||||
(tw) => hashStr(game.uuid + ";" + x + ";" + y) * tw,
|
||||
).sprite;
|
||||
}
|
||||
return lt;
|
||||
},
|
||||
[game.uuid, x, y],
|
||||
);
|
||||
const i = x;
|
||||
const j = y;
|
||||
const isRevealed = game.isRevealed[i][j];
|
||||
|
|
@ -285,11 +305,13 @@ const Tile = ({
|
|||
const isQuestionMark = game.isQuestionMark[i][j];
|
||||
const base =
|
||||
isRevealed || (isMine && !isFlagged) ? (
|
||||
<Sprite key="b" texture={theme.revealed} />
|
||||
<Sprite key="b" texture={resolveSprite(theme.revealed)} />
|
||||
) : (
|
||||
<Sprite key="b" texture={theme.tile} />
|
||||
<Sprite key="b" texture={resolveSprite(theme.tile)} />
|
||||
);
|
||||
const extra = isLastPos ? <Sprite key="e" texture={theme.lastPos} /> : null;
|
||||
const extra = isLastPos ? (
|
||||
<Sprite key="e" texture={resolveSprite(theme.lastPos)} />
|
||||
) : null;
|
||||
const touchStart = useRef<number>(0);
|
||||
const isMove = useRef<boolean>(false);
|
||||
const startX = useRef<number>(0);
|
||||
|
|
@ -323,14 +345,24 @@ const Tile = ({
|
|||
);
|
||||
let content: ReactNode = null;
|
||||
if (isFlagged) {
|
||||
content = <Sprite key="c" texture={theme.flag} {...baseProps} />;
|
||||
content = (
|
||||
<Sprite key="c" texture={resolveSprite(theme.flag)} {...baseProps} />
|
||||
);
|
||||
} else if (isMine) {
|
||||
content = <Sprite key="c" texture={theme.mine} {...baseProps} />;
|
||||
content = (
|
||||
<Sprite key="c" texture={resolveSprite(theme.mine)} {...baseProps} />
|
||||
);
|
||||
} else if (value !== -1 && isRevealed) {
|
||||
const img = theme[value.toString() as keyof Theme] as Texture;
|
||||
content = img ? <Sprite key="c" texture={img} {...baseProps} /> : null;
|
||||
} else if (isQuestionMark) {
|
||||
content = <Sprite key="c" texture={theme.questionMark} {...baseProps} />;
|
||||
content = (
|
||||
<Sprite
|
||||
key="c"
|
||||
texture={resolveSprite(theme.questionMark)}
|
||||
{...baseProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const [, setCursorX] = useAtom(cursorXAtom);
|
||||
const [, setCursorY] = useAtom(cursorYAtom);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@ import { useEffect, useState } from "react";
|
|||
type Png = typeof import("*.png");
|
||||
type LazySprite = () => Promise<Png>;
|
||||
|
||||
interface WeightedLazySprites {
|
||||
weight: number;
|
||||
sprite: LazySprite;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
size: number;
|
||||
mine: LazySprite;
|
||||
tile: LazySprite;
|
||||
mine: LazySprite | WeightedLazySprites[];
|
||||
tile: LazySprite | WeightedLazySprites[];
|
||||
revealed: LazySprite;
|
||||
flag: LazySprite;
|
||||
flag: LazySprite | WeightedLazySprites[];
|
||||
questionMark: LazySprite;
|
||||
lastPos: LazySprite;
|
||||
1: LazySprite;
|
||||
|
|
@ -22,7 +27,17 @@ export interface Theme {
|
|||
8: LazySprite;
|
||||
}
|
||||
|
||||
export type LoadedTheme = Record<Exclude<keyof Theme, "size">, Texture> & {
|
||||
export type LoadedTexture =
|
||||
| Texture
|
||||
| {
|
||||
weight: number;
|
||||
sprite: Texture;
|
||||
}[];
|
||||
|
||||
export type LoadedTheme = Record<
|
||||
Exclude<keyof Theme, "size">,
|
||||
LoadedTexture
|
||||
> & {
|
||||
size: number;
|
||||
};
|
||||
|
||||
|
|
@ -34,13 +49,25 @@ export const useTheme = (theme: Theme) => {
|
|||
const loadTheme = async () => {
|
||||
const loadedEntries = await Promise.all(
|
||||
Object.entries(theme).map(async ([key, value]) => {
|
||||
const loaded =
|
||||
typeof value === "function"
|
||||
? await Assets.load((await value()).default)
|
||||
: value;
|
||||
let loaded = value;
|
||||
if (typeof value === "function") {
|
||||
loaded = await Assets.load((await value()).default);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
loaded = await Promise.all(
|
||||
loaded.map(async (sprite: WeightedLazySprites) => {
|
||||
return {
|
||||
weight: sprite.weight,
|
||||
sprite: await Assets.load((await sprite.sprite()).default),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return [key, loaded] as const;
|
||||
}),
|
||||
);
|
||||
console.log("loaded", Object.fromEntries(loadedEntries));
|
||||
setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme);
|
||||
};
|
||||
loadTheme();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import { Theme } from "./Theme";
|
||||
import type { Theme } from "./Theme";
|
||||
|
||||
export const techiesDireTheme: Theme = {
|
||||
size: 32,
|
||||
mine: () => import("../assets/themes/techies/dire/mine-1.png"),
|
||||
mine: [
|
||||
{
|
||||
weight: 0.5,
|
||||
sprite: () => import("../assets/themes/techies/dire/mine-1.png"),
|
||||
},
|
||||
{
|
||||
weight: 0.5,
|
||||
sprite: () => import("../assets/themes/techies/dire/mine-2.png"),
|
||||
},
|
||||
],
|
||||
tile: () => import("../assets/themes/techies/dire/tile-1.png"),
|
||||
revealed: () => import("../assets/themes/techies/dire/revealed.png"),
|
||||
flag: () => import("../assets/themes/techies/flag.png"),
|
||||
|
|
|
|||
|
|
@ -184,11 +184,11 @@ const Store = () => {
|
|||
variant="outline"
|
||||
size="default"
|
||||
className="mx-auto items-center"
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
openLootbox
|
||||
.mutateAsync({ id: lootbox.id })
|
||||
.then(() => refetch())
|
||||
}
|
||||
.then(() => refetch());
|
||||
}}
|
||||
>
|
||||
Buy for <b>{lootbox.priceText}</b> <GemsIcon />
|
||||
</Button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue