Rewrote targetPreview for pulsation effect

This commit is contained in:
aronmal 2023-01-09 01:13:10 +01:00
parent 7196f45ee8
commit 9230b06f6c
Signed by: aronmal
GPG key ID: 816B7707426FC612
5 changed files with 67 additions and 55 deletions

View file

@ -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<SetStateAction<TargetType>>, setTargetPreview: Dispatch<SetStateAction<TargetPreviewType>>, hits: HitType[], DispatchHits: Dispatch<HitDispatchType> } }) {
function BorderTiles({ props: { count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile } }: {
props: {
count: number,
setTarget: Dispatch<SetStateAction<TargetType>>,
setTargetPreviewPos: Dispatch<SetStateAction<TargetPreviewPosType>>,
hits: HitType[],
DispatchHits: Dispatch<HitDispatchType>,
setLastLeftTile: Dispatch<SetStateAction<LastLeftTileType>>
}
}) {
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 })}
></div>
})}
</>

View file

@ -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<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 { 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 (
<div id='gamefield'>
{/* <Bluetooth /> */}
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
{/* Bordes */}
<BorderTiles count={count} actions={{ setTarget, setTargetPreview, hits, DispatchHits }} />
<BorderTiles props={{ count, setTarget, setTargetPreviewPos, hits, DispatchHits, setLastLeftTile }} />
{/* Collumn lettes and row numbers */}
<Labeling count={count} />
@ -89,16 +92,16 @@ function Gamefield() {
{/* Fog images */}
{/* <FogImages /> */}
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{ '--x': target.x, '--y': target.y } as CSSProperties}>
<div className={`hit-svg target-preview ${targetPreview.show ? 'show' : ''}`} style={{ '--x': targetPreview.x, '--y': targetPreview.y } as CSSProperties}>
<FontAwesomeIcon icon={faCrosshairs} />
</div>
<div className={`hit-svg target-preview ${targetPreview.show ? 'show' : ''}`} style={{ '--x': targetPreview.x, '--y': targetPreview.y } as CSSProperties}>
<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 => (
<Item props={e} />
{items.map((e, i) => (
<Item key={i} props={e} />
))}
</div>
</div>

View file

@ -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);

View file

@ -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,

View file

@ -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 {