This commit is contained in:
CutieCat2804 2024-10-18 17:09:56 +02:00
commit 2efb88787f
6 changed files with 104 additions and 27 deletions

View File

@ -15,6 +15,7 @@ const requestDuration = new Histogram({
help: "Request duration", help: "Request duration",
labelNames: ["action"], labelNames: ["action"],
}); });
// promClient.register.registerMetric(requestDuration);
const metricsUser = process.env.METRICS_USER; const metricsUser = process.env.METRICS_USER;
const metricsPassword = process.env.METRICS_PASSWORD; const metricsPassword = process.env.METRICS_PASSWORD;

View File

@ -3,11 +3,19 @@ export const pickRandom = <T>(arr: T[]) => {
return arr[index]; 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) => { export const hashStr = (str: string) => {
return [...str].reduce( return Number(`0.${bashHashStr(str)}`);
(hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0,
0,
);
}; };
export const weightedPickRandom = <T>( export const weightedPickRandom = <T>(

View File

@ -1,20 +1,25 @@
import { import {
ReactNode, type ReactNode,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
useState, useState,
} from "react"; } 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 { Container, Sprite, Stage, useTick } from "@pixi/react";
import Viewport from "./pixi/PixiViewport"; import Viewport from "./pixi/PixiViewport";
import type { Viewport as PixiViewport } from "pixi-viewport"; import type { Viewport as PixiViewport } from "pixi-viewport";
import { import {
ClientGame, type ClientGame,
getValue, getValue,
isServerGame, isServerGame,
ServerGame, type ServerGame,
} from "../../shared/game"; } from "../../shared/game";
import { useWSQuery } from "../hooks"; import { useWSQuery } from "../hooks";
import { Texture } from "pixi.js"; import { Texture } from "pixi.js";
@ -37,6 +42,7 @@ import "@pixi/canvas-sprite-tiling";
import "@pixi/canvas-sprite"; import "@pixi/canvas-sprite";
import "@pixi/canvas-text"; import "@pixi/canvas-text";
import { themes } from "../themes"; import { themes } from "../themes";
import { hashStr, weightedPickRandom } from "../../shared/utils";
interface BoardProps { interface BoardProps {
className?: string; className?: string;
@ -271,6 +277,20 @@ const Tile = ({
onRightClick, onRightClick,
onLeftClick, onLeftClick,
}: TileProps) => { }: 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 i = x;
const j = y; const j = y;
const isRevealed = game.isRevealed[i][j]; const isRevealed = game.isRevealed[i][j];
@ -285,11 +305,13 @@ const Tile = ({
const isQuestionMark = game.isQuestionMark[i][j]; const isQuestionMark = game.isQuestionMark[i][j];
const base = const base =
isRevealed || (isMine && !isFlagged) ? ( 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 touchStart = useRef<number>(0);
const isMove = useRef<boolean>(false); const isMove = useRef<boolean>(false);
const startX = useRef<number>(0); const startX = useRef<number>(0);
@ -323,14 +345,24 @@ const Tile = ({
); );
let content: ReactNode = null; let content: ReactNode = null;
if (isFlagged) { if (isFlagged) {
content = <Sprite key="c" texture={theme.flag} {...baseProps} />; content = (
<Sprite key="c" texture={resolveSprite(theme.flag)} {...baseProps} />
);
} else if (isMine) { } 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) { } else if (value !== -1 && isRevealed) {
const img = theme[value.toString() as keyof Theme] as Texture; const img = theme[value.toString() as keyof Theme] as Texture;
content = img ? <Sprite key="c" texture={img} {...baseProps} /> : null; content = img ? <Sprite key="c" texture={img} {...baseProps} /> : null;
} else if (isQuestionMark) { } 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 [, setCursorX] = useAtom(cursorXAtom);
const [, setCursorY] = useAtom(cursorYAtom); const [, setCursorY] = useAtom(cursorYAtom);

View File

@ -4,12 +4,17 @@ import { useEffect, useState } from "react";
type Png = typeof import("*.png"); type Png = typeof import("*.png");
type LazySprite = () => Promise<Png>; type LazySprite = () => Promise<Png>;
interface WeightedLazySprites {
weight: number;
sprite: LazySprite;
}
export interface Theme { export interface Theme {
size: number; size: number;
mine: LazySprite; mine: LazySprite | WeightedLazySprites[];
tile: LazySprite; tile: LazySprite | WeightedLazySprites[];
revealed: LazySprite; revealed: LazySprite;
flag: LazySprite; flag: LazySprite | WeightedLazySprites[];
questionMark: LazySprite; questionMark: LazySprite;
lastPos: LazySprite; lastPos: LazySprite;
1: LazySprite; 1: LazySprite;
@ -22,7 +27,17 @@ export interface Theme {
8: LazySprite; 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; size: number;
}; };
@ -34,13 +49,25 @@ export const useTheme = (theme: Theme) => {
const loadTheme = async () => { const loadTheme = async () => {
const loadedEntries = await Promise.all( const loadedEntries = await Promise.all(
Object.entries(theme).map(async ([key, value]) => { Object.entries(theme).map(async ([key, value]) => {
const loaded = let loaded = value;
typeof value === "function" if (typeof value === "function") {
? await Assets.load((await value()).default) loaded = await Assets.load((await value()).default);
: value; }
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; return [key, loaded] as const;
}), }),
); );
console.log("loaded", Object.fromEntries(loadedEntries));
setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme); setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme);
}; };
loadTheme(); loadTheme();

View File

@ -1,8 +1,17 @@
import { Theme } from "./Theme"; import type { Theme } from "./Theme";
export const techiesDireTheme: Theme = { export const techiesDireTheme: Theme = {
size: 32, 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"), tile: () => import("../assets/themes/techies/dire/tile-1.png"),
revealed: () => import("../assets/themes/techies/dire/revealed.png"), revealed: () => import("../assets/themes/techies/dire/revealed.png"),
flag: () => import("../assets/themes/techies/flag.png"), flag: () => import("../assets/themes/techies/flag.png"),

View File

@ -184,11 +184,11 @@ const Store = () => {
variant="outline" variant="outline"
size="default" size="default"
className="mx-auto items-center" className="mx-auto items-center"
onClick={() => onClick={() => {
openLootbox openLootbox
.mutateAsync({ id: lootbox.id }) .mutateAsync({ id: lootbox.id })
.then(() => refetch()) .then(() => refetch());
} }}
> >
Buy for <b>{lootbox.priceText}</b> <GemsIcon /> Buy for <b>{lootbox.priceText}</b> <GemsIcon />
</Button> </Button>