From e803832812f836965cc23d3ec0e2b3664a3438dc Mon Sep 17 00:00:00 2001 From: aronmal Date: Mon, 9 Jan 2023 01:13:10 +0100 Subject: [PATCH] Rewrote targetPreview for pulsation effect --- leaky-ships/components/BorderTiles.tsx | 20 ++++++-- leaky-ships/components/Gamefield.tsx | 63 ++++++++++++++------------ leaky-ships/helpers.ts | 14 ++++-- leaky-ships/interfaces.ts | 14 ++++-- leaky-ships/styles/App.scss | 11 +---- 5 files changed, 67 insertions(+), 55 deletions(-) diff --git a/leaky-ships/components/BorderTiles.tsx b/leaky-ships/components/BorderTiles.tsx index 144c8ca..df4e7b7 100644 --- a/leaky-ships/components/BorderTiles.tsx +++ b/leaky-ships/components/BorderTiles.tsx @@ -1,8 +1,17 @@ import { CSSProperties, Dispatch, SetStateAction } from 'react'; import { borderCN, cornerCN, fieldIndex, isHit } from '../helpers'; -import { HitDispatchType, HitType, TargetPreviewType, TargetType } from '../interfaces'; +import { HitDispatchType, HitType, LastLeftTileType, TargetPreviewPosType, TargetType } from '../interfaces'; -function BorderTiles({ count, actions: { setTarget, setTargetPreview, hits, DispatchHits } }: { count: number, actions: { setTarget: Dispatch>, setTargetPreview: Dispatch>, hits: HitType[], DispatchHits: Dispatch } }) { +function BorderTiles({ props: { count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile } }: { + props: { + count: number, + setTarget: Dispatch>, + setTargetPreviewPos: Dispatch>, + hits: HitType[], + DispatchHits: Dispatch, + setLastLeftTile: Dispatch> + } +}) { let tilesProperties: { key: number, isGameTile: boolean, @@ -33,9 +42,9 @@ function BorderTiles({ count, actions: { setTarget, setTargetPreview, hits, Disp className={classNameString} style={{ '--x': x, '--y': y } as CSSProperties} onClick={() => { - if (!isGameTile && !isHit(hits, x, y).length) + if (!isGameTile || isHit(hits, x, y).length) return; - setTargetPreview(e => ({ ...e, shouldShow: false, show: false })) + setTargetPreviewPos(e => ({ ...e, shouldShow: false })) setTarget(t => { if (t.x === x && t.y === y) { DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } }); @@ -46,7 +55,8 @@ function BorderTiles({ count, actions: { setTarget, setTargetPreview, hits, Disp }); }} - onMouseEnter={() => setTargetPreview(e => ({ ...e, newX: x, newY: y, shouldShow: isGameTile }))} + onMouseEnter={() => setTargetPreviewPos({ x, y, shouldShow: isGameTile })} + onMouseLeave={() => setLastLeftTile({ x, y })} > })} diff --git a/leaky-ships/components/Gamefield.tsx b/leaky-ships/components/Gamefield.tsx index 6263740..117329f 100644 --- a/leaky-ships/components/Gamefield.tsx +++ b/leaky-ships/components/Gamefield.tsx @@ -7,8 +7,8 @@ import BorderTiles from './BorderTiles'; import HitElems from './HitElems'; import Labeling from './Labeling'; import Ships from './Ships'; -import { hitReducer, initlialTarget, initlialTargetPreview, isHit } from '../helpers'; -import { HitType, TargetPreviewType, TargetType } from '../interfaces'; +import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers'; +import { HitType, LastLeftTileType, TargetPreviewPosType, TargetPreviewType, TargetType } from '../interfaces'; import Item from './Item'; function Gamefield() { @@ -22,62 +22,65 @@ function Gamefield() { ] 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 { newX, newY, shouldShow, appearOK, eventReady, show, x, y } = targetPreview; - const positionChange = !(x === newX && y === newY); - const alreadyTargeting = target.show && target.x === targetPreview.newX && target.y === targetPreview.newY - // if not ready or no new position - if (!eventReady || (!positionChange && show)) - return; - if (show) { - // hide preview to change position when hidden - setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: false })); - } else if (shouldShow && appearOK && !isHit(hits, newX, newY).length && !alreadyTargeting) { - // BUT only appear again if it's supposed to (in case the mouse left over the edge) and () - setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: true, x: newX, y: newY })); - } - }, [targetPreview, hits]) + 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(() => { - if (targetPreview.eventReady) + const { x: newX, y: newY } = targetPreviewPos; + setEventReady(false); + if (targetPreview.show || !appearOK) return; const autoTimeout = setTimeout(() => { - setTargetPreview(e => ({ ...e, eventReady: true })); - }, 200); + setTargetPreview(e => ({ ...e, x: newX, y: newY })); + setEventReady(true); + setAppearOK(true); + }, 250); // or abort if state has changed early return () => { clearTimeout(autoTimeout); } - }, [targetPreview.eventReady]); + }, [targetPreviewPos, targetPreview.show, appearOK]); // approve targetPreview new position after 200 mil. sec. useEffect(() => { // early return to start cooldown only when about to show up - if (!targetPreview.shouldShow) - return; const autoTimeout = setTimeout(() => { - setTargetPreview(e => ({ ...e, appearOK: true })); - }, 350); + setAppearOK(!targetPreview.show) + }, targetPreview.show ? 500 : 100); // or abort if movement is repeated early return () => { clearTimeout(autoTimeout); } - }, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]); + }, [targetPreview.show]); return (
{/* */}
{/* Bordes */} - + {/* Collumn lettes and row numbers */} @@ -89,16 +92,16 @@ function Gamefield() { {/* Fog images */} {/* */} -
+
-
+
- {items.map(e => ( - + {items.map((e, i) => ( + ))}
diff --git a/leaky-ships/helpers.ts b/leaky-ships/helpers.ts index 16c919b..a0a2460 100644 --- a/leaky-ships/helpers.ts +++ b/leaky-ships/helpers.ts @@ -35,19 +35,23 @@ export const hitReducer = (formObject: HitType[], action: HitDispatchType) => { return formObject; } } +export const initlialLastLeftTile = { + x: 0, + y: 0 +}; export const initlialTarget = { show: false, x: 2, y: 2 }; export const initlialTargetPreview = { - newX: 0, - newY: 0, - shouldShow: false, - appearOK: false, - eventReady: true, show: false, x: 2, y: 2 }; +export const initlialTargetPreviewPos = { + shouldShow: false, + x: 0, + y: 0 +}; export const isHit = (hits: HitType[], x: number, y: number) => hits.filter(h => h.x === x && h.y === y); diff --git a/leaky-ships/interfaces.ts b/leaky-ships/interfaces.ts index e1c957a..3ad75f4 100644 --- a/leaky-ships/interfaces.ts +++ b/leaky-ships/interfaces.ts @@ -1,18 +1,22 @@ +export interface LastLeftTileType { + x: number, + y: number +} export interface TargetType { show: boolean, x: number, y: number }; export interface TargetPreviewType { - newX: number, - newY: number, - shouldShow: boolean, - appearOK: boolean, - eventReady: boolean, show: boolean, x: number, y: number }; +export interface TargetPreviewPosType { + shouldShow: boolean, + x: number, + y: number +} export interface FieldType { field: string, x: number, diff --git a/leaky-ships/styles/App.scss b/leaky-ships/styles/App.scss index d300b65..5e36fce 100644 --- a/leaky-ships/styles/App.scss +++ b/leaky-ships/styles/App.scss @@ -28,9 +28,6 @@ body { .App-header { background-color: $theme; min-height: 100vh; - // @include flex-col; - // align-items: center; - // justify-content: center; font-size: calc(10px + 2vmin); color: white; } @@ -57,8 +54,6 @@ body { } #game-frame { - // border: 1px solid orange; - // position: relative; // $height: 864px; $height: 648px; $width: $height; @@ -71,7 +66,6 @@ body { justify-items: center; grid-template-rows: .75fr repeat(12, 1fr) .75fr; grid-template-columns: .75fr repeat(12, 1fr) .75fr; - // grid-gap: 1px solid blue; >.label { grid-column: var(--x); @@ -95,7 +89,6 @@ body { > :not(.border) { box-sizing: border-box; - // border: 1px solid red; } >span { @@ -109,8 +102,6 @@ body { position: relative; @include flex-col; align-items: center; - // justify-content: center; - border: 1px solid yellow; grid-row: var(--x); pointer-events: none; @@ -172,7 +163,7 @@ body { &.target-preview { color: orange; opacity: 0; - @include transition(.2s); + @include transition(.5s); } &.show {