mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: more progress
This commit is contained in:
1 parent
08eed5ce31
commit
4218626cbf
1 file changed
+178
-60
+178
-60
@@ -1,93 +1,211 @@
|
|||||||
import { Button, Card, Code, Flex, Switch, Text } from '@radix-ui/themes';
|
import { ChevronUpIcon, PlayIcon } from '@radix-ui/react-icons';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
Code,
|
||||||
|
Flex,
|
||||||
|
Select,
|
||||||
|
Text,
|
||||||
|
} from '@radix-ui/themes';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import type { TankCharacteristics } from '../../../../../../core/blitzkit/tankCharacteristics';
|
import type { TankCharacteristics } from '../../../../../../core/blitzkit/tankCharacteristics';
|
||||||
|
import { Var } from '../../../../../../core/radix/var';
|
||||||
|
|
||||||
interface DispersionVisualizerProps {
|
interface DispersionVisualizerProps {
|
||||||
stats: TankCharacteristics;
|
stats: TankCharacteristics;
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshRate = 1000 / 60; // ms
|
const BASE_SIZE = 10; // rem
|
||||||
|
const REFRESH_RATE = 1000 / 60; // ms
|
||||||
|
const VISUALIZER_TYPES = [
|
||||||
|
'afterMove',
|
||||||
|
'afterHullTraverse',
|
||||||
|
'afterTurretTraverse',
|
||||||
|
'afterShot',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type VisualizerType = (typeof VISUALIZER_TYPES)[number];
|
||||||
|
|
||||||
export function DispersionVisualizer({ stats }: DispersionVisualizerProps) {
|
export function DispersionVisualizer({ stats }: DispersionVisualizerProps) {
|
||||||
const aInfinity = stats.dispersion;
|
|
||||||
const tau = stats.aimTime;
|
|
||||||
const timeText = useRef<HTMLElement>(null);
|
const timeText = useRef<HTMLElement>(null);
|
||||||
const aText = useRef<HTMLElement>(null);
|
const dispersionText = useRef<HTMLElement>(null);
|
||||||
const [a0, setA0] = useState(stats.dispersionMoving);
|
const serverSideCircle = useRef<HTMLDivElement>(null);
|
||||||
const [stateDiscriminator, setStateDiscriminator] = useState(0);
|
const [visualize, setVisualize] = useState<VisualizerType>('afterMove');
|
||||||
const [gunBroken, setGunBroken] = useState(false);
|
const [gunDamaged, setGunDamaged] = useState(false);
|
||||||
|
const [stateDiscriminator, setStateDiscriminator] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const tau = stats.aimTime;
|
||||||
let t = 0;
|
let t = 0;
|
||||||
|
let coefficient: number;
|
||||||
|
const dispersionBase = stats.dispersion;
|
||||||
|
let dispersionAdditive: number;
|
||||||
|
|
||||||
|
if (gunDamaged) {
|
||||||
|
coefficient = stats.dispersionGunDamaged;
|
||||||
|
} else {
|
||||||
|
coefficient = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (visualize) {
|
||||||
|
case 'afterMove':
|
||||||
|
dispersionAdditive = stats.dispersionMoving;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'afterHullTraverse':
|
||||||
|
dispersionAdditive = stats.dispersionHullTraversing;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'afterTurretTraverse':
|
||||||
|
dispersionAdditive = stats.dispersionTurretTraversing;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'afterShot':
|
||||||
|
dispersionAdditive = stats.dispersion * (stats.dispersionShooting - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (!timeText.current || !aText.current) return;
|
if (
|
||||||
|
!timeText.current ||
|
||||||
t += refreshRate / 1000; // ms -> s
|
!dispersionText.current ||
|
||||||
const a = aInfinity + a0 * Math.exp(-t / tau);
|
!serverSideCircle.current
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
t += REFRESH_RATE / 1000; // ms -> s
|
||||||
|
const dispersion =
|
||||||
|
coefficient *
|
||||||
|
(dispersionBase + dispersionAdditive * Math.exp(-t / tau));
|
||||||
timeText.current.innerText = `t = ${t.toFixed(1)}s`;
|
timeText.current.innerText = `t = ${t.toFixed(1)}s`;
|
||||||
aText.current.innerText = `a = ${a.toFixed(3)}m`;
|
dispersionText.current.innerText = `a = ${dispersion.toFixed(3)}m`;
|
||||||
}, refreshRate);
|
|
||||||
|
const serverSideSize = `${(dispersion / coefficient / dispersionBase) * BASE_SIZE}rem`;
|
||||||
|
serverSideCircle.current.style.width = serverSideSize;
|
||||||
|
serverSideCircle.current.style.height = serverSideSize;
|
||||||
|
}, REFRESH_RATE);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [aInfinity, tau, a0]);
|
}, [visualize, gunDamaged, stateDiscriminator]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<Flex direction="column" gap="4">
|
<Flex direction="column" gap="4">
|
||||||
<Flex gap="2" wrap="wrap">
|
<Flex align="center" justify="between">
|
||||||
<Button
|
<Flex align="center" gap="1">
|
||||||
variant="ghost"
|
<Text>After</Text>
|
||||||
onClick={() => {
|
<Select.Root
|
||||||
setA0(stats.dispersionMoving);
|
value={visualize}
|
||||||
}}
|
onValueChange={(value) => setVisualize(value as VisualizerType)}
|
||||||
|
>
|
||||||
|
<Select.Trigger />
|
||||||
|
<Select.Content>
|
||||||
|
<Select.Item value={'afterMove' satisfies VisualizerType}>
|
||||||
|
move
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item
|
||||||
|
value={'afterHullTraverse' satisfies VisualizerType}
|
||||||
|
>
|
||||||
|
hull traverse
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item
|
||||||
|
value={'afterTurretTraverse' satisfies VisualizerType}
|
||||||
|
>
|
||||||
|
turret traverse
|
||||||
|
</Select.Item>
|
||||||
|
<Select.Item value={'afterShot' satisfies VisualizerType}>
|
||||||
|
shot
|
||||||
|
</Select.Item>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
gap="2"
|
||||||
|
onClick={() => setGunDamaged((state) => !state)}
|
||||||
|
style={{ cursor: 'pointer', userSelect: 'none' }}
|
||||||
>
|
>
|
||||||
Move
|
<Text>Gun damaged</Text>
|
||||||
</Button>
|
<Checkbox checked={gunDamaged} />
|
||||||
<Button
|
</Flex>
|
||||||
variant="ghost"
|
|
||||||
onClick={() => {
|
|
||||||
setA0(stats.dispersionHullTraversing);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Hull traverse
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => {
|
|
||||||
setA0(stats.dispersionTurretTraversing);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Turret traverse
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => {
|
|
||||||
setA0(stats.dispersion + stats.dispersionShooting);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Shoot
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex align="center" gap="2">
|
<Box
|
||||||
<Switch
|
width="100%"
|
||||||
checked={gunBroken}
|
style={{
|
||||||
onCheckedChange={(checked) => setGunBroken(checked as boolean)}
|
aspectRatio: '1 / 1',
|
||||||
|
backgroundColor: Var('color-background'),
|
||||||
|
borderRadius: Var('radius-2'),
|
||||||
|
}}
|
||||||
|
// overflow="hidden"
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
style={{
|
||||||
|
transform: `translate(-50%, -${1 / 8}rem)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text size="6" trim="both">
|
||||||
|
<ChevronUpIcon width="1em" height="1em" />
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
width={`${BASE_SIZE}rem`}
|
||||||
|
height={`${BASE_SIZE}rem`}
|
||||||
|
style={{
|
||||||
|
borderRadius: '100%',
|
||||||
|
outline: `${1 / 16}rem solid ${Var('gray-9')}`,
|
||||||
|
outlineOffset: `-${1 / 16 / 2}rem`,
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Text>Gun broken</Text>
|
<Box
|
||||||
</Flex>
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
width={`${BASE_SIZE}rem`}
|
||||||
|
height={`${BASE_SIZE}rem`}
|
||||||
|
ref={serverSideCircle}
|
||||||
|
style={{
|
||||||
|
borderRadius: '100%',
|
||||||
|
outline: `${1 / 8}rem solid ${Var('green-9')}`,
|
||||||
|
outlineOffset: `-${1 / 8 / 2}rem`,
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
justify="between"
|
||||||
|
position="absolute"
|
||||||
|
bottom="0"
|
||||||
|
width="100%"
|
||||||
|
p="2"
|
||||||
|
>
|
||||||
|
<Code color="gray" ref={timeText}>
|
||||||
|
t = 0s
|
||||||
|
</Code>
|
||||||
|
|
||||||
<Flex justify="between">
|
<Code color="gray" ref={dispersionText}>
|
||||||
<Code color="gray" ref={timeText}>
|
a = 0.000m
|
||||||
t = 0s
|
</Code>
|
||||||
</Code>
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Code color="gray" ref={aText}>
|
<Flex justify="end">
|
||||||
a = {(aInfinity + a0).toFixed(3)}m
|
<Button onClick={() => setStateDiscriminator((state) => !state)}>
|
||||||
</Code>
|
<PlayIcon /> Play
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in new issue
Block a user