diff --git a/leaky-ships/components/Gamefield.tsx b/leaky-ships/components/Gamefield.tsx index 117329f..7c6601b 100644 --- a/leaky-ships/components/Gamefield.tsx +++ b/leaky-ships/components/Gamefield.tsx @@ -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 BorderTiles from './BorderTiles'; // import FogImages from './FogImages'; import HitElems from './HitElems'; import Labeling from './Labeling'; 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 useGameEvent from './useGameEvent'; +import { CSSProperties } from 'react'; 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 [lastLeftTile, setLastLeftTile] = useState(initlialLastLeftTile); - const [target, setTarget] = useState(initlialTarget); - const [eventReady, setEventReady] = useState(false); - const [appearOK, setAppearOK] = useState(false); - const [targetPreview, setTargetPreview] = useState(initlialTargetPreview); - const [targetPreviewPos, setTargetPreviewPos] = useState(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) - }, targetPreview.show ? 500 : 100); - - // or abort if movement is repeated early - return () => { - clearTimeout(autoTimeout); - } - }, [targetPreview.show]); + const { + targets, + eventBar, + setLastLeftTile, + setTarget, + setTargetPreviewPos, + hits, + DispatchHits + } = useGameEvent(); return (
@@ -92,18 +38,9 @@ function Gamefield() { {/* Fog images */} {/* */} -
- -
-
- -
-
-
- {items.map((e, i) => ( - - ))} + {targets}
+ {eventBar} ) } diff --git a/leaky-ships/components/Target.tsx b/leaky-ships/components/Target.tsx new file mode 100644 index 0000000..2e83f4a --- /dev/null +++ b/leaky-ships/components/Target.tsx @@ -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 ( +
+ +
+ ) +} + +export default Target \ No newline at end of file diff --git a/leaky-ships/components/useGameEvent.tsx b/leaky-ships/components/useGameEvent.tsx new file mode 100644 index 0000000..15a67a3 --- /dev/null +++ b/leaky-ships/components/useGameEvent.tsx @@ -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(initlialLastLeftTile); + const [target, setTarget] = useState(initlialTarget); + const [eventReady, setEventReady] = useState(false); + const [appearOK, setAppearOK] = useState(false); + const [targetPreview, setTargetPreview] = useState(initlialTargetPreview); + const [targetPreviewPos, setTargetPreviewPos] = useState(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, targetPreview]) + + const eventBar = useMemo(() => +
+ {items.map((e, i) => ( + + ))} +
, [items]) + + return { + targets, + eventBar, + setLastLeftTile, + setTarget, + setTargetPreviewPos, + hits, + DispatchHits + } +} + +export default useGameEvent \ No newline at end of file