diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 5633a9794..44e40c25f 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -860,7 +860,8 @@
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": {
"placeholder": "Type to guess...",
- "button": "Guess",
+ "guess": "Guess",
+ "next": "Next",
"no_results": "No results"
}
},
diff --git a/packages/website/src/pages/[...locale]/tools/guess/_index.tsx b/packages/website/src/pages/[...locale]/tools/guess/_index.tsx
index 7ef48b173..3c72e704b 100644
--- a/packages/website/src/pages/[...locale]/tools/guess/_index.tsx
+++ b/packages/website/src/pages/[...locale]/tools/guess/_index.tsx
@@ -1,16 +1,19 @@
import { Box, Flex } from '@radix-ui/themes';
+import { Suspense } from 'react';
import { awaitableModelDefinitions } from '../../../../core/awaitables/modelDefinitions';
import { awaitableProvisionDefinitions } from '../../../../core/awaitables/provisionDefinitions';
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
-import { Var } from '../../../../core/radix/var';
import {
LocaleProvider,
type LocaleAcceptorProps,
} from '../../../../hooks/useLocale';
import { Duel } from '../../../../stores/duel';
+import { GuessEphemeral } from '../../../../stores/guessEphemeral';
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
-import { GuessRenderer } from './components/GuessRenderer';
+import { Background } from './components/Background';
import { Guesser } from './components/Guesser';
+import { GuessRenderer } from './components/GuessRenderer';
+import { GuessRendererLoader } from './components/GuessRendererLoader';
const [tankDefinitions, modelDefinitions, provisionDefinitions] =
await Promise.all([
@@ -22,52 +25,40 @@ const [tankDefinitions, modelDefinitions, provisionDefinitions] =
const ids = Object.keys(tankDefinitions.tanks);
export function Page({ locale }: LocaleAcceptorProps) {
- const id = Number(ids[Math.floor(Math.random() * ids.length)]);
- const tank = tankDefinitions.tanks[id];
- const model = modelDefinitions.models[id];
+ const initialId = Number(ids[Math.floor(Math.random() * ids.length)]);
+ const initialTank = tankDefinitions.tanks[initialId];
return (
-
-
-
-
-
-
-
+
+
+
+
+
+ );
+}
+
+function Page2() {
+ const tank = GuessEphemeral.use((state) => state.tank);
+ const model = modelDefinitions.models[tank.id];
+
+ return (
+
+
+
+
+
);
}
function Content() {
return (
-
-
+
+
-
+ }>
+
+
diff --git a/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.css b/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.css
new file mode 100644
index 000000000..27600f511
--- /dev/null
+++ b/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.css
@@ -0,0 +1,14 @@
+.guess-background {
+ animation: moveBackground 180s linear infinite;
+}
+
+@keyframes moveBackground {
+ from {
+ top: 0;
+ left: 0;
+ }
+ to {
+ top: -100%;
+ left: -100%;
+ }
+}
diff --git a/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.tsx b/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.tsx
new file mode 100644
index 000000000..009574341
--- /dev/null
+++ b/packages/website/src/pages/[...locale]/tools/guess/components/Background/index.tsx
@@ -0,0 +1,76 @@
+import { Box } from '@radix-ui/themes';
+import { useEffect } from 'react';
+import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
+import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions';
+import { tankToDuelMember } from '../../../../../../core/blitzkit/tankToDuelMember';
+import { Var } from '../../../../../../core/radix/var';
+import { Duel } from '../../../../../../stores/duel';
+import { GuessEphemeral } from '../../../../../../stores/guessEphemeral';
+import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
+import './index.css';
+
+const [modelDefinitions, provisionDefinitions] = await Promise.all([
+ awaitableModelDefinitions,
+ awaitableProvisionDefinitions,
+]);
+
+export function Background() {
+ const state = GuessEphemeral.use((state) => state.guessState);
+ const tank = GuessEphemeral.use((state) => state.tank);
+ const mutateDuel = Duel.useMutation();
+ const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
+
+ useEffect(() => {
+ mutateDuel((state) => {
+ const model = modelDefinitions.models[tank.id];
+ state.protagonist = tankToDuelMember(tank, model, provisionDefinitions);
+ });
+ mutateTankopediaEphemeral((state) => {
+ state.model = modelDefinitions.models[tank.id];
+ });
+ }, [tank]);
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/website/src/pages/[...locale]/tools/guess/components/GuessRenderer.tsx b/packages/website/src/pages/[...locale]/tools/guess/components/GuessRenderer.tsx
index 9efb18f3a..266bff97e 100644
--- a/packages/website/src/pages/[...locale]/tools/guess/components/GuessRenderer.tsx
+++ b/packages/website/src/pages/[...locale]/tools/guess/components/GuessRenderer.tsx
@@ -19,6 +19,13 @@ export const revealEvent = new Quicklime();
export function GuessRenderer() {
const fog = useRef(new Fog('black', nearConcealed, farConcealed));
const reveal = useCallback((show: boolean) => {
+ if (!show) {
+ fog.current.near = nearConcealed;
+ fog.current.far = farConcealed;
+ invalidate();
+ return;
+ }
+
const t0 = Date.now() / 1000;
function frame() {
diff --git a/packages/website/src/pages/[...locale]/tools/guess/components/GuessRendererLoader.tsx b/packages/website/src/pages/[...locale]/tools/guess/components/GuessRendererLoader.tsx
new file mode 100644
index 000000000..da9479e91
--- /dev/null
+++ b/packages/website/src/pages/[...locale]/tools/guess/components/GuessRendererLoader.tsx
@@ -0,0 +1,14 @@
+import { Box, Spinner } from '@radix-ui/themes';
+
+export function GuessRendererLoader() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/website/src/pages/[...locale]/tools/guess/components/Guesser.tsx b/packages/website/src/pages/[...locale]/tools/guess/components/Guesser.tsx
index 17bb18d07..9f20b20d9 100644
--- a/packages/website/src/pages/[...locale]/tools/guess/components/Guesser.tsx
+++ b/packages/website/src/pages/[...locale]/tools/guess/components/Guesser.tsx
@@ -3,7 +3,11 @@ import {
TankDefinition,
TIER_ROMAN_NUMERALS,
} from '@blitzkit/core';
-import { MagnifyingGlassIcon, PaperPlaneIcon } from '@radix-ui/react-icons';
+import {
+ ArrowRightIcon,
+ MagnifyingGlassIcon,
+ PaperPlaneIcon,
+} from '@radix-ui/react-icons';
import {
Box,
Button,
@@ -21,6 +25,7 @@ import { SearchResults } from '../../../../../components/SearchResults';
import { awaitableTankDefinitions } from '../../../../../core/awaitables/tankDefinitions';
import { awaitableTankNames } from '../../../../../core/awaitables/tankNames';
import { useLocale } from '../../../../../hooks/useLocale';
+import { GuessEphemeral } from '../../../../../stores/guessEphemeral';
import { revealEvent } from './GuessRenderer';
const { go } = fuzzysort;
@@ -30,12 +35,17 @@ const [tankNames, tankDefinitions] = await Promise.all([
awaitableTankDefinitions,
]);
+const ids = Object.keys(tankDefinitions.tanks);
+
export function Guesser() {
+ const tank = GuessEphemeral.use((state) => state.tank);
+ const guessState = GuessEphemeral.use((state) => state.guessState);
const { strings, unwrap } = useLocale();
const input = useRef(null);
const [searching, setSearching] = useState(false);
const [results, setResults] = useState(null);
const [selected, setSelected] = useState(null);
+ const mutateGuessEphemeral = GuessEphemeral.useMutation();
const requestSearch = useCallback(() => {
setSelected(null);
@@ -124,6 +134,7 @@ export function Guesser() {
{
- revealEvent.dispatch(true);
+ if (guessState === null) {
+ console.log('what');
+ const correct = selected!.id === tank.id;
+
+ revealEvent.dispatch(true);
+ mutateGuessEphemeral((draft) => {
+ draft.guessState = correct;
+ draft.totalSeen++;
+ draft.correct += correct ? 1 : 0;
+ });
+ } else {
+ const id = Number(ids[Math.floor(Math.random() * ids.length)]);
+ const tank = tankDefinitions.tanks[id];
+
+ revealEvent.dispatch(false);
+ mutateGuessEphemeral((draft) => {
+ if (!input.current) return;
+
+ draft.tank = tank;
+ draft.guessState = null;
+ input.current.value = '';
+ });
+ }
}}
>
- {strings.website.tools.guess.search.button}
+ {guessState === null ? (
+ <>
+ {strings.website.tools.guess.search.guess}
+
+ >
+ ) : (
+ <>
+ {strings.website.tools.guess.search.next}
+
+ >
+ )}
diff --git a/packages/website/src/stores/guessEphemeral.ts b/packages/website/src/stores/guessEphemeral.ts
new file mode 100644
index 000000000..106f481be
--- /dev/null
+++ b/packages/website/src/stores/guessEphemeral.ts
@@ -0,0 +1,20 @@
+import type { TankDefinition } from '@blitzkit/core';
+import { create } from 'zustand';
+import { createContextualStore } from '../core/zustand/createContextualStore';
+
+export interface GuessEphemeral {
+ tank: TankDefinition;
+ // true = correct, false = incorrect, null = not guessed
+ guessState: boolean | null;
+ totalSeen: number;
+ correct: number;
+}
+
+export const GuessEphemeral = createContextualStore((tank: TankDefinition) =>
+ create()(() => ({
+ tank,
+ guessState: null,
+ totalSeen: 0,
+ correct: 0,
+ })),
+);