Add custom useGameEvent hook
This commit is contained in:
parent
9230b06f6c
commit
b928510632
3 changed files with 119 additions and 76 deletions
|
@ -1,79 +1,25 @@
|
||||||
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { CSSProperties, useEffect, useReducer, useState } from 'react';
|
|
||||||
// import Bluetooth from './Bluetooth';
|
// import Bluetooth from './Bluetooth';
|
||||||
import BorderTiles from './BorderTiles';
|
import BorderTiles from './BorderTiles';
|
||||||
// import FogImages from './FogImages';
|
// import FogImages from './FogImages';
|
||||||
import HitElems from './HitElems';
|
import HitElems from './HitElems';
|
||||||
import Labeling from './Labeling';
|
import Labeling from './Labeling';
|
||||||
import Ships from './Ships';
|
import Ships from './Ships';
|
||||||
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
|
|
||||||
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
|
|
||||||
import Item from './Item';
|
import Item from './Item';
|
||||||
|
import useGameEvent from './useGameEvent';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
function Gamefield() {
|
function Gamefield() {
|
||||||
|
|
||||||
const items = [
|
|
||||||
{ icon: 'burger-menu', text: 'Menu', cllFn: () => { } },
|
|
||||||
{ icon: 'radar', text: 'Radar scan', cllFn: () => { } },
|
|
||||||
{ icon: 'missle', text: 'Fire torpedo', cllFn: () => { } },
|
|
||||||
{ icon: 'scope', text: 'Fire missle', cllFn: () => { } },
|
|
||||||
{ icon: 'gear', text: 'Settings', cllFn: () => { } }
|
|
||||||
]
|
|
||||||
|
|
||||||
const count = 12;
|
const count = 12;
|
||||||
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
const {
|
||||||
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
targets,
|
||||||
const [eventReady, setEventReady] = useState(false);
|
eventBar,
|
||||||
const [appearOK, setAppearOK] = useState(false);
|
setLastLeftTile,
|
||||||
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
setTarget,
|
||||||
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
setTargetPreviewPos,
|
||||||
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
hits,
|
||||||
|
DispatchHits
|
||||||
// handle visibility and position change of targetPreview
|
} = useGameEvent();
|
||||||
useEffect(() => {
|
|
||||||
const { show, x, y } = targetPreview;
|
|
||||||
const { shouldShow } = targetPreviewPos;
|
|
||||||
// if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid
|
|
||||||
const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
|
|
||||||
const isSet = x === target.x && y === target.y
|
|
||||||
|
|
||||||
if (show && !appearOK)
|
|
||||||
setTargetPreview(e => ({ ...e, show: false }));
|
|
||||||
if (!show && shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !hasLeft && !isSet)
|
|
||||||
setTargetPreview(e => ({ ...e, show: true }));
|
|
||||||
}, [targetPreview, hits, eventReady, appearOK, lastLeftTile])
|
|
||||||
|
|
||||||
// enable targetPreview event again after 200 mil. sec.
|
|
||||||
useEffect(() => {
|
|
||||||
const { x: newX, y: newY } = targetPreviewPos;
|
|
||||||
setEventReady(false);
|
|
||||||
if (targetPreview.show || !appearOK)
|
|
||||||
return;
|
|
||||||
const autoTimeout = setTimeout(() => {
|
|
||||||
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
|
|
||||||
setEventReady(true);
|
|
||||||
setAppearOK(true);
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
// or abort if state has changed early
|
|
||||||
return () => {
|
|
||||||
clearTimeout(autoTimeout);
|
|
||||||
}
|
|
||||||
}, [targetPreviewPos, targetPreview.show, appearOK]);
|
|
||||||
|
|
||||||
// approve targetPreview new position after 200 mil. sec.
|
|
||||||
useEffect(() => {
|
|
||||||
// early return to start cooldown only when about to show up
|
|
||||||
const autoTimeout = setTimeout(() => {
|
|
||||||
setAppearOK(!targetPreview.show)
|
|
||||||
}, targetPreview.show ? 500 : 100);
|
|
||||||
|
|
||||||
// or abort if movement is repeated early
|
|
||||||
return () => {
|
|
||||||
clearTimeout(autoTimeout);
|
|
||||||
}
|
|
||||||
}, [targetPreview.show]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id='gamefield'>
|
<div id='gamefield'>
|
||||||
|
@ -92,18 +38,9 @@ function Gamefield() {
|
||||||
|
|
||||||
{/* Fog images */}
|
{/* Fog images */}
|
||||||
{/* <FogImages /> */}
|
{/* <FogImages /> */}
|
||||||
<div className={`hit-svg target-preview ${targetPreview.show ? 'show' : ''}`} style={{ '--x': targetPreview.x, '--y': targetPreview.y } as CSSProperties}>
|
{targets}
|
||||||
<FontAwesomeIcon icon={faCrosshairs} />
|
|
||||||
</div>
|
|
||||||
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{ '--x': target.x, '--y': target.y } as CSSProperties}>
|
|
||||||
<FontAwesomeIcon icon={faCrosshairs} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='event-bar'>
|
|
||||||
{items.map((e, i) => (
|
|
||||||
<Item key={i} props={e} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
{eventBar}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
13
leaky-ships/components/Target.tsx
Normal file
13
leaky-ships/components/Target.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
|
function Target({ preview, target: { x, y, show } }: { preview?: boolean, target: { x: number, y: number, show: boolean } }) {
|
||||||
|
return (
|
||||||
|
<div className={`hit-svg target${preview ? '-preview' : ''} ${show ? 'show' : ''}`} style={{ '--x': x, '--y': y } as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Target
|
93
leaky-ships/components/useGameEvent.tsx
Normal file
93
leaky-ships/components/useGameEvent.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { useEffect, useMemo, useReducer, useState } from 'react';
|
||||||
|
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
|
||||||
|
import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces';
|
||||||
|
import Item from './Item';
|
||||||
|
import Target from './Target';
|
||||||
|
|
||||||
|
function useGameEvent() {
|
||||||
|
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
||||||
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
|
const [eventReady, setEventReady] = useState(false);
|
||||||
|
const [appearOK, setAppearOK] = useState(false);
|
||||||
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
||||||
|
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
||||||
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
|
|
||||||
|
// handle visibility and position change of targetPreview
|
||||||
|
useEffect(() => {
|
||||||
|
const { show, x, y } = targetPreview;
|
||||||
|
const { shouldShow } = targetPreviewPos;
|
||||||
|
// if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid
|
||||||
|
const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
|
||||||
|
const isSet = x === target.x && y === target.y
|
||||||
|
|
||||||
|
if (show && !appearOK)
|
||||||
|
setTargetPreview(e => ({ ...e, show: false }));
|
||||||
|
if (!show && shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !hasLeft && !isSet)
|
||||||
|
setTargetPreview(e => ({ ...e, show: true }));
|
||||||
|
}, [targetPreview, hits, eventReady, appearOK, lastLeftTile])
|
||||||
|
|
||||||
|
// enable targetPreview event again after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
const { x: newX, y: newY } = targetPreviewPos;
|
||||||
|
setEventReady(false);
|
||||||
|
if (targetPreview.show || !appearOK)
|
||||||
|
return;
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setTargetPreview(e => ({ ...e, x: newX, y: newY }));
|
||||||
|
setEventReady(true);
|
||||||
|
setAppearOK(true);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
// or abort if state has changed early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [targetPreviewPos, targetPreview.show, appearOK]);
|
||||||
|
|
||||||
|
// approve targetPreview new position after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
// early return to start cooldown only when about to show up
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setAppearOK(!targetPreview.show)
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// or abort if movement is repeated early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [targetPreview.show]);
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ icon: 'burger-menu', text: 'Menu', cllFn: () => { } },
|
||||||
|
{ icon: 'radar', text: 'Radar scan', cllFn: () => { } },
|
||||||
|
{ icon: 'missle', text: 'Fire torpedo', cllFn: () => { } },
|
||||||
|
{ icon: 'scope', text: 'Fire missle', cllFn: () => { } },
|
||||||
|
{ icon: 'gear', text: 'Settings', cllFn: () => { } }
|
||||||
|
]
|
||||||
|
|
||||||
|
const targets = useMemo(() =>
|
||||||
|
<>
|
||||||
|
<Target target={target} />
|
||||||
|
<Target preview={true} target={targetPreview} />
|
||||||
|
</>, [target, targetPreview])
|
||||||
|
|
||||||
|
const eventBar = useMemo(() =>
|
||||||
|
<div className='event-bar'>
|
||||||
|
{items.map((e, i) => (
|
||||||
|
<Item key={i} props={e} />
|
||||||
|
))}
|
||||||
|
</div>, [items])
|
||||||
|
|
||||||
|
return {
|
||||||
|
targets,
|
||||||
|
eventBar,
|
||||||
|
setLastLeftTile,
|
||||||
|
setTarget,
|
||||||
|
setTargetPreviewPos,
|
||||||
|
hits,
|
||||||
|
DispatchHits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGameEvent
|
Loading…
Add table
Add a link
Reference in a new issue